aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore48
-rw-r--r--.travis.yml14
-rw-r--r--Gemfile97
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.rdoc9
-rw-r--r--RELEASING_RAILS.rdoc44
-rw-r--r--[-rwxr-xr-x]Rakefile33
-rw-r--r--actionmailer/CHANGELOG449
-rw-r--r--actionmailer/CHANGELOG.md507
-rw-r--r--actionmailer/MIT-LICENSE2
-rw-r--r--actionmailer/README.rdoc18
-rw-r--r--[-rwxr-xr-x]actionmailer/Rakefile2
-rw-r--r--actionmailer/actionmailer.gemspec6
-rw-r--r--actionmailer/lib/action_mailer.rb6
-rw-r--r--actionmailer/lib/action_mailer/base.rb77
-rw-r--r--actionmailer/lib/action_mailer/collector.rb4
-rw-r--r--actionmailer/lib/action_mailer/delivery_methods.rb2
-rw-r--r--actionmailer/lib/action_mailer/log_subscriber.rb6
-rw-r--r--actionmailer/lib/action_mailer/mail_helper.rb9
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb5
-rw-r--r--actionmailer/lib/action_mailer/test_case.rb17
-rw-r--r--actionmailer/lib/action_mailer/test_helper.rb2
-rw-r--r--actionmailer/lib/action_mailer/version.rb4
-rw-r--r--actionmailer/lib/rails/generators/mailer/USAGE2
-rw-r--r--actionmailer/lib/rails/generators/mailer/templates/mailer.rb6
-rw-r--r--actionmailer/test/abstract_unit.rb32
-rw-r--r--actionmailer/test/asset_host_test.rb2
-rw-r--r--actionmailer/test/base_test.rb67
-rw-r--r--actionmailer/test/fixtures/anonymous/welcome.erb1
-rw-r--r--actionmailer/test/i18n_with_controller_test.rb2
-rw-r--r--actionmailer/test/mail_helper_test.rb44
-rw-r--r--actionmailer/test/mail_layout_test.rb2
-rw-r--r--actionmailer/test/url_test.rb4
-rw-r--r--actionpack/CHANGELOG5685
-rw-r--r--actionpack/CHANGELOG.md6164
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/README.rdoc8
-rw-r--r--actionpack/RUNNING_UNIT_TESTS2
-rw-r--r--[-rwxr-xr-x]actionpack/Rakefile20
-rw-r--r--actionpack/actionpack.gemspec12
-rw-r--r--actionpack/examples/performance.rb185
-rw-r--r--actionpack/examples/views/_collection.erb3
-rw-r--r--actionpack/examples/views/_hello.erb1
-rw-r--r--actionpack/examples/views/_hundred_partials.erb3
-rw-r--r--actionpack/examples/views/_partial.erb10
-rw-r--r--actionpack/examples/views/_ten_partials.erb10
-rw-r--r--actionpack/examples/views/hashes/_hash.erb3
-rw-r--r--actionpack/examples/views/my_hashes/_my_hash.erb3
-rw-r--r--actionpack/examples/views/template.html.erb1
-rw-r--r--actionpack/lib/abstract_controller.rb4
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb4
-rw-r--r--actionpack/lib/abstract_controller/base.rb18
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb46
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb6
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb193
-rw-r--r--actionpack/lib/abstract_controller/logger.rb5
-rw-r--r--actionpack/lib/abstract_controller/railties/routes_helpers.rb4
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb29
-rw-r--r--actionpack/lib/abstract_controller/url_for.rb12
-rw-r--r--actionpack/lib/abstract_controller/view_paths.rb4
-rw-r--r--actionpack/lib/action_controller.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb24
-rw-r--r--actionpack/lib/action_controller/caching.rb1
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb30
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb65
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb7
-rw-r--r--actionpack/lib/action_controller/deprecated.rb6
-rw-r--r--actionpack/lib/action_controller/deprecated/integration_test.rb3
-rw-r--r--actionpack/lib/action_controller/deprecated/performance_test.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb7
-rw-r--r--actionpack/lib/action_controller/metal.rb10
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb58
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb65
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb57
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb10
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb20
-rw-r--r--actionpack/lib/action_controller/metal/head.rb23
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb7
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb117
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb2
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb7
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb213
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb23
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb24
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb32
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb10
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb25
-rw-r--r--actionpack/lib/action_controller/metal/rescue.rb13
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb59
-rw-r--r--actionpack/lib/action_controller/metal/session_management.rb9
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb13
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb40
-rw-r--r--actionpack/lib/action_controller/railtie.rb31
-rw-r--r--actionpack/lib/action_controller/railties/helpers.rb22
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb24
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb20
-rw-r--r--actionpack/lib/action_controller/test_case.rb210
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/document.rb2
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb16
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb2
-rw-r--r--actionpack/lib/action_dispatch.rb19
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb56
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb17
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb18
-rw-r--r--actionpack/lib/action_dispatch/http/mime_types.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/rack_cache.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb74
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb42
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb18
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb57
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/closed_error.rb7
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb62
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_exceptions.rb82
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb80
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb72
-rw-r--r--actionpack/lib/action_dispatch/middleware/params_parser.rb13
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb30
-rw-r--r--actionpack/lib/action_dispatch/middleware/reloader.rb47
-rw-r--r--actionpack/lib/action_dispatch/middleware/remote_ip.rb148
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb36
-rw-r--r--actionpack/lib/action_dispatch/middleware/rescue.rb26
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb46
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb49
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb173
-rw-r--r--actionpack/lib/action_dispatch/middleware/ssl.rb70
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb12
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb1
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb23
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb19
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb166
-rw-r--r--actionpack/lib/action_dispatch/routing.rb25
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb408
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb24
-rw-r--r--actionpack/lib/action_dispatch/routing/redirection.rb165
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb260
-rw-r--r--actionpack/lib/action_dispatch/routing/routes_proxy.rb4
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb48
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/dom.rb14
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb65
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb20
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb30
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/tag.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb75
-rw-r--r--actionpack/lib/action_dispatch/testing/test_process.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb1
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_pack/version.rb4
-rw-r--r--actionpack/lib/action_view.rb12
-rw-r--r--actionpack/lib/action_view/asset_paths.rb10
-rw-r--r--actionpack/lib/action_view/base.rb29
-rw-r--r--actionpack/lib/action_view/buffers.rb2
-rw-r--r--actionpack/lib/action_view/context.rb4
-rw-r--r--actionpack/lib/action_view/flows.rb5
-rw-r--r--actionpack/lib/action_view/helpers.rb5
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb11
-rw-r--r--actionpack/lib/action_view/helpers/asset_paths.rb7
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb205
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb1
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb69
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb59
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/benchmark_helper.rb13
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb1
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb44
-rw-r--r--actionpack/lib/action_view/helpers/csrf_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb310
-rw-r--r--actionpack/lib/action_view/helpers/debug_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb705
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb379
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb115
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb50
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb413
-rw-r--r--actionpack/lib/action_view/helpers/output_safety_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb25
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb66
-rw-r--r--actionpack/lib/action_view/helpers/tags.rb34
-rw-r--r--actionpack/lib/action_view/helpers/tags/base.rb150
-rw-r--r--actionpack/lib/action_view/helpers/tags/check_box.rb62
-rw-r--r--actionpack/lib/action_view/helpers/tags/checkable.rb16
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb37
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_helpers.rb82
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb30
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_select.rb28
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_field.rb14
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_select.rb70
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_select.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/email_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/file_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb29
-rw-r--r--actionpack/lib/action_view/helpers/tags/hidden_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/label.rb65
-rw-r--r--actionpack/lib/action_view/helpers/tags/number_field.rb18
-rw-r--r--actionpack/lib/action_view/helpers/tags/password_field.rb12
-rw-r--r--actionpack/lib/action_view/helpers/tags/radio_button.rb31
-rw-r--r--actionpack/lib/action_view/helpers/tags/range_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/search_field.rb24
-rw-r--r--actionpack/lib/action_view/helpers/tags/select.rb41
-rw-r--r--actionpack/lib/action_view/helpers/tags/tel_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/text_area.rb18
-rw-r--r--actionpack/lib/action_view/helpers/tags/text_field.rb29
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_field.rb14
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_select.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_zone_select.rb20
-rw-r--r--actionpack/lib/action_view/helpers/tags/url_field.rb8
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb145
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb34
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb139
-rw-r--r--actionpack/lib/action_view/locale/en.yml4
-rw-r--r--actionpack/lib/action_view/log_subscriber.rb3
-rw-r--r--actionpack/lib/action_view/lookup_context.rb57
-rw-r--r--actionpack/lib/action_view/railtie.rb12
-rw-r--r--actionpack/lib/action_view/renderer/abstract_renderer.rb22
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb178
-rw-r--r--actionpack/lib/action_view/renderer/streaming_template_renderer.rb5
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb40
-rw-r--r--actionpack/lib/action_view/template.rb124
-rw-r--r--actionpack/lib/action_view/template/error.rb7
-rw-r--r--actionpack/lib/action_view/template/handlers.rb16
-rw-r--r--actionpack/lib/action_view/template/handlers/builder.rb11
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb30
-rw-r--r--actionpack/lib/action_view/template/handlers/raw.rb11
-rw-r--r--actionpack/lib/action_view/template/resolver.rb14
-rw-r--r--actionpack/lib/action_view/test_case.rb58
-rw-r--r--actionpack/lib/sprockets/assets.rake95
-rw-r--r--actionpack/lib/sprockets/bootstrap.rb37
-rw-r--r--actionpack/lib/sprockets/compressors.rb83
-rw-r--r--actionpack/lib/sprockets/helpers.rb6
-rw-r--r--actionpack/lib/sprockets/helpers/isolated_helper.rb13
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb166
-rw-r--r--actionpack/lib/sprockets/railtie.rb61
-rw-r--r--actionpack/lib/sprockets/static_compiler.rb61
-rw-r--r--actionpack/test/abstract/abstract_controller_test.rb7
-rw-r--r--actionpack/test/abstract/collector_test.rb4
-rw-r--r--actionpack/test/abstract/helper_test.rb4
-rw-r--r--actionpack/test/abstract/layouts_test.rb120
-rw-r--r--actionpack/test/abstract/translation_test.rb4
-rw-r--r--actionpack/test/abstract_unit.rb77
-rw-r--r--actionpack/test/activerecord/active_record_store_test.rb19
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb22
-rw-r--r--actionpack/test/activerecord/render_partial_with_record_identification_test.rb72
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb55
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb94
-rw-r--r--actionpack/test/controller/addresses_render_test.rb35
-rw-r--r--actionpack/test/controller/assert_select_test.rb11
-rw-r--r--actionpack/test/controller/base_test.rb105
-rw-r--r--actionpack/test/controller/caching_test.rb134
-rw-r--r--actionpack/test/controller/capture_test.rb6
-rw-r--r--actionpack/test/controller/content_type_test.rb12
-rw-r--r--actionpack/test/controller/default_url_options_with_filter_test.rb2
-rw-r--r--actionpack/test/controller/filters_test.rb25
-rw-r--r--actionpack/test/controller/flash_hash_test.rb81
-rw-r--r--actionpack/test/controller/flash_test.rb46
-rw-r--r--actionpack/test/controller/force_ssl_test.rb66
-rw-r--r--actionpack/test/controller/helper_test.rb7
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb2
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb47
-rw-r--r--actionpack/test/controller/integration_test.rb174
-rw-r--r--actionpack/test/controller/layout_test.rb15
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb13
-rw-r--r--actionpack/test/controller/mime_responds_test.rb168
-rw-r--r--actionpack/test/controller/new_base/bare_metal_test.rb72
-rw-r--r--actionpack/test/controller/new_base/content_type_test.rb2
-rw-r--r--actionpack/test/controller/new_base/render_layout_test.rb36
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb8
-rw-r--r--actionpack/test/controller/new_base/render_template_test.rb8
-rw-r--r--actionpack/test/controller/new_base/render_test.rb4
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb8
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb75
-rw-r--r--actionpack/test/controller/record_identifier_test.rb2
-rw-r--r--actionpack/test/controller/redirect_test.rb36
-rw-r--r--actionpack/test/controller/render_json_test.rb5
-rw-r--r--actionpack/test/controller/render_test.rb207
-rw-r--r--actionpack/test/controller/render_xml_test.rb4
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb96
-rw-r--r--actionpack/test/controller/rescue_test.rb32
-rw-r--r--actionpack/test/controller/resources_test.rb30
-rw-r--r--actionpack/test/controller/routing_test.rb598
-rw-r--r--actionpack/test/controller/runner_test.rb2
-rw-r--r--actionpack/test/controller/selector_test.rb2
-rw-r--r--actionpack/test/controller/send_file_test.rb17
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb71
-rw-r--r--actionpack/test/controller/sweeper_test.rb16
-rw-r--r--actionpack/test/controller/test_case_test.rb (renamed from actionpack/test/controller/test_test.rb)178
-rw-r--r--actionpack/test/controller/url_for_integration_test.rb38
-rw-r--r--actionpack/test/controller/url_for_test.rb47
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb2
-rw-r--r--actionpack/test/controller/view_paths_test.rb16
-rw-r--r--actionpack/test/controller/webservice_test.rb4
-rw-r--r--actionpack/test/dispatch/callbacks_test.rb11
-rw-r--r--actionpack/test/dispatch/cookies_test.rb153
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb157
-rw-r--r--actionpack/test/dispatch/header_test.rb5
-rw-r--r--actionpack/test/dispatch/mapper_test.rb19
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb15
-rw-r--r--actionpack/test/dispatch/mime_type_test.rb43
-rw-r--r--actionpack/test/dispatch/mount_test.rb7
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb31
-rw-r--r--actionpack/test/dispatch/reloader_test.rb37
-rw-r--r--actionpack/test/dispatch/request/json_params_parsing_test.rb22
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request/query_string_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request/session_test.rb48
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb4
-rw-r--r--actionpack/test/dispatch/request/xml_params_parsing_test.rb30
-rw-r--r--actionpack/test/dispatch/request_id_test.rb65
-rw-r--r--actionpack/test/dispatch/request_test.rb199
-rw-r--r--actionpack/test/dispatch/response_test.rb17
-rw-r--r--actionpack/test/dispatch/routing_assertions_test.rb18
-rw-r--r--actionpack/test/dispatch/routing_test.rb2600
-rw-r--r--actionpack/test/dispatch/session/abstract_store_test.rb56
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb179
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb4
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb4
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb131
-rw-r--r--actionpack/test/dispatch/ssl_test.rb157
-rw-r--r--actionpack/test/dispatch/static_test.rb101
-rw-r--r--actionpack/test/dispatch/uploaded_file_test.rb16
-rw-r--r--actionpack/test/dispatch/url_generation_test.rb15
-rw-r--r--actionpack/test/fixtures/addresses/list.erb1
-rw-r--r--actionpack/test/fixtures/company.rb4
-rw-r--r--actionpack/test/fixtures/developer.rb2
-rw-r--r--actionpack/test/fixtures/developers.yml2
-rw-r--r--actionpack/test/fixtures/fun/games/_game.erb2
-rw-r--r--actionpack/test/fixtures/fun/serious/games/_game.erb2
-rw-r--r--actionpack/test/fixtures/games/_game.erb1
-rw-r--r--actionpack/test/fixtures/layouts/with_html_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/plain_text.raw1
-rw-r--r--actionpack/test/fixtures/plain_text_with_characters.raw1
-rw-r--r--actionpack/test/fixtures/public/foo/こんにちは.html1
-rw-r--r--actionpack/test/fixtures/reply.rb2
-rw-r--r--actionpack/test/fixtures/routes/bogus.rb1
-rw-r--r--actionpack/test/fixtures/routes/external.rb1
-rw-r--r--actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css1
-rw-r--r--actionpack/test/fixtures/sprockets/app/images/logo.pngbin6646 -> 0 bytes
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/application.js1
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js0
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/extra.js0
-rw-r--r--actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js0
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/application.css1
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css0
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/extra.css0
-rw-r--r--actionpack/test/fixtures/sprockets/app/stylesheets/style.css0
-rw-r--r--actionpack/test/fixtures/test/_b_layout_for_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_content_tag_nested_in_content_tag.erb3
-rw-r--r--actionpack/test/fixtures/test/_label_with_block.erb4
-rw-r--r--actionpack/test/fixtures/test/_partial_html_erb.html.erb1
-rw-r--r--actionpack/test/fixtures/test/_partial_only_html.html1
-rw-r--r--actionpack/test/fixtures/test/hello_world_with_partial.html.erb2
-rw-r--r--actionpack/test/fixtures/test/one.html.erb1
-rw-r--r--actionpack/test/fixtures/test/with_html_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/test/with_partial.html.erb1
-rw-r--r--actionpack/test/fixtures/test/with_partial.text.erb1
-rw-r--r--actionpack/test/fixtures/test/with_xml_template.html.erb1
-rw-r--r--actionpack/test/fixtures/translations/templates/default.erb1
-rw-r--r--actionpack/test/fixtures/with_format.json.erb1
-rw-r--r--actionpack/test/lib/controller/fake_controllers.rb31
-rw-r--r--actionpack/test/lib/controller/fake_models.rb14
-rw-r--r--actionpack/test/lib/testing_sandbox.rb15
-rw-r--r--actionpack/test/routing/helper_test.rb31
-rw-r--r--actionpack/test/template/active_model_helper_test.rb31
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb469
-rw-r--r--actionpack/test/template/atom_feed_helper_test.rb38
-rw-r--r--actionpack/test/template/benchmark_helper_test.rb24
-rw-r--r--actionpack/test/template/capture_helper_test.rb85
-rw-r--r--actionpack/test/template/compiled_templates_test.rb6
-rw-r--r--actionpack/test/template/compressors_test.rb28
-rw-r--r--actionpack/test/template/date_helper_i18n_test.rb57
-rw-r--r--actionpack/test/template/date_helper_test.rb252
-rw-r--r--actionpack/test/template/erb/tag_helper_test.rb6
-rw-r--r--actionpack/test/template/erb_util_test.rb16
-rw-r--r--actionpack/test/template/form_collections_helper_test.rb336
-rw-r--r--actionpack/test/template/form_helper_test.rb1026
-rw-r--r--actionpack/test/template/form_options_helper_test.rb240
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb76
-rw-r--r--actionpack/test/template/html-scanner/cdata_node_test.rb2
-rw-r--r--actionpack/test/template/html-scanner/document_test.rb2
-rw-r--r--actionpack/test/template/html-scanner/node_test.rb2
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb29
-rw-r--r--actionpack/test/template/html-scanner/tag_node_test.rb9
-rw-r--r--actionpack/test/template/html-scanner/text_node_test.rb2
-rw-r--r--actionpack/test/template/html-scanner/tokenizer_test.rb2
-rw-r--r--actionpack/test/template/javascript_helper_test.rb43
-rw-r--r--actionpack/test/template/log_subscriber_test.rb4
-rw-r--r--actionpack/test/template/lookup_context_test.rb12
-rw-r--r--actionpack/test/template/number_helper_i18n_test.rb7
-rw-r--r--actionpack/test/template/number_helper_test.rb29
-rw-r--r--actionpack/test/template/output_buffer_test.rb16
-rw-r--r--actionpack/test/template/output_safety_helper_test.rb2
-rw-r--r--actionpack/test/template/record_tag_helper_test.rb85
-rw-r--r--actionpack/test/template/render_test.rb190
-rw-r--r--actionpack/test/template/sanitize_helper_test.rb2
-rw-r--r--actionpack/test/template/sprockets_helper_test.rb311
-rw-r--r--actionpack/test/template/streaming_render_test.rb2
-rw-r--r--actionpack/test/template/tag_helper_test.rb23
-rw-r--r--actionpack/test/template/template_error_test.rb4
-rw-r--r--actionpack/test/template/template_test.rb111
-rw-r--r--actionpack/test/template/test_case_test.rb8
-rw-r--r--actionpack/test/template/test_test.rb2
-rw-r--r--actionpack/test/template/testing/fixture_resolver_test.rb4
-rw-r--r--actionpack/test/template/testing/null_resolver_test.rb4
-rw-r--r--actionpack/test/template/text_helper_test.rb173
-rw-r--r--actionpack/test/template/translation_helper_test.rb43
-rw-r--r--actionpack/test/template/url_helper_test.rb98
-rw-r--r--actionpack/test/ts_isolated.rb8
-rw-r--r--activemodel/CHANGELOG107
-rw-r--r--activemodel/CHANGELOG.md183
-rw-r--r--activemodel/MIT-LICENSE2
-rw-r--r--activemodel/README.rdoc57
-rw-r--r--[-rwxr-xr-x]activemodel/Rakefile1
-rw-r--r--activemodel/activemodel.gemspec7
-rw-r--r--activemodel/lib/active_model.rb11
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb158
-rw-r--r--activemodel/lib/active_model/callbacks.rb8
-rw-r--r--activemodel/lib/active_model/configuration.rb134
-rw-r--r--activemodel/lib/active_model/conversion.rb10
-rw-r--r--activemodel/lib/active_model/dirty.rb10
-rw-r--r--activemodel/lib/active_model/errors.rb91
-rw-r--r--activemodel/lib/active_model/lint.rb54
-rw-r--r--activemodel/lib/active_model/locale/en.yml3
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb68
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb2
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/sanitizer.rb57
-rw-r--r--activemodel/lib/active_model/model.rb76
-rw-r--r--activemodel/lib/active_model/naming.rb71
-rw-r--r--activemodel/lib/active_model/observing.rb74
-rw-r--r--activemodel/lib/active_model/secure_password.rb28
-rw-r--r--activemodel/lib/active_model/serialization.rb50
-rw-r--r--activemodel/lib/active_model/serializers/json.rb3
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb9
-rw-r--r--activemodel/lib/active_model/test_case.rb12
-rw-r--r--activemodel/lib/active_model/translation.rb23
-rw-r--r--activemodel/lib/active_model/validations.rb33
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb4
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb10
-rw-r--r--activemodel/lib/active_model/validations/clusivity.rb31
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb9
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb64
-rw-r--r--activemodel/lib/active_model/validations/format.rb52
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb52
-rw-r--r--activemodel/lib/active_model/validations/length.rb70
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb53
-rw-r--r--activemodel/lib/active_model/validations/presence.rb30
-rw-r--r--activemodel/lib/active_model/validations/validates.rb23
-rw-r--r--activemodel/lib/active_model/validations/with.rb18
-rw-r--r--activemodel/lib/active_model/validator.rb5
-rw-r--r--activemodel/lib/active_model/version.rb4
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb55
-rw-r--r--activemodel/test/cases/configuration_test.rb154
-rw-r--r--activemodel/test/cases/conversion_test.rb2
-rw-r--r--activemodel/test/cases/dirty_test.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb72
-rw-r--r--activemodel/test/cases/helper.rb5
-rw-r--r--activemodel/test/cases/lint_test.rb6
-rw-r--r--activemodel/test/cases/mass_assignment_security/sanitizer_test.rb12
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb9
-rw-r--r--activemodel/test/cases/model_test.rb26
-rw-r--r--activemodel/test/cases/naming_test.rb101
-rw-r--r--activemodel/test/cases/observing_test.rb69
-rw-r--r--activemodel/test/cases/secure_password_test.rb36
-rw-r--r--activemodel/test/cases/serialization_test.rb111
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb8
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb19
-rw-r--r--activemodel/test/cases/translation_test.rb20
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb7
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb21
-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/i18n_generate_message_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb17
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb12
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb102
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb7
-rw-r--r--activemodel/test/cases/validations/validates_test.rb8
-rw-r--r--activemodel/test/cases/validations_test.rb37
-rw-r--r--activemodel/test/models/administrator.rb3
-rw-r--r--activemodel/test/models/blog_post.rb8
-rw-r--r--activemodel/test/models/user.rb3
-rw-r--r--activemodel/test/models/visitor.rb7
-rw-r--r--activerecord/CHANGELOG6569
-rw-r--r--activerecord/CHANGELOG.md7163
-rw-r--r--activerecord/MIT-LICENSE2
-rw-r--r--activerecord/README.rdoc10
-rw-r--r--activerecord/RUNNING_UNIT_TESTS9
-rw-r--r--[-rwxr-xr-x]activerecord/Rakefile19
-rw-r--r--activerecord/activerecord.gemspec9
-rw-r--r--activerecord/examples/performance.rb23
-rw-r--r--activerecord/lib/active_record.rb41
-rw-r--r--activerecord/lib/active_record/aggregations.rb23
-rw-r--r--activerecord/lib/active_record/associations.rb69
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb9
-rw-r--r--activerecord/lib/active_record/associations/association.rb29
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb52
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb3
-rw-r--r--activerecord/lib/active_record/associations/builder/association.rb43
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb12
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb21
-rw-r--r--activerecord/lib/active_record/associations/builder/has_one.rb21
-rw-r--r--activerecord/lib/active_record/associations/builder/singular_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb101
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb325
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb17
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb12
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb80
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb10
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb19
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb14
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb221
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb221
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb19
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb94
-rw-r--r--activerecord/lib/active_record/attribute_methods/query.rb7
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb135
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb115
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb95
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb32
-rw-r--r--activerecord/lib/active_record/autosave_association.rb74
-rw-r--r--activerecord/lib/active_record/base.rb1870
-rw-r--r--activerecord/lib/active_record/callbacks.rb53
-rw-r--r--activerecord/lib/active_record/coders/yaml_column.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb405
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb161
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb60
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb26
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb117
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb176
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb150
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb134
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb53
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb83
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb116
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb104
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb252
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb790
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb82
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb580
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb544
-rw-r--r--activerecord/lib/active_record/connection_handling.rb96
-rw-r--r--activerecord/lib/active_record/core.rb393
-rw-r--r--activerecord/lib/active_record/counter_cache.rb15
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb56
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb130
-rw-r--r--activerecord/lib/active_record/dynamic_scope_match.rb23
-rw-r--r--activerecord/lib/active_record/explain.rb82
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb24
-rw-r--r--activerecord/lib/active_record/fixtures.rb230
-rw-r--r--activerecord/lib/active_record/fixtures/file.rb5
-rw-r--r--activerecord/lib/active_record/identity_map.rb157
-rw-r--r--activerecord/lib/active_record/inheritance.rb181
-rw-r--r--activerecord/lib/active_record/integration.rb49
-rw-r--r--activerecord/lib/active_record/locale/en.yml3
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb87
-rw-r--r--activerecord/lib/active_record/locking/pessimistic.rb22
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb6
-rw-r--r--activerecord/lib/active_record/migration.rb274
-rw-r--r--activerecord/lib/active_record/migration/command_recorder.rb14
-rw-r--r--activerecord/lib/active_record/migration/join_table.rb17
-rw-r--r--activerecord/lib/active_record/model.rb108
-rw-r--r--activerecord/lib/active_record/model_schema.rb342
-rw-r--r--activerecord/lib/active_record/named_scope.rb200
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb41
-rw-r--r--activerecord/lib/active_record/null_relation.rb63
-rw-r--r--activerecord/lib/active_record/persistence.rb131
-rw-r--r--activerecord/lib/active_record/query_cache.rb45
-rw-r--r--activerecord/lib/active_record/querying.rb69
-rw-r--r--activerecord/lib/active_record/railtie.rb60
-rw-r--r--activerecord/lib/active_record/railties/databases.rake235
-rw-r--r--activerecord/lib/active_record/railties/jdbcmysql_error.rb4
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb26
-rw-r--r--activerecord/lib/active_record/reflection.rb41
-rw-r--r--activerecord/lib/active_record/relation.rb232
-rw-r--r--activerecord/lib/active_record/relation/batches.rb9
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb191
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb49
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb275
-rw-r--r--activerecord/lib/active_record/relation/merger.rb122
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb82
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb394
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb138
-rw-r--r--activerecord/lib/active_record/result.rb35
-rw-r--r--activerecord/lib/active_record/sanitization.rb194
-rw-r--r--activerecord/lib/active_record/schema.rb17
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb19
-rw-r--r--activerecord/lib/active_record/schema_migration.rb34
-rw-r--r--activerecord/lib/active_record/scoping.rb31
-rw-r--r--activerecord/lib/active_record/scoping/default.rb146
-rw-r--r--activerecord/lib/active_record/scoping/named.rb200
-rw-r--r--activerecord/lib/active_record/serialization.rb4
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb6
-rw-r--r--activerecord/lib/active_record/session_store.rb31
-rw-r--r--activerecord/lib/active_record/store.rb31
-rw-r--r--activerecord/lib/active_record/test_case.rb81
-rw-r--r--activerecord/lib/active_record/timestamp.rb2
-rw-r--r--activerecord/lib/active_record/transactions.rb34
-rw-r--r--activerecord/lib/active_record/translation.rb22
-rw-r--r--activerecord/lib/active_record/validations.rb2
-rw-r--r--activerecord/lib/active_record/validations/associated.rb5
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb47
-rw-r--r--activerecord/lib/active_record/version.rb4
-rw-r--r--activerecord/lib/rails/generators/active_record.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/migration.rb15
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb12
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb11
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/migration.rb8
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/model.rb5
-rw-r--r--activerecord/test/active_record/connection_adapters/fake_adapter.rb9
-rw-r--r--activerecord/test/assets/test.txt1
-rw-r--r--activerecord/test/cases/adapter_test.rb255
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb29
-rw-r--r--activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb56
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/schema_test.rb10
-rw-r--r--activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb4
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb22
-rw-r--r--activerecord/test/cases/adapters/mysql2/explain_test.rb26
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql2/schema_test.rb21
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/connection_test.rb15
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb19
-rw-r--r--activerecord/test/cases/adapters/postgresql/explain_test.rb35
-rw-r--r--activerecord/test/cases/adapters/postgresql/hstore_test.rb157
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb42
-rw-r--r--activerecord/test/cases/adapters/postgresql/quoting_test.rb12
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb95
-rw-r--r--activerecord/test/cases/adapters/postgresql/statement_pool_test.rb16
-rw-r--r--activerecord/test/cases/adapters/postgresql/utils_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/view_test.rb2
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb8
-rw-r--r--activerecord/test/cases/adapters/sqlite3/explain_test.rb26
-rw-r--r--activerecord/test/cases/adapters/sqlite3/quoting_test.rb15
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb22
-rw-r--r--activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb2
-rw-r--r--activerecord/test/cases/ar_schema_test.rb7
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb42
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb32
-rw-r--r--activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb7
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb6
-rw-r--r--activerecord/test/cases/associations/eager_singularization_test.rb14
-rw-r--r--activerecord/test/cases/associations/eager_test.rb444
-rw-r--r--activerecord/test/cases/associations/extension_test.rb16
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb123
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb433
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb83
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb95
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb26
-rw-r--r--activerecord/test/cases/associations/identity_map_test.rb137
-rw-r--r--activerecord/test/cases/associations/inner_join_association_test.rb8
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb54
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb101
-rw-r--r--activerecord/test/cases/associations/nested_through_associations_test.rb13
-rw-r--r--activerecord/test/cases/associations_test.rb31
-rw-r--r--activerecord/test/cases/attribute_methods/read_test.rb20
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb159
-rw-r--r--activerecord/test/cases/autosave_association_test.rb77
-rw-r--r--activerecord/test/cases/base_test.rb594
-rw-r--r--activerecord/test/cases/batches_test.rb16
-rw-r--r--activerecord/test/cases/binary_test.rb8
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb17
-rw-r--r--activerecord/test/cases/calculations_test.rb204
-rw-r--r--activerecord/test/cases/callbacks_test.rb14
-rw-r--r--activerecord/test/cases/coders/yaml_column_test.rb7
-rw-r--r--activerecord/test/cases/column_definition_test.rb9
-rw-r--r--activerecord/test/cases/column_test.rb81
-rw-r--r--activerecord/test/cases/connection_adapters/abstract_adapter_test.rb54
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb5
-rw-r--r--activerecord/test/cases/connection_adapters/connection_specification_test.rb12
-rw-r--r--activerecord/test/cases/connection_adapters/quoting_test.rb13
-rw-r--r--activerecord/test/cases/connection_adapters/schema_cache_test.rb59
-rw-r--r--activerecord/test/cases/connection_management_test.rb53
-rw-r--r--activerecord/test/cases/connection_pool_test.rb202
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb41
-rw-r--r--activerecord/test/cases/counter_cache_test.rb20
-rw-r--r--activerecord/test/cases/custom_locking_test.rb2
-rw-r--r--activerecord/test/cases/deprecated_dynamic_methods_test.rb607
-rw-r--r--activerecord/test/cases/deprecated_finder_test.rb30
-rw-r--r--activerecord/test/cases/dirty_test.rb18
-rw-r--r--activerecord/test/cases/dup_test.rb8
-rw-r--r--activerecord/test/cases/dynamic_finder_match_test.rb98
-rw-r--r--activerecord/test/cases/explain_subscriber_test.rb48
-rw-r--r--activerecord/test/cases/explain_test.rb117
-rw-r--r--activerecord/test/cases/finder_respond_to_test.rb10
-rw-r--r--activerecord/test/cases/finder_test.rb641
-rw-r--r--activerecord/test/cases/fixtures_test.rb115
-rw-r--r--activerecord/test/cases/helper.rb46
-rw-r--r--activerecord/test/cases/identity_map/middleware_test.rb71
-rw-r--r--activerecord/test/cases/identity_map_test.rb439
-rw-r--r--activerecord/test/cases/inclusion_test.rb113
-rw-r--r--activerecord/test/cases/inheritance_test.rb37
-rw-r--r--activerecord/test/cases/invalid_date_test.rb2
-rw-r--r--activerecord/test/cases/invertible_migration_test.rb12
-rw-r--r--activerecord/test/cases/lifecycle_test.rb2
-rw-r--r--activerecord/test/cases/locking_test.rb84
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb15
-rw-r--r--activerecord/test/cases/mass_assignment_security_test.rb84
-rw-r--r--activerecord/test/cases/method_scoping_test.rb558
-rw-r--r--activerecord/test/cases/migration/change_schema_test.rb324
-rw-r--r--activerecord/test/cases/migration/change_table_test.rb221
-rw-r--r--activerecord/test/cases/migration/column_attributes_test.rb198
-rw-r--r--activerecord/test/cases/migration/column_positioning_test.rb60
-rw-r--r--activerecord/test/cases/migration/command_recorder_test.rb18
-rw-r--r--activerecord/test/cases/migration/create_join_table_test.rb70
-rw-r--r--activerecord/test/cases/migration/helper.rb64
-rw-r--r--activerecord/test/cases/migration/index_test.rb185
-rw-r--r--activerecord/test/cases/migration/logger_test.rb37
-rw-r--r--activerecord/test/cases/migration/references_index_test.rb99
-rw-r--r--activerecord/test/cases/migration/rename_column_test.rb194
-rw-r--r--activerecord/test/cases/migration/rename_table_test.rb72
-rw-r--r--activerecord/test/cases/migration/table_and_index_test.rb24
-rw-r--r--activerecord/test/cases/migration_test.rb2583
-rw-r--r--activerecord/test/cases/migrator_test.rb377
-rw-r--r--activerecord/test/cases/modules_test.rb16
-rw-r--r--activerecord/test/cases/multiple_db_test.rb21
-rw-r--r--activerecord/test/cases/named_scope_test.rb165
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb17
-rw-r--r--activerecord/test/cases/persistence_test.rb31
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb98
-rw-r--r--activerecord/test/cases/primary_keys_test.rb64
-rw-r--r--activerecord/test/cases/query_cache_test.rb39
-rw-r--r--activerecord/test/cases/readonly_test.rb13
-rw-r--r--activerecord/test/cases/reaper_test.rb81
-rw-r--r--activerecord/test/cases/reflection_test.rb26
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb57
-rw-r--r--activerecord/test/cases/relation_test.rb169
-rw-r--r--activerecord/test/cases/relations_test.rb281
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb62
-rw-r--r--activerecord/test/cases/serialization_test.rb9
-rw-r--r--activerecord/test/cases/session_store/session_test.rb10
-rw-r--r--activerecord/test/cases/session_store/sql_bypass_test.rb (renamed from activerecord/test/cases/session_store/sql_bypass.rb)5
-rw-r--r--activerecord/test/cases/store_test.rb54
-rw-r--r--activerecord/test/cases/test_case.rb10
-rw-r--r--activerecord/test/cases/timestamp_test.rb10
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb72
-rw-r--r--activerecord/test/cases/transactions_test.rb3
-rw-r--r--activerecord/test/cases/unconnected_test.rb6
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb43
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb19
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb80
-rw-r--r--activerecord/test/cases/validations_test.rb26
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb42
-rw-r--r--activerecord/test/fixtures/admin/randomly_named_a9.yml7
-rw-r--r--activerecord/test/fixtures/admin/randomly_named_b0.yml7
-rw-r--r--activerecord/test/fixtures/colleges.yml3
-rw-r--r--activerecord/test/fixtures/courses.yml1
-rw-r--r--activerecord/test/fixtures/developers.yml2
-rw-r--r--activerecord/test/fixtures/dog_lovers.yml4
-rw-r--r--activerecord/test/fixtures/dogs.yml3
-rw-r--r--activerecord/test/fixtures/other_topics.yml42
-rw-r--r--activerecord/test/fixtures/peoples_treasures.yml3
-rw-r--r--activerecord/test/fixtures/randomly_named_a9.yml7
-rw-r--r--activerecord/test/fixtures/teapots.yml3
-rw-r--r--activerecord/test/migrations/broken/100_migration_that_raises_exception.rb10
-rw-r--r--activerecord/test/migrations/duplicate/3_foo.rb7
-rw-r--r--activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb7
-rw-r--r--activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb7
-rw-r--r--activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb12
-rw-r--r--activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb9
-rw-r--r--activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb12
-rw-r--r--activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb9
-rw-r--r--activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb8
-rw-r--r--activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb12
-rw-r--r--activerecord/test/migrations/rename/1_we_need_things.rb11
-rw-r--r--activerecord/test/migrations/rename/2_rename_things.rb9
-rw-r--r--activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb9
-rw-r--r--activerecord/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb (renamed from activerecord/test/migrations/duplicate/1_people_have_last_names.rb)4
-rw-r--r--activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb (renamed from activerecord/test/migrations/duplicate/2_we_need_reminders.rb)0
-rw-r--r--activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb (renamed from activerecord/test/migrations/duplicate/3_innocent_jointable.rb)0
-rw-r--r--activerecord/test/models/admin/randomly_named_c1.rb3
-rw-r--r--activerecord/test/models/admin/user.rb3
-rw-r--r--activerecord/test/models/arunit2_model.rb3
-rw-r--r--activerecord/test/models/author.rb9
-rw-r--r--activerecord/test/models/bird.rb5
-rw-r--r--activerecord/test/models/bulb.rb2
-rw-r--r--activerecord/test/models/car.rb13
-rw-r--r--activerecord/test/models/categorization.rb2
-rw-r--r--activerecord/test/models/category.rb2
-rw-r--r--activerecord/test/models/college.rb5
-rw-r--r--activerecord/test/models/comment.rb18
-rw-r--r--activerecord/test/models/company.rb11
-rw-r--r--activerecord/test/models/country.rb2
-rw-r--r--activerecord/test/models/course.rb5
-rw-r--r--activerecord/test/models/dashboard.rb4
-rw-r--r--activerecord/test/models/developer.rb52
-rw-r--r--activerecord/test/models/dog.rb4
-rw-r--r--activerecord/test/models/dog_lover.rb4
-rw-r--r--activerecord/test/models/joke.rb4
-rw-r--r--activerecord/test/models/keyboard.rb2
-rw-r--r--activerecord/test/models/legacy_thing.rb2
-rw-r--r--activerecord/test/models/liquid.rb2
-rw-r--r--activerecord/test/models/minivan.rb2
-rw-r--r--activerecord/test/models/mixed_case_monkey.rb2
-rw-r--r--activerecord/test/models/organization.rb2
-rw-r--r--activerecord/test/models/owner.rb2
-rw-r--r--activerecord/test/models/parrot.rb3
-rw-r--r--activerecord/test/models/person.rb19
-rw-r--r--activerecord/test/models/pet.rb2
-rw-r--r--activerecord/test/models/post.rb43
-rw-r--r--activerecord/test/models/project.rb2
-rw-r--r--activerecord/test/models/randomly_named_c1.rb3
-rw-r--r--activerecord/test/models/reader.rb9
-rw-r--r--activerecord/test/models/reference.rb2
-rw-r--r--activerecord/test/models/reply.rb2
-rw-r--r--activerecord/test/models/speedometer.rb4
-rw-r--r--activerecord/test/models/string_key_object.rb2
-rw-r--r--activerecord/test/models/subscriber.rb2
-rw-r--r--activerecord/test/models/teapot.rb35
-rw-r--r--activerecord/test/models/topic.rb40
-rw-r--r--activerecord/test/models/toy.rb4
-rw-r--r--activerecord/test/models/treaty.rb2
-rw-r--r--activerecord/test/models/warehouse_thing.rb4
-rw-r--r--activerecord/test/models/without_table.rb2
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb4
-rw-r--r--activerecord/test/schema/mysql_specific_schema.rb2
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb48
-rw-r--r--activerecord/test/schema/schema.rb49
-rw-r--r--activerecord/test/schema/sqlite_specific_schema.rb2
-rw-r--r--activerecord/test/support/connection.rb13
-rw-r--r--activeresource/CHANGELOG337
-rw-r--r--activeresource/MIT-LICENSE20
-rw-r--r--activeresource/README.rdoc187
-rwxr-xr-xactiveresource/Rakefile65
-rw-r--r--activeresource/activeresource.gemspec24
-rw-r--r--activeresource/examples/performance.rb70
-rw-r--r--activeresource/lib/active_resource.rb45
-rw-r--r--activeresource/lib/active_resource/base.rb1483
-rw-r--r--activeresource/lib/active_resource/connection.rb287
-rw-r--r--activeresource/lib/active_resource/custom_methods.rb121
-rw-r--r--activeresource/lib/active_resource/exceptions.rb72
-rw-r--r--activeresource/lib/active_resource/formats.rb22
-rw-r--r--activeresource/lib/active_resource/formats/json_format.rb25
-rw-r--r--activeresource/lib/active_resource/formats/xml_format.rb25
-rw-r--r--activeresource/lib/active_resource/http_mock.rb335
-rw-r--r--activeresource/lib/active_resource/log_subscriber.rb15
-rw-r--r--activeresource/lib/active_resource/observing.rb29
-rw-r--r--activeresource/lib/active_resource/railtie.rb14
-rw-r--r--activeresource/lib/active_resource/schema.rb59
-rw-r--r--activeresource/lib/active_resource/validations.rb134
-rw-r--r--activeresource/lib/active_resource/version.rb10
-rw-r--r--activeresource/test/abstract_unit.rb144
-rw-r--r--activeresource/test/cases/authorization_test.rb254
-rw-r--r--activeresource/test/cases/base/custom_methods_test.rb101
-rw-r--r--activeresource/test/cases/base/equality_test.rb52
-rw-r--r--activeresource/test/cases/base/load_test.rb199
-rw-r--r--activeresource/test/cases/base/schema_test.rb411
-rw-r--r--activeresource/test/cases/base_errors_test.rb107
-rw-r--r--activeresource/test/cases/base_test.rb1144
-rw-r--r--activeresource/test/cases/connection_test.rb276
-rw-r--r--activeresource/test/cases/finder_test.rb139
-rw-r--r--activeresource/test/cases/format_test.rb114
-rw-r--r--activeresource/test/cases/http_mock_test.rb202
-rw-r--r--activeresource/test/cases/log_subscriber_test.rb32
-rw-r--r--activeresource/test/cases/observing_test.rb55
-rw-r--r--activeresource/test/cases/validations_test.rb67
-rw-r--r--activeresource/test/fixtures/address.rb19
-rw-r--r--activeresource/test/fixtures/beast.rb14
-rw-r--r--activeresource/test/fixtures/customer.rb3
-rw-r--r--activeresource/test/fixtures/person.rb3
-rw-r--r--activeresource/test/fixtures/project.rb18
-rw-r--r--activeresource/test/fixtures/proxy.rb4
-rw-r--r--activeresource/test/fixtures/sound.rb9
-rw-r--r--activeresource/test/fixtures/street_address.rb4
-rw-r--r--activeresource/test/fixtures/subscription_plan.rb5
-rw-r--r--activeresource/test/setter_trap.rb26
-rw-r--r--activesupport/CHANGELOG1558
-rw-r--r--activesupport/CHANGELOG.md1736
-rw-r--r--activesupport/MIT-LICENSE2
-rw-r--r--activesupport/README.rdoc4
-rw-r--r--[-rwxr-xr-x]activesupport/Rakefile0
-rw-r--r--activesupport/activesupport.gemspec9
-rwxr-xr-x[-rw-r--r--]activesupport/bin/generate_tables0
-rw-r--r--activesupport/lib/active_support.rb24
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb20
-rw-r--r--activesupport/lib/active_support/base64.rb42
-rw-r--r--activesupport/lib/active_support/basic_object.rb24
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb2
-rw-r--r--activesupport/lib/active_support/buffered_logger.rb138
-rw-r--r--activesupport/lib/active_support/cache.rb163
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb19
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb7
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb1
-rw-r--r--activesupport/lib/active_support/cache/null_store.rb44
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb4
-rw-r--r--activesupport/lib/active_support/callbacks.rb306
-rw-r--r--activesupport/lib/active_support/concern.rb10
-rw-r--r--activesupport/lib/active_support/configurable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/array.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb100
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/array/random_access.rb30
-rw-r--r--activesupport/lib/active_support/core_ext/array/uniq_by.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/array/wrap.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb138
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb34
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb146
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb44
-rw-r--r--activesupport/lib/active_support/core_ext/date/freeze.rb33
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb69
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb82
-rw-r--r--activesupport/lib/active_support/core_ext/exception.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/file.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/file/path.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/float.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/float/rounding.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/hash.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb36
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_dup.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/hash/deep_merge.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/hash/diff.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb37
-rw-r--r--activesupport/lib/active_support/core_ext/hash/reverse_merge.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/hash/slice.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/integer/inflections.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/integer/multiple.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/integer/time.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/io.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/debugger.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/singleton_class.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/logger.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/module/anonymous.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/module/attr_internal.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb68
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb42
-rw-r--r--activesupport/lib/active_support/core_ext/module/method_names.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/module/qualified_const.rb52
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/module/synchronization.rb43
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb21
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb44
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb31
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/object/instance_variables.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_json.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/proc.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/process.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/process/daemon.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/range.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range/blockless_step.rb29
-rw-r--r--activesupport/lib/active_support/core_ext/range/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range/cover.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/range/include_range.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/range/overlaps.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/rexml.rb46
-rw-r--r--activesupport/lib/active_support/core_ext/string.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb185
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb74
-rw-r--r--activesupport/lib/active_support/core_ext/string/encoding.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/string/exclude.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb31
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb113
-rw-r--r--activesupport/lib/active_support/core_ext/string/inquiry.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/interpolation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb110
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb87
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb180
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb52
-rw-r--r--activesupport/lib/active_support/core_ext/time/marshal.rb27
-rw-r--r--activesupport/lib/active_support/core_ext/time/publicize_conversion_methods.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb29
-rw-r--r--activesupport/lib/active_support/core_ext/uri.rb30
-rw-r--r--activesupport/lib/active_support/dependencies.rb118
-rw-r--r--activesupport/lib/active_support/dependencies/autoload.rb11
-rw-r--r--activesupport/lib/active_support/deprecation.rb1
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb32
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb5
-rw-r--r--activesupport/lib/active_support/duration.rb2
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb132
-rw-r--r--activesupport/lib/active_support/gzip.rb3
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb17
-rw-r--r--activesupport/lib/active_support/i18n.rb1
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb13
-rw-r--r--activesupport/lib/active_support/inflections.rb24
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb24
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb211
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb2
-rw-r--r--activesupport/lib/active_support/json/decoding.rb6
-rw-r--r--activesupport/lib/active_support/json/encoding.rb52
-rw-r--r--activesupport/lib/active_support/lazy_load_hooks.rb50
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb12
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb12
-rw-r--r--activesupport/lib/active_support/logger.rb53
-rw-r--r--activesupport/lib/active_support/memoizable.rb116
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb73
-rw-r--r--activesupport/lib/active_support/message_verifier.rb13
-rw-r--r--activesupport/lib/active_support/multibyte.rb24
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb357
-rw-r--r--activesupport/lib/active_support/multibyte/exceptions.rb8
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb84
-rw-r--r--activesupport/lib/active_support/multibyte/utils.rb60
-rw-r--r--activesupport/lib/active_support/notifications.rb139
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb103
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb8
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb200
-rw-r--r--activesupport/lib/active_support/ordered_options.rb11
-rw-r--r--activesupport/lib/active_support/railtie.rb40
-rw-r--r--activesupport/lib/active_support/rescuable.rb7
-rw-r--r--activesupport/lib/active_support/ruby/shim.rb22
-rw-r--r--activesupport/lib/active_support/string_inquirer.rb4
-rw-r--r--activesupport/lib/active_support/tagged_logging.rb58
-rw-r--r--activesupport/lib/active_support/test_case.rb45
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb5
-rw-r--r--activesupport/lib/active_support/testing/deprecation.rb19
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb58
-rw-r--r--activesupport/lib/active_support/testing/pending.rb52
-rw-r--r--activesupport/lib/active_support/testing/performance.rb95
-rw-r--r--activesupport/lib/active_support/testing/performance/jruby.rb2
-rw-r--r--activesupport/lib/active_support/testing/performance/ruby.rb38
-rw-r--r--activesupport/lib/active_support/testing/performance/ruby/mri.rb57
-rw-r--r--activesupport/lib/active_support/testing/performance/ruby/yarv.rb57
-rw-r--r--activesupport/lib/active_support/testing/setup_and_teardown.rb88
-rw-r--r--activesupport/lib/active_support/time.rb6
-rw-r--r--activesupport/lib/active_support/time/autoload.rb5
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb44
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb77
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin813343 -> 904408 bytes
-rw-r--r--activesupport/lib/active_support/version.rb4
-rw-r--r--activesupport/lib/active_support/whiny_nil.rb60
-rw-r--r--activesupport/lib/active_support/xml_mini.rb9
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb9
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb1
-rw-r--r--activesupport/lib/active_support/xml_mini/libxmlsax.rb3
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb1
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogirisax.rb3
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb1
-rw-r--r--activesupport/test/abstract_unit.rb37
-rw-r--r--activesupport/test/benchmarkable_test.rb53
-rw-r--r--activesupport/test/broadcast_logger_test.rb82
-rw-r--r--activesupport/test/buffered_logger_test.rb201
-rw-r--r--activesupport/test/caching_test.rb263
-rw-r--r--activesupport/test/callback_inheritance_test.rb27
-rw-r--r--activesupport/test/callbacks_test.rb138
-rw-r--r--activesupport/test/class_cache_test.rb43
-rw-r--r--activesupport/test/clean_logger_test.rb39
-rw-r--r--activesupport/test/concern_test.rb9
-rw-r--r--activesupport/test/constantize_test_cases.rb42
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb70
-rw-r--r--activesupport/test/core_ext/base64_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb2
-rw-r--r--activesupport/test/core_ext/blank_test.rb2
-rw-r--r--activesupport/test/core_ext/class/attribute_accessor_test.rb16
-rw-r--r--activesupport/test/core_ext/class/delegating_attributes_test.rb2
-rw-r--r--activesupport/test/core_ext/class_test.rb2
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb50
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb63
-rw-r--r--activesupport/test/core_ext/deep_dup_test.rb53
-rw-r--r--activesupport/test/core_ext/duplicable_test.rb20
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb11
-rw-r--r--activesupport/test/core_ext/file_test.rb6
-rw-r--r--activesupport/test/core_ext/float_ext_test.rb26
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb58
-rw-r--r--activesupport/test/core_ext/integer_ext_test.rb15
-rw-r--r--activesupport/test/core_ext/io_test.rb23
-rw-r--r--activesupport/test/core_ext/kernel_test.rb13
-rw-r--r--activesupport/test/core_ext/load_error_test.rb4
-rw-r--r--activesupport/test/core_ext/module/attr_internal_test.rb2
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb16
-rw-r--r--activesupport/test/core_ext/module/attribute_aliasing_test.rb2
-rw-r--r--activesupport/test/core_ext/module/qualified_const_test.rb94
-rw-r--r--activesupport/test/core_ext/module/remove_method_test.rb29
-rw-r--r--activesupport/test/core_ext/module/synchronization_test.rb89
-rw-r--r--activesupport/test/core_ext/module_test.rb29
-rw-r--r--activesupport/test/core_ext/name_error_test.rb2
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb6
-rw-r--r--activesupport/test/core_ext/object/inclusion_test.rb12
-rw-r--r--activesupport/test/core_ext/object/to_param_test.rb2
-rw-r--r--activesupport/test/core_ext/object/to_query_test.rb6
-rw-r--r--activesupport/test/core_ext/object_and_class_ext_test.rb18
-rw-r--r--activesupport/test/core_ext/proc_test.rb14
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb45
-rw-r--r--activesupport/test/core_ext/regexp_ext_test.rb2
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb116
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb156
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb57
-rw-r--r--activesupport/test/core_ext/uri_ext_test.rb10
-rw-r--r--activesupport/test/dependencies_test.rb83
-rw-r--r--activesupport/test/deprecation/proxy_wrappers_test.rb2
-rw-r--r--activesupport/test/deprecation_test.rb40
-rw-r--r--activesupport/test/descendants_tracker_with_autoloading_test.rb5
-rw-r--r--activesupport/test/descendants_tracker_without_autoloading_test.rb5
-rw-r--r--activesupport/test/file_update_checker_test.rb74
-rw-r--r--activesupport/test/flush_cache_on_private_memoization_test.rb45
-rw-r--r--activesupport/test/gzip_test.rb7
-rw-r--r--activesupport/test/i18n_test.rb2
-rw-r--r--activesupport/test/inflector_test.rb165
-rw-r--r--activesupport/test/inflector_test_cases.rb37
-rw-r--r--activesupport/test/isolation_test.rb182
-rw-r--r--activesupport/test/json/encoding_test.rb49
-rw-r--r--activesupport/test/lazy_load_hooks_test.rb29
-rw-r--r--activesupport/test/load_paths_test.rb2
-rw-r--r--activesupport/test/log_subscriber_test.rb6
-rw-r--r--activesupport/test/logger_test.rb123
-rw-r--r--activesupport/test/memoizable_test.rb290
-rw-r--r--activesupport/test/message_encryptor_test.rb61
-rw-r--r--activesupport/test/message_verifier_test.rb6
-rw-r--r--activesupport/test/multibyte_chars_test.rb84
-rw-r--r--activesupport/test/multibyte_conformance.rb2
-rw-r--r--activesupport/test/multibyte_test_helpers.rb5
-rw-r--r--activesupport/test/multibyte_unicode_database_test.rb2
-rw-r--r--activesupport/test/multibyte_utils_test.rb137
-rw-r--r--activesupport/test/notifications/evented_notification_test.rb67
-rw-r--r--activesupport/test/notifications_test.rb20
-rw-r--r--activesupport/test/option_merger_test.rb2
-rw-r--r--activesupport/test/ordered_hash_test.rb36
-rw-r--r--activesupport/test/ordered_options_test.rb11
-rw-r--r--activesupport/test/rescuable_test.rb2
-rw-r--r--activesupport/test/safe_buffer_test.rb56
-rw-r--r--activesupport/test/string_inquirer_test.rb2
-rw-r--r--activesupport/test/tagged_logging_test.rb72
-rw-r--r--activesupport/test/test_case_test.rb64
-rw-r--r--activesupport/test/test_test.rb21
-rw-r--r--activesupport/test/time_zone_test.rb37
-rw-r--r--activesupport/test/transliterate_test.rb2
-rw-r--r--activesupport/test/ts_isolated.rb7
-rw-r--r--activesupport/test/whiny_nil_test.rb54
-rw-r--r--activesupport/test/xml_mini/jdom_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini/libxml_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini/libxmlsax_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini/nokogiri_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini/nokogirisax_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini/rexml_engine_test.rb2
-rw-r--r--activesupport/test/xml_mini_test.rb2
-rwxr-xr-xci/travis.rb12
-rw-r--r--guides/Rakefile11
-rw-r--r--guides/assets/images/belongs_to.png (renamed from railties/guides/assets/images/belongs_to.png)bin34017 -> 34017 bytes
-rw-r--r--guides/assets/images/book_icon.gif (renamed from railties/guides/assets/images/book_icon.gif)bin337 -> 337 bytes
-rw-r--r--guides/assets/images/bullet.gif (renamed from railties/guides/assets/images/bullet.gif)bin60 -> 60 bytes
-rw-r--r--guides/assets/images/challenge.png (renamed from railties/guides/assets/images/challenge.png)bin54134 -> 54134 bytes
-rw-r--r--guides/assets/images/chapters_icon.gif (renamed from railties/guides/assets/images/chapters_icon.gif)bin628 -> 628 bytes
-rw-r--r--guides/assets/images/check_bullet.gif (renamed from railties/guides/assets/images/check_bullet.gif)bin384 -> 384 bytes
-rw-r--r--guides/assets/images/credits_pic_blank.gif (renamed from railties/guides/assets/images/credits_pic_blank.gif)bin613 -> 613 bytes
-rw-r--r--guides/assets/images/csrf.png (renamed from railties/guides/assets/images/csrf.png)bin41996 -> 41996 bytes
-rw-r--r--guides/assets/images/customized_error_messages.png (renamed from railties/guides/assets/images/customized_error_messages.png)bin5055 -> 5055 bytes
-rw-r--r--guides/assets/images/edge_badge.png (renamed from railties/guides/assets/images/edge_badge.png)bin7945 -> 7945 bytes
-rw-r--r--guides/assets/images/error_messages.png (renamed from railties/guides/assets/images/error_messages.png)bin14645 -> 14645 bytes
-rw-r--r--guides/assets/images/favicon.icobin0 -> 1150 bytes
-rw-r--r--guides/assets/images/feature_tile.gif (renamed from railties/guides/assets/images/feature_tile.gif)bin43 -> 43 bytes
-rw-r--r--guides/assets/images/footer_tile.gif (renamed from railties/guides/assets/images/footer_tile.gif)bin44 -> 44 bytes
-rw-r--r--guides/assets/images/fxn.png (renamed from railties/guides/assets/images/fxn.png)bin20664 -> 20664 bytes
-rw-r--r--guides/assets/images/getting_started/confirm_dialog.pngbin0 -> 36070 bytes
-rw-r--r--guides/assets/images/getting_started/form_with_errors.pngbin0 -> 20820 bytes
-rw-r--r--guides/assets/images/getting_started/index_action_with_edit_link.pngbin0 -> 15547 bytes
-rw-r--r--guides/assets/images/getting_started/new_post.pngbin0 -> 14334 bytes
-rw-r--r--guides/assets/images/getting_started/post_with_comments.pngbin0 -> 31630 bytes
-rw-r--r--guides/assets/images/getting_started/routing_error_no_controller.pngbin0 -> 15744 bytes
-rw-r--r--guides/assets/images/getting_started/routing_error_no_route_matches.pngbin0 -> 16065 bytes
-rw-r--r--guides/assets/images/getting_started/show_action_for_posts.pngbin0 -> 6885 bytes
-rw-r--r--guides/assets/images/getting_started/template_is_missing_posts_new.pngbin0 -> 15168 bytes
-rw-r--r--guides/assets/images/getting_started/undefined_method_post_path.pngbin0 -> 15254 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_create_for_posts.pngbin0 -> 12652 bytes
-rw-r--r--guides/assets/images/getting_started/unknown_action_new_for_posts.pngbin0 -> 12756 bytes
-rw-r--r--guides/assets/images/grey_bullet.gif (renamed from railties/guides/assets/images/grey_bullet.gif)bin45 -> 45 bytes
-rw-r--r--guides/assets/images/habtm.png (renamed from railties/guides/assets/images/habtm.png)bin63801 -> 63801 bytes
-rw-r--r--guides/assets/images/has_many.png (renamed from railties/guides/assets/images/has_many.png)bin38582 -> 38582 bytes
-rw-r--r--guides/assets/images/has_many_through.png (renamed from railties/guides/assets/images/has_many_through.png)bin100220 -> 100220 bytes
-rw-r--r--guides/assets/images/has_one.png (renamed from railties/guides/assets/images/has_one.png)bin39022 -> 39022 bytes
-rw-r--r--guides/assets/images/has_one_through.png (renamed from railties/guides/assets/images/has_one_through.png)bin92594 -> 92594 bytes
-rw-r--r--guides/assets/images/header_backdrop.png (renamed from railties/guides/assets/images/header_backdrop.png)bin882 -> 882 bytes
-rw-r--r--guides/assets/images/header_tile.gif (renamed from railties/guides/assets/images/header_tile.gif)bin44 -> 44 bytes
-rw-r--r--guides/assets/images/i18n/demo_html_safe.png (renamed from railties/guides/assets/images/i18n/demo_html_safe.png)bin11946 -> 11946 bytes
-rw-r--r--guides/assets/images/i18n/demo_localized_pirate.png (renamed from railties/guides/assets/images/i18n/demo_localized_pirate.png)bin15027 -> 15027 bytes
-rw-r--r--guides/assets/images/i18n/demo_translated_en.png (renamed from railties/guides/assets/images/i18n/demo_translated_en.png)bin12057 -> 12057 bytes
-rw-r--r--guides/assets/images/i18n/demo_translated_pirate.png (renamed from railties/guides/assets/images/i18n/demo_translated_pirate.png)bin13392 -> 13392 bytes
-rw-r--r--guides/assets/images/i18n/demo_translation_missing.png (renamed from railties/guides/assets/images/i18n/demo_translation_missing.png)bin13143 -> 13143 bytes
-rw-r--r--guides/assets/images/i18n/demo_untranslated.png (renamed from railties/guides/assets/images/i18n/demo_untranslated.png)bin11925 -> 11925 bytes
-rw-r--r--guides/assets/images/icons/README (renamed from railties/guides/assets/images/icons/README)0
-rw-r--r--guides/assets/images/icons/callouts/1.png (renamed from railties/guides/assets/images/icons/callouts/1.png)bin329 -> 329 bytes
-rw-r--r--guides/assets/images/icons/callouts/10.png (renamed from railties/guides/assets/images/icons/callouts/10.png)bin361 -> 361 bytes
-rw-r--r--guides/assets/images/icons/callouts/11.png (renamed from railties/guides/assets/images/icons/callouts/11.png)bin565 -> 565 bytes
-rw-r--r--guides/assets/images/icons/callouts/12.png (renamed from railties/guides/assets/images/icons/callouts/12.png)bin617 -> 617 bytes
-rw-r--r--guides/assets/images/icons/callouts/13.png (renamed from railties/guides/assets/images/icons/callouts/13.png)bin623 -> 623 bytes
-rw-r--r--guides/assets/images/icons/callouts/14.png (renamed from railties/guides/assets/images/icons/callouts/14.png)bin411 -> 411 bytes
-rw-r--r--guides/assets/images/icons/callouts/15.png (renamed from railties/guides/assets/images/icons/callouts/15.png)bin640 -> 640 bytes
-rw-r--r--guides/assets/images/icons/callouts/2.png (renamed from railties/guides/assets/images/icons/callouts/2.png)bin353 -> 353 bytes
-rw-r--r--guides/assets/images/icons/callouts/3.png (renamed from railties/guides/assets/images/icons/callouts/3.png)bin350 -> 350 bytes
-rw-r--r--guides/assets/images/icons/callouts/4.png (renamed from railties/guides/assets/images/icons/callouts/4.png)bin345 -> 345 bytes
-rw-r--r--guides/assets/images/icons/callouts/5.png (renamed from railties/guides/assets/images/icons/callouts/5.png)bin348 -> 348 bytes
-rw-r--r--guides/assets/images/icons/callouts/6.png (renamed from railties/guides/assets/images/icons/callouts/6.png)bin355 -> 355 bytes
-rw-r--r--guides/assets/images/icons/callouts/7.png (renamed from railties/guides/assets/images/icons/callouts/7.png)bin344 -> 344 bytes
-rw-r--r--guides/assets/images/icons/callouts/8.png (renamed from railties/guides/assets/images/icons/callouts/8.png)bin357 -> 357 bytes
-rw-r--r--guides/assets/images/icons/callouts/9.png (renamed from railties/guides/assets/images/icons/callouts/9.png)bin357 -> 357 bytes
-rw-r--r--guides/assets/images/icons/caution.png (renamed from railties/guides/assets/images/icons/caution.png)bin2554 -> 2554 bytes
-rw-r--r--guides/assets/images/icons/example.png (renamed from railties/guides/assets/images/icons/example.png)bin2354 -> 2354 bytes
-rw-r--r--guides/assets/images/icons/home.png (renamed from railties/guides/assets/images/icons/home.png)bin1340 -> 1340 bytes
-rw-r--r--guides/assets/images/icons/important.png (renamed from railties/guides/assets/images/icons/important.png)bin2657 -> 2657 bytes
-rw-r--r--guides/assets/images/icons/next.png (renamed from railties/guides/assets/images/icons/next.png)bin1302 -> 1302 bytes
-rw-r--r--guides/assets/images/icons/note.png (renamed from railties/guides/assets/images/icons/note.png)bin2730 -> 2730 bytes
-rw-r--r--guides/assets/images/icons/prev.png (renamed from railties/guides/assets/images/icons/prev.png)bin1348 -> 1348 bytes
-rw-r--r--guides/assets/images/icons/tip.png (renamed from railties/guides/assets/images/icons/tip.png)bin2602 -> 2602 bytes
-rw-r--r--guides/assets/images/icons/up.png (renamed from railties/guides/assets/images/icons/up.png)bin1320 -> 1320 bytes
-rw-r--r--guides/assets/images/icons/warning.png (renamed from railties/guides/assets/images/icons/warning.png)bin2828 -> 2828 bytes
-rw-r--r--guides/assets/images/jaimeiniesta.jpg (renamed from railties/guides/assets/images/jaimeiniesta.jpg)bin11913 -> 11913 bytes
-rw-r--r--guides/assets/images/nav_arrow.gif (renamed from railties/guides/assets/images/nav_arrow.gif)bin427 -> 427 bytes
-rw-r--r--guides/assets/images/polymorphic.png (renamed from railties/guides/assets/images/polymorphic.png)bin85248 -> 85248 bytes
-rw-r--r--guides/assets/images/radar.png (renamed from railties/guides/assets/images/radar.png)bin19521 -> 19521 bytes
-rw-r--r--guides/assets/images/rails_guides_kindle_cover.jpgbin0 -> 31785 bytes
-rw-r--r--guides/assets/images/rails_guides_logo.gif (renamed from railties/guides/assets/images/rails_guides_logo.gif)bin5114 -> 5114 bytes
-rw-r--r--guides/assets/images/rails_logo_remix.gif (renamed from railties/guides/assets/images/rails_logo_remix.gif)bin8533 -> 8533 bytes
-rw-r--r--guides/assets/images/rails_welcome.png (renamed from railties/guides/assets/images/rails_welcome.png)bin121314 -> 121314 bytes
-rw-r--r--guides/assets/images/session_fixation.png (renamed from railties/guides/assets/images/session_fixation.png)bin47860 -> 47860 bytes
-rw-r--r--guides/assets/images/tab_grey.gif (renamed from railties/guides/assets/images/tab_grey.gif)bin4924 -> 4924 bytes
-rw-r--r--guides/assets/images/tab_info.gif (renamed from railties/guides/assets/images/tab_info.gif)bin4762 -> 4762 bytes
-rw-r--r--guides/assets/images/tab_note.gif (renamed from railties/guides/assets/images/tab_note.gif)bin4807 -> 4807 bytes
-rw-r--r--guides/assets/images/tab_red.gif (renamed from railties/guides/assets/images/tab_red.gif)bin4753 -> 4753 bytes
-rw-r--r--guides/assets/images/tab_yellow.gif (renamed from railties/guides/assets/images/tab_yellow.gif)bin4759 -> 4759 bytes
-rw-r--r--guides/assets/images/tab_yellow.png (renamed from railties/guides/assets/images/tab_yellow.png)bin1611 -> 1611 bytes
-rw-r--r--guides/assets/images/validation_error_messages.png (renamed from railties/guides/assets/images/validation_error_messages.png)bin1107 -> 1107 bytes
-rw-r--r--guides/assets/images/vijaydev.jpg (renamed from railties/guides/assets/images/vijaydev.jpg)bin4610 -> 4610 bytes
-rw-r--r--guides/assets/javascripts/guides.js (renamed from railties/guides/assets/javascripts/guides.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushAS3.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushAS3.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushBash.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushBash.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushCSharp.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushCSharp.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushCpp.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushCpp.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushCss.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushCss.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushDelphi.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushDelphi.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushDiff.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushDiff.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushErlang.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushErlang.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushGroovy.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushGroovy.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushJScript.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushJScript.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushJava.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushJava.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushPerl.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushPerl.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushPhp.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushPhp.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushPlain.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushPlain.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushPython.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushPython.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushRuby.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushRuby.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushSass.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushSass.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushScala.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushScala.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushSql.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushSql.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushVb.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushVb.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shBrushXml.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shBrushXml.js)0
-rw-r--r--guides/assets/javascripts/syntaxhighlighter/shCore.js (renamed from railties/guides/assets/javascripts/syntaxhighlighter/shCore.js)0
-rw-r--r--guides/assets/stylesheets/fixes.css (renamed from railties/guides/assets/stylesheets/fixes.css)2
-rw-r--r--guides/assets/stylesheets/kindle.css11
-rw-r--r--guides/assets/stylesheets/main.css (renamed from railties/guides/assets/stylesheets/main.css)15
-rw-r--r--guides/assets/stylesheets/print.css (renamed from railties/guides/assets/stylesheets/print.css)0
-rw-r--r--guides/assets/stylesheets/reset.css (renamed from railties/guides/assets/stylesheets/reset.css)0
-rw-r--r--guides/assets/stylesheets/style.css (renamed from railties/guides/assets/stylesheets/style.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shCore.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shCore.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shCoreDefault.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shCoreDefault.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shCoreDjango.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shCoreDjango.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shCoreRDark.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shCoreRDark.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shThemeDefault.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shThemeDefault.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shThemeDjango.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shThemeDjango.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css)0
-rwxr-xr-xguides/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shThemeRDark.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shThemeRDark.css)0
-rw-r--r--guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css (renamed from railties/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css)0
-rw-r--r--guides/code/getting_started/Gemfile38
-rw-r--r--guides/code/getting_started/README.rdoc (renamed from railties/guides/code/getting_started/README)10
-rw-r--r--guides/code/getting_started/Rakefile (renamed from railties/guides/code/getting_started/Rakefile)0
-rw-r--r--guides/code/getting_started/app/assets/images/rails.png (renamed from railties/guides/code/getting_started/app/assets/images/rails.png)bin6646 -> 6646 bytes
-rw-r--r--guides/code/getting_started/app/assets/javascripts/application.js15
-rw-r--r--guides/code/getting_started/app/assets/stylesheets/application.css13
-rw-r--r--guides/code/getting_started/app/controllers/application_controller.rb (renamed from railties/guides/code/getting_started/app/controllers/application_controller.rb)0
-rw-r--r--guides/code/getting_started/app/controllers/comments_controller.rb (renamed from railties/guides/code/getting_started/app/controllers/comments_controller.rb)5
-rw-r--r--guides/code/getting_started/app/controllers/home_controller.rb5
-rw-r--r--guides/code/getting_started/app/controllers/posts_controller.rb47
-rw-r--r--guides/code/getting_started/app/helpers/application_helper.rb (renamed from railties/guides/code/getting_started/app/helpers/application_helper.rb)0
-rw-r--r--guides/code/getting_started/app/helpers/comments_helper.rb (renamed from railties/guides/code/getting_started/app/helpers/comments_helper.rb)0
-rw-r--r--guides/code/getting_started/app/helpers/posts_helper.rb2
-rw-r--r--guides/code/getting_started/app/helpers/welcome_helper.rb2
-rw-r--r--guides/code/getting_started/app/mailers/.gitkeep (renamed from railties/guides/code/getting_started/app/mailers/.gitkeep)0
-rw-r--r--guides/code/getting_started/app/models/.gitkeep (renamed from railties/guides/code/getting_started/app/models/.gitkeep)0
-rw-r--r--guides/code/getting_started/app/models/comment.rb (renamed from railties/guides/code/getting_started/app/models/comment.rb)0
-rw-r--r--guides/code/getting_started/app/models/post.rb6
-rw-r--r--guides/code/getting_started/app/views/comments/_comment.html.erb (renamed from railties/guides/code/getting_started/app/views/comments/_comment.html.erb)8
-rw-r--r--guides/code/getting_started/app/views/comments/_form.html.erb (renamed from railties/guides/code/getting_started/app/views/comments/_form.html.erb)12
-rw-r--r--guides/code/getting_started/app/views/layouts/application.html.erb (renamed from railties/guides/code/getting_started/app/views/layouts/application.html.erb)4
-rw-r--r--guides/code/getting_started/app/views/posts/_form.html.erb25
-rw-r--r--guides/code/getting_started/app/views/posts/edit.html.erb5
-rw-r--r--guides/code/getting_started/app/views/posts/index.html.erb23
-rw-r--r--guides/code/getting_started/app/views/posts/new.html.erb (renamed from railties/guides/code/getting_started/app/views/posts/new.html.erb)2
-rw-r--r--guides/code/getting_started/app/views/posts/show.html.erb18
-rw-r--r--guides/code/getting_started/app/views/welcome/index.html.erb2
-rw-r--r--guides/code/getting_started/config.ru (renamed from railties/guides/code/getting_started/config.ru)0
-rw-r--r--guides/code/getting_started/config/application.rb (renamed from railties/guides/code/getting_started/config/application.rb)21
-rw-r--r--guides/code/getting_started/config/boot.rb (renamed from railties/guides/code/getting_started/config/boot.rb)2
-rw-r--r--guides/code/getting_started/config/database.yml (renamed from railties/guides/code/getting_started/config/database.yml)0
-rw-r--r--guides/code/getting_started/config/environment.rb (renamed from railties/guides/code/getting_started/config/environment.rb)0
-rw-r--r--guides/code/getting_started/config/environments/development.rb (renamed from railties/guides/code/getting_started/config/environments/development.rb)24
-rw-r--r--guides/code/getting_started/config/environments/production.rb (renamed from railties/guides/code/getting_started/config/environments/production.rb)45
-rw-r--r--guides/code/getting_started/config/environments/test.rb (renamed from railties/guides/code/getting_started/config/environments/test.rb)26
-rw-r--r--guides/code/getting_started/config/initializers/backtrace_silencers.rb (renamed from railties/guides/code/getting_started/config/initializers/backtrace_silencers.rb)0
-rw-r--r--guides/code/getting_started/config/initializers/inflections.rb (renamed from railties/guides/code/getting_started/config/initializers/inflections.rb)5
-rw-r--r--guides/code/getting_started/config/initializers/mime_types.rb (renamed from railties/guides/code/getting_started/config/initializers/mime_types.rb)0
-rw-r--r--guides/code/getting_started/config/initializers/secret_token.rb (renamed from railties/guides/code/getting_started/config/initializers/secret_token.rb)2
-rw-r--r--guides/code/getting_started/config/initializers/session_store.rb (renamed from railties/guides/code/getting_started/config/initializers/session_store.rb)0
-rw-r--r--guides/code/getting_started/config/initializers/wrap_parameters.rb (renamed from railties/guides/code/getting_started/config/initializers/wrap_parameters.rb)0
-rw-r--r--guides/code/getting_started/config/locales/en.yml (renamed from railties/guides/code/getting_started/config/locales/en.yml)0
-rw-r--r--guides/code/getting_started/config/routes.rb (renamed from railties/guides/code/getting_started/config/routes.rb)13
-rw-r--r--guides/code/getting_started/db/migrate/20110901012815_create_comments.rb (renamed from railties/guides/code/getting_started/db/migrate/20110901012815_create_comments.rb)0
-rw-r--r--guides/code/getting_started/db/migrate/20120420083127_create_posts.rb (renamed from railties/guides/code/getting_started/db/migrate/20110901012504_create_posts.rb)3
-rw-r--r--guides/code/getting_started/db/schema.rb (renamed from railties/guides/code/getting_started/db/schema.rb)17
-rw-r--r--guides/code/getting_started/db/seeds.rb (renamed from railties/guides/code/getting_started/db/seeds.rb)0
-rw-r--r--guides/code/getting_started/doc/README_FOR_APP (renamed from railties/guides/code/getting_started/doc/README_FOR_APP)0
-rw-r--r--guides/code/getting_started/lib/assets/.gitkeep (renamed from railties/guides/code/getting_started/lib/assets/.gitkeep)0
-rw-r--r--guides/code/getting_started/lib/tasks/.gitkeep (renamed from railties/guides/code/getting_started/lib/tasks/.gitkeep)0
-rw-r--r--guides/code/getting_started/public/404.html (renamed from railties/guides/code/getting_started/public/404.html)0
-rw-r--r--guides/code/getting_started/public/422.html (renamed from railties/guides/code/getting_started/public/422.html)0
-rw-r--r--guides/code/getting_started/public/500.html (renamed from railties/guides/code/getting_started/public/500.html)1
-rw-r--r--guides/code/getting_started/public/favicon.ico (renamed from railties/guides/code/getting_started/public/favicon.ico)0
-rw-r--r--guides/code/getting_started/public/robots.txt (renamed from railties/guides/code/getting_started/public/robots.txt)2
-rwxr-xr-xguides/code/getting_started/script/rails (renamed from railties/guides/code/getting_started/script/rails)0
-rw-r--r--guides/code/getting_started/test/fixtures/.gitkeep (renamed from railties/guides/code/getting_started/test/fixtures/.gitkeep)0
-rw-r--r--guides/code/getting_started/test/fixtures/comments.yml (renamed from railties/guides/code/getting_started/test/fixtures/comments.yml)0
-rw-r--r--guides/code/getting_started/test/fixtures/posts.yml (renamed from railties/guides/code/getting_started/test/fixtures/posts.yml)6
-rw-r--r--guides/code/getting_started/test/functional/.gitkeep (renamed from railties/guides/code/getting_started/test/functional/.gitkeep)0
-rw-r--r--guides/code/getting_started/test/functional/comments_controller_test.rb (renamed from railties/guides/code/getting_started/test/functional/comments_controller_test.rb)0
-rw-r--r--guides/code/getting_started/test/functional/home_controller_test.rb (renamed from railties/guides/code/getting_started/test/functional/home_controller_test.rb)2
-rw-r--r--guides/code/getting_started/test/functional/posts_controller_test.rb (renamed from railties/guides/code/getting_started/test/functional/posts_controller_test.rb)0
-rw-r--r--guides/code/getting_started/test/integration/.gitkeep (renamed from railties/guides/code/getting_started/test/integration/.gitkeep)0
-rw-r--r--guides/code/getting_started/test/performance/browsing_test.rb (renamed from railties/guides/code/getting_started/test/performance/browsing_test.rb)2
-rw-r--r--guides/code/getting_started/test/test_helper.rb (renamed from railties/guides/code/getting_started/test/test_helper.rb)0
-rw-r--r--guides/code/getting_started/test/unit/.gitkeep (renamed from railties/guides/code/getting_started/test/unit/.gitkeep)0
-rw-r--r--guides/code/getting_started/test/unit/comment_test.rb (renamed from railties/guides/code/getting_started/test/unit/comment_test.rb)0
-rw-r--r--guides/code/getting_started/test/unit/helpers/comments_helper_test.rb (renamed from railties/guides/code/getting_started/test/unit/helpers/comments_helper_test.rb)0
-rw-r--r--guides/code/getting_started/test/unit/helpers/home_helper_test.rb (renamed from railties/guides/code/getting_started/test/unit/helpers/home_helper_test.rb)0
-rw-r--r--guides/code/getting_started/test/unit/helpers/posts_helper_test.rb (renamed from railties/guides/code/getting_started/test/unit/helpers/posts_helper_test.rb)0
-rw-r--r--guides/code/getting_started/test/unit/post_test.rb (renamed from railties/guides/code/getting_started/test/unit/post_test.rb)0
-rw-r--r--guides/code/getting_started/test/unit/tag_test.rb (renamed from railties/guides/code/getting_started/test/unit/tag_test.rb)0
-rw-r--r--guides/code/getting_started/vendor/plugins/.gitkeep (renamed from railties/guides/code/getting_started/vendor/assets/stylesheets/.gitkeep)0
-rw-r--r--guides/rails_guides.rb (renamed from railties/guides/rails_guides.rb)7
-rw-r--r--guides/rails_guides/generator.rb (renamed from railties/guides/rails_guides/generator.rb)112
-rw-r--r--guides/rails_guides/helpers.rb (renamed from railties/guides/rails_guides/helpers.rb)16
-rw-r--r--guides/rails_guides/indexer.rb (renamed from railties/guides/rails_guides/indexer.rb)3
-rw-r--r--guides/rails_guides/levenshtein.rb (renamed from railties/guides/rails_guides/levenshtein.rb)2
-rw-r--r--guides/rails_guides/textile_extensions.rb (renamed from railties/guides/rails_guides/textile_extensions.rb)6
-rw-r--r--guides/source/2_2_release_notes.textile (renamed from railties/guides/source/2_2_release_notes.textile)2
-rw-r--r--guides/source/2_3_release_notes.textile (renamed from railties/guides/source/2_3_release_notes.textile)0
-rw-r--r--guides/source/3_0_release_notes.textile (renamed from railties/guides/source/3_0_release_notes.textile)0
-rw-r--r--guides/source/3_1_release_notes.textile (renamed from railties/guides/source/3_1_release_notes.textile)109
-rw-r--r--guides/source/3_2_release_notes.textile552
-rw-r--r--guides/source/_license.html.erb2
-rw-r--r--guides/source/_welcome.html.erb19
-rw-r--r--guides/source/action_controller_overview.textile (renamed from railties/guides/source/action_controller_overview.textile)23
-rw-r--r--guides/source/action_mailer_basics.textile (renamed from railties/guides/source/action_mailer_basics.textile)19
-rw-r--r--guides/source/action_view_overview.textile (renamed from railties/guides/source/action_view_overview.textile)145
-rw-r--r--guides/source/active_model_basics.textile (renamed from railties/guides/source/active_model_basics.textile)7
-rw-r--r--guides/source/active_record_basics.textile (renamed from railties/guides/source/active_record_basics.textile)4
-rw-r--r--guides/source/active_record_querying.textile (renamed from railties/guides/source/active_record_querying.textile)398
-rw-r--r--guides/source/active_record_validations_callbacks.textile (renamed from railties/guides/source/active_record_validations_callbacks.textile)85
-rw-r--r--guides/source/active_support_core_extensions.textile (renamed from railties/guides/source/active_support_core_extensions.textile)650
-rw-r--r--guides/source/active_support_instrumentation.textile448
-rw-r--r--guides/source/ajax_on_rails.textile (renamed from railties/guides/source/ajax_on_rails.textile)19
-rw-r--r--guides/source/api_documentation_guidelines.textile (renamed from railties/guides/source/api_documentation_guidelines.textile)44
-rw-r--r--guides/source/asset_pipeline.textile (renamed from railties/guides/source/asset_pipeline.textile)268
-rw-r--r--guides/source/association_basics.textile (renamed from railties/guides/source/association_basics.textile)122
-rw-r--r--guides/source/caching_with_rails.textile (renamed from railties/guides/source/caching_with_rails.textile)92
-rw-r--r--guides/source/command_line.textile (renamed from railties/guides/source/command_line.textile)67
-rw-r--r--guides/source/configuring.textile (renamed from railties/guides/source/configuring.textile)216
-rw-r--r--guides/source/contributing_to_ruby_on_rails.textile (renamed from railties/guides/source/contributing_to_ruby_on_rails.textile)146
-rw-r--r--guides/source/credits.html.erb (renamed from railties/guides/source/credits.html.erb)0
-rw-r--r--guides/source/debugging_rails_applications.textile (renamed from railties/guides/source/debugging_rails_applications.textile)43
-rw-r--r--guides/source/documents.yaml167
-rw-r--r--guides/source/engines.textile764
-rw-r--r--guides/source/form_helpers.textile (renamed from railties/guides/source/form_helpers.textile)60
-rw-r--r--guides/source/generators.textile (renamed from railties/guides/source/generators.textile)39
-rw-r--r--guides/source/getting_started.textile1745
-rw-r--r--guides/source/i18n.textile (renamed from railties/guides/source/i18n.textile)42
-rw-r--r--guides/source/index.html.erb30
-rw-r--r--guides/source/initialization.textile (renamed from railties/guides/source/initialization.textile)50
-rw-r--r--guides/source/kindle/KINDLE.md26
-rw-r--r--guides/source/kindle/copyright.html.erb1
-rw-r--r--guides/source/kindle/layout.html.erb27
-rw-r--r--guides/source/kindle/rails_guides.opf.erb52
-rw-r--r--guides/source/kindle/toc.html.erb24
-rw-r--r--guides/source/kindle/toc.ncx.erb64
-rw-r--r--guides/source/kindle/welcome.html.erb5
-rw-r--r--guides/source/layout.html.erb (renamed from railties/guides/source/layout.html.erb)61
-rw-r--r--guides/source/layouts_and_rendering.textile (renamed from railties/guides/source/layouts_and_rendering.textile)235
-rw-r--r--guides/source/migrations.textile932
-rw-r--r--guides/source/nested_model_forms.textile (renamed from railties/guides/source/nested_model_forms.textile)12
-rw-r--r--guides/source/performance_testing.textile (renamed from railties/guides/source/performance_testing.textile)4
-rw-r--r--guides/source/plugins.textile (renamed from railties/guides/source/plugins.textile)60
-rw-r--r--guides/source/rails_application_templates.textile (renamed from railties/guides/source/rails_application_templates.textile)27
-rw-r--r--guides/source/rails_on_rack.textile (renamed from railties/guides/source/rails_on_rack.textile)127
-rw-r--r--guides/source/routing.textile (renamed from railties/guides/source/routing.textile)154
-rw-r--r--guides/source/ruby_on_rails_guides_guidelines.textile (renamed from railties/guides/source/ruby_on_rails_guides_guidelines.textile)35
-rw-r--r--guides/source/security.textile (renamed from railties/guides/source/security.textile)15
-rw-r--r--guides/source/testing.textile (renamed from railties/guides/source/testing.textile)64
-rw-r--r--guides/source/upgrading_ruby_on_rails.textile198
-rw-r--r--guides/w3c_validator.rb (renamed from railties/guides/w3c_validator.rb)3
-rw-r--r--install.rb4
-rw-r--r--load_paths.rb3
-rw-r--r--rails.gemspec29
-rw-r--r--railties/CHANGELOG2386
-rw-r--r--railties/CHANGELOG.md2514
-rw-r--r--railties/MIT-LICENSE2
-rw-r--r--railties/README.rdoc19
-rw-r--r--[-rwxr-xr-x]railties/Rakefile25
-rw-r--r--railties/guides/assets/images/posts_index.pngbin60846 -> 0 bytes
-rw-r--r--railties/guides/code/getting_started/Gemfile32
-rw-r--r--railties/guides/code/getting_started/app/assets/javascripts/application.js9
-rw-r--r--railties/guides/code/getting_started/app/assets/javascripts/comments.js.coffee3
-rw-r--r--railties/guides/code/getting_started/app/assets/javascripts/home.js.coffee3
-rw-r--r--railties/guides/code/getting_started/app/assets/javascripts/posts.js.coffee3
-rw-r--r--railties/guides/code/getting_started/app/assets/stylesheets/application.css7
-rw-r--r--railties/guides/code/getting_started/app/assets/stylesheets/comments.css.scss3
-rw-r--r--railties/guides/code/getting_started/app/assets/stylesheets/home.css.scss3
-rw-r--r--railties/guides/code/getting_started/app/assets/stylesheets/posts.css.scss3
-rw-r--r--railties/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss56
-rw-r--r--railties/guides/code/getting_started/app/controllers/home_controller.rb5
-rw-r--r--railties/guides/code/getting_started/app/controllers/posts_controller.rb84
-rw-r--r--railties/guides/code/getting_started/app/helpers/home_helper.rb2
-rw-r--r--railties/guides/code/getting_started/app/helpers/posts_helper.rb5
-rw-r--r--railties/guides/code/getting_started/app/models/post.rb11
-rw-r--r--railties/guides/code/getting_started/app/models/tag.rb3
-rw-r--r--railties/guides/code/getting_started/app/views/home/index.html.erb2
-rw-r--r--railties/guides/code/getting_started/app/views/posts/_form.html.erb32
-rw-r--r--railties/guides/code/getting_started/app/views/posts/edit.html.erb6
-rw-r--r--railties/guides/code/getting_started/app/views/posts/index.html.erb27
-rw-r--r--railties/guides/code/getting_started/app/views/posts/show.html.erb31
-rw-r--r--railties/guides/code/getting_started/app/views/tags/_form.html.erb12
-rw-r--r--railties/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb11
-rw-r--r--railties/guides/code/getting_started/test/fixtures/tags.yml9
-rw-r--r--railties/guides/code/getting_started/vendor/plugins/.gitkeep0
-rw-r--r--railties/guides/source/active_resource_basics.textile120
-rw-r--r--railties/guides/source/engines.textile393
-rw-r--r--railties/guides/source/getting_started.textile1892
-rw-r--r--railties/guides/source/index.html.erb191
-rw-r--r--railties/guides/source/migrations.textile673
-rw-r--r--railties/lib/rails.rb62
-rw-r--r--railties/lib/rails/all.rb3
-rw-r--r--railties/lib/rails/application.rb164
-rw-r--r--railties/lib/rails/application/bootstrap.rb35
-rw-r--r--railties/lib/rails/application/configuration.rb92
-rw-r--r--railties/lib/rails/application/finisher.rb67
-rw-r--r--railties/lib/rails/application/railties.rb2
-rw-r--r--railties/lib/rails/application/route_inspector.rb106
-rw-r--r--railties/lib/rails/application/routes_reloader.rb28
-rw-r--r--railties/lib/rails/backtrace_cleaner.rb17
-rw-r--r--railties/lib/rails/code_statistics.rb35
-rw-r--r--railties/lib/rails/commands.rb15
-rw-r--r--railties/lib/rails/commands/application.rb1
-rw-r--r--railties/lib/rails/commands/benchmarker.rb2
-rw-r--r--railties/lib/rails/commands/console.rb88
-rw-r--r--railties/lib/rails/commands/dbconsole.rb96
-rw-r--r--railties/lib/rails/commands/plugin.rb542
-rw-r--r--railties/lib/rails/commands/plugin_new.rb2
-rw-r--r--railties/lib/rails/commands/profiler.rb2
-rw-r--r--railties/lib/rails/commands/runner.rb1
-rw-r--r--railties/lib/rails/commands/server.rb20
-rw-r--r--railties/lib/rails/configuration.rb33
-rw-r--r--railties/lib/rails/console/app.rb49
-rw-r--r--railties/lib/rails/console/helpers.rb14
-rw-r--r--railties/lib/rails/deprecation.rb18
-rw-r--r--railties/lib/rails/engine.rb123
-rw-r--r--railties/lib/rails/engine/commands.rb4
-rw-r--r--railties/lib/rails/engine/configuration.rb9
-rw-r--r--railties/lib/rails/engine/railties.rb9
-rw-r--r--railties/lib/rails/generators.rb21
-rw-r--r--railties/lib/rails/generators/actions.rb100
-rw-r--r--railties/lib/rails/generators/active_model.rb6
-rw-r--r--railties/lib/rails/generators/app_base.rb97
-rw-r--r--railties/lib/rails/generators/base.rb60
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb38
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/show.html.erb2
-rw-r--r--railties/lib/rails/generators/generated_attribute.rb76
-rw-r--r--railties/lib/rails/generators/migration.rb4
-rw-r--r--railties/lib/rails/generators/named_base.rb22
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile14
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README10
-rw-r--r--[-rwxr-xr-x]railties/lib/rails/generators/rails/app/templates/Rakefile1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb31
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/boot.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt28
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt63
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt27
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/session_store.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/gitignore1
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/404.html2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/422.html2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/500.html2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/index.html6
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/robots.txt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb2
-rw-r--r--railties/lib/rails/generators/rails/controller/templates/controller.rb2
-rw-r--r--railties/lib/rails/generators/rails/migration/migration_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/model/model_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb59
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/Gemfile2
-rw-r--r--[-rwxr-xr-x]railties/lib/rails/generators/rails/plugin_new/templates/Rakefile10
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/gitignore8
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb3
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb1
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb5
-rw-r--r--railties/lib/rails/generators/rails/resource/resource_generator.rb8
-rw-r--r--railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb13
-rw-r--r--railties/lib/rails/generators/rails/scaffold/USAGE24
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb28
-rw-r--r--railties/lib/rails/generators/rails/task/USAGE9
-rw-r--r--railties/lib/rails/generators/rails/task/task_generator.rb12
-rw-r--r--railties/lib/rails/generators/rails/task/templates/task.rb8
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb6
-rw-r--r--railties/lib/rails/generators/test_case.rb17
-rw-r--r--railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb3
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb21
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb10
-rw-r--r--railties/lib/rails/info.rb4
-rw-r--r--railties/lib/rails/initializable.rb2
-rw-r--r--railties/lib/rails/paths.rb86
-rw-r--r--railties/lib/rails/performance_test_help.rb2
-rw-r--r--railties/lib/rails/plugin.rb91
-rw-r--r--railties/lib/rails/queueing.rb73
-rw-r--r--railties/lib/rails/rack.rb1
-rw-r--r--railties/lib/rails/rack/content_length.rb38
-rw-r--r--railties/lib/rails/rack/debugger.rb4
-rw-r--r--railties/lib/rails/rack/log_tailer.rb10
-rw-r--r--railties/lib/rails/rack/logger.rb38
-rw-r--r--railties/lib/rails/railtie.rb18
-rw-r--r--railties/lib/rails/railtie/configuration.rb14
-rw-r--r--railties/lib/rails/ruby_version_check.rb14
-rw-r--r--railties/lib/rails/rubyprof_ext.rb2
-rw-r--r--railties/lib/rails/source_annotation_extractor.rb30
-rw-r--r--railties/lib/rails/tasks/documentation.rake70
-rw-r--r--railties/lib/rails/tasks/engine.rake1
-rw-r--r--railties/lib/rails/tasks/statistics.rake1
-rw-r--r--railties/lib/rails/test_help.rb28
-rw-r--r--railties/lib/rails/test_unit/sub_test_task.rb8
-rw-r--r--railties/lib/rails/test_unit/testing.rake88
-rw-r--r--railties/lib/rails/version.rb4
-rw-r--r--railties/railties.gemspec11
-rw-r--r--railties/test/abstract_unit.rb5
-rw-r--r--railties/test/application/asset_debugging_test.rb12
-rw-r--r--railties/test/application/assets_test.rb91
-rw-r--r--railties/test/application/build_original_fullpath_test.rb27
-rw-r--r--railties/test/application/configuration_test.rb113
-rw-r--r--railties/test/application/console_test.rb16
-rw-r--r--railties/test/application/generators_test.rb2
-rw-r--r--railties/test/application/initializers/boot_test.rb2
-rw-r--r--railties/test/application/initializers/check_ruby_version_test.rb39
-rw-r--r--railties/test/application/initializers/frameworks_test.rb35
-rw-r--r--railties/test/application/initializers/hooks_test.rb2
-rw-r--r--railties/test/application/initializers/i18n_test.rb9
-rw-r--r--railties/test/application/initializers/load_path_test.rb2
-rw-r--r--railties/test/application/initializers/notifications_test.rb2
-rw-r--r--railties/test/application/loading_test.rb217
-rw-r--r--railties/test/application/middleware/best_practices_test.rb2
-rw-r--r--railties/test/application/middleware/cache_test.rb8
-rw-r--r--railties/test/application/middleware/cookies_test.rb47
-rw-r--r--railties/test/application/middleware/exceptions_test.rb117
-rw-r--r--railties/test/application/middleware/remote_ip_test.rb2
-rw-r--r--railties/test/application/middleware/sendfile_test.rb15
-rw-r--r--railties/test/application/middleware/session_test.rb50
-rw-r--r--railties/test/application/middleware/show_exceptions_test.rb41
-rw-r--r--railties/test/application/middleware_test.rb59
-rw-r--r--railties/test/application/paths_test.rb6
-rw-r--r--railties/test/application/queue_test.rb145
-rw-r--r--railties/test/application/rack/logger_test.rb4
-rw-r--r--railties/test/application/rackup_test.rb4
-rw-r--r--railties/test/application/rake/migrations_test.rb158
-rw-r--r--railties/test/application/rake/notes_test.rb134
-rw-r--r--railties/test/application/rake_test.rb128
-rw-r--r--railties/test/application/route_inspect_test.rb133
-rw-r--r--railties/test/application/routing_test.rb105
-rw-r--r--railties/test/application/runner_test.rb2
-rw-r--r--railties/test/application/test_test.rb27
-rw-r--r--railties/test/application/url_generation_test.rb4
-rw-r--r--railties/test/backtrace_cleaner_test.rb54
-rw-r--r--railties/test/commands/console_test.rb106
-rw-r--r--railties/test/commands/dbconsole_test.rb160
-rw-r--r--railties/test/commands/server_test.rb26
-rw-r--r--railties/test/engine_test.rb24
-rw-r--r--railties/test/fixtures/lib/generators/usage_template/USAGE1
-rw-r--r--railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb5
-rw-r--r--railties/test/generators/actions_test.rb47
-rw-r--r--railties/test/generators/app_generator_test.rb170
-rw-r--r--railties/test/generators/generated_attribute_test.rb7
-rw-r--r--railties/test/generators/mailer_generator_test.rb29
-rw-r--r--railties/test/generators/migration_generator_test.rb84
-rw-r--r--railties/test/generators/model_generator_test.rb86
-rw-r--r--railties/test/generators/named_base_test.rb9
-rw-r--r--railties/test/generators/namespaced_generators_test.rb17
-rw-r--r--railties/test/generators/orm_test.rb38
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb66
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb26
-rw-r--r--railties/test/generators/scaffold_generator_test.rb24
-rw-r--r--railties/test/generators/shared_generator_tests.rb19
-rw-r--r--railties/test/generators/task_generator_test.rb12
-rw-r--r--railties/test/generators_test.rb12
-rw-r--r--railties/test/isolation/abstract_unit.rb49
-rw-r--r--railties/test/paths_test.rb8
-rw-r--r--railties/test/queueing/test_queue_test.rb67
-rw-r--r--railties/test/queueing/threaded_consumer_test.rb100
-rw-r--r--railties/test/rails_info_controller_test.rb2
-rw-r--r--railties/test/rails_info_test.rb2
-rw-r--r--railties/test/railties/engine_test.rb656
-rw-r--r--railties/test/railties/generators_test.rb4
-rw-r--r--railties/test/railties/mounted_engine_test.rb24
-rw-r--r--railties/test/railties/plugin_ordering_test.rb76
-rw-r--r--railties/test/railties/plugin_test.rb123
-rw-r--r--railties/test/railties/railtie_test.rb2
-rw-r--r--railties/test/railties/shared_tests.rb361
-rw-r--r--tasks/release.rb15
-rwxr-xr-xtools/profile2
-rw-r--r--version.rb4
1659 files changed, 66602 insertions, 57084 deletions
diff --git a/.gitignore b/.gitignore
index ab0950d7dd..854fdbf450 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,26 +1,24 @@
-pkg
-.bundle
-Gemfile.lock
+# Don't put *.swp, *.bak, etc here; those belong in a global ~/.gitignore.
+# Check out http://help.github.com/ignore-files/ for how to set that up.
+
debug.log
-doc/rdoc
-activemodel/doc
-activeresource/doc
-activerecord/doc
-activerecord/sqlnet.log
-actionpack/doc
-actionmailer/doc
-activesupport/doc
-activesupport/test/tmp
-activemodel/test/fixtures/fixture_database.sqlite3
-actionpack/test/tmp
-activesupport/test/fixtures/isolation_test
-dist
-railties/test/500.html
-railties/test/fixtures/tmp
-railties/test/initializer/root/log
-railties/doc
-railties/guides/output
-railties/tmp
-.rvmrc
-.rbenv-version
-RDOC_MAIN.rdoc
+.Gemfile
+/.bundle
+/.rbenv-version
+/.rvmrc
+/Gemfile.lock
+/pkg
+/dist
+/doc/rdoc
+/*/doc
+/*/test/tmp
+/activerecord/sqlnet.log
+/activemodel/test/fixtures/fixture_database.sqlite3
+/activesupport/test/fixtures/isolation_test
+/railties/test/500.html
+/railties/test/fixtures/tmp
+/railties/test/initializer/root/log
+/railties/doc
+/railties/tmp
+/guides/output
+/RDOC_MAIN.rdoc
diff --git a/.travis.yml b/.travis.yml
index 4a27f7788d..c6f868498d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,15 @@
script: 'ci/travis.rb'
+before_install:
+ - gem install bundler
rvm:
- - 1.8.7
- - 1.9.2
- #- 1.9.3 # Disable 1.9.3 builds until Travis is on 1.9.3-rc1.
+ - 1.9.3
env:
- "GEM=railties"
- - "GEM=ap,am,amo,ares,as"
+ - "GEM=ap,am,amo,as"
- "GEM=ar:mysql"
- "GEM=ar:mysql2"
- "GEM=ar:sqlite3"
+ - "GEM=ar:postgresql"
notifications:
email: false
irc:
@@ -16,4 +17,9 @@ notifications:
on_failure: always
channels:
- "irc.freenode.org#rails-contrib"
+ campfire:
+ on_success: change
+ on_failure: always
+ rooms:
+ - secure: "CGWvthGkBKNnTnk9YSmf9AXKoiRI33fCl5D3jU4nx3cOPu6kv2R9nMjt9EAo\nOuS4Q85qNSf4VNQ2cUPNiNYSWQ+XiTfivKvDUw/QW9r1FejYyeWarMsSBWA+\n0fADjF1M2dkDIVLgYPfwoXEv7l+j654F1KLKB69F0F/netwP9CQ="
bundler_args: --path vendor/bundle
diff --git a/Gemfile b/Gemfile
index f63260a260..ac835936ca 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,104 +1,101 @@
-source "http://rubygems.org"
+source 'https://rubygems.org'
gemspec
if ENV['AREL']
- gem "arel", :path => ENV['AREL']
+ gem 'arel', path: ENV['AREL']
+else
+ gem 'arel'
end
-gem "bcrypt-ruby", "~> 3.0.0"
-gem "jquery-rails"
+gem 'rack-test', github: "brynary/rack-test"
+gem 'bcrypt-ruby', '~> 3.0.0'
+gem 'jquery-rails'
+gem 'minitest', '~> 3.0.0'
if ENV['JOURNEY']
- gem "journey", :path => ENV['JOURNEY']
+ gem 'journey', path: ENV['JOURNEY']
else
- gem "journey", :git => "git://github.com/rails/journey"
+ gem 'journey', github: "rails/journey"
+end
+
+if ENV['AR_DEPRECATED_FINDERS']
+ gem 'active_record_deprecated_finders', path: ENV['AR_DEPRECATED_FINDERS']
+else
+ gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
end
# This needs to be with require false to avoid
# it being automatically loaded by sprockets
-gem "uglifier", ">= 1.0.3", :require => false
+gem 'uglifier', '>= 1.0.3', require: false
-# Temp fix until rake 0.9.3 is out
-if RUBY_VERSION >= "1.9.3"
- gem "rake", "0.9.3.beta.1"
-else
- gem "rake", ">= 0.8.7"
-end
-gem "mocha", ">= 0.9.8"
+gem 'rake', '>= 0.8.7'
+gem 'mocha', '>= 0.11.2'
group :doc do
- gem "rdoc", "~> 3.4"
- gem "sdoc", "~> 0.3"
- gem "RedCloth", "~> 4.2" if RUBY_VERSION < "1.9.3"
- gem "w3c_validators"
+ # The current sdoc cannot generate GitHub links due
+ # to a bug, but the PR that fixes it has been there
+ # for some weeks unapplied. As a temporary solution
+ # this is our own fork with the fix.
+ gem 'sdoc', github: 'fxn/sdoc'
+ gem 'RedCloth', '~> 4.2'
+ gem 'w3c_validators'
end
# AS
-gem "memcache-client", ">= 1.8.5"
+gem 'memcache-client', '>= 1.8.5'
-platforms :mri_18 do
- gem "system_timer"
- gem "ruby-debug", ">= 0.10.3" unless ENV['TRAVIS']
- gem "json"
-end
-
-platforms :mri_19 do
- # TODO: Remove the conditional when ruby-debug19 supports Ruby >= 1.9.3
- gem "ruby-debug19", :require => "ruby-debug" unless RUBY_VERSION > "1.9.2" || ENV['TRAVIS']
-end
+# Add your own local bundler stuff
+local_gemfile = File.dirname(__FILE__) + "/.Gemfile"
+instance_eval File.read local_gemfile if File.exists? local_gemfile
platforms :mri do
group :test do
- gem "ruby-prof" if RUBY_VERSION < "1.9.3"
+ gem 'ruby-prof', '~> 0.11.2'
end
end
platforms :ruby do
- if ENV["RB_FSEVENT"]
- gem "rb-fsevent"
- end
- gem "json"
- gem "yajl-ruby"
- gem "nokogiri", ">= 1.4.5"
+ gem 'json'
+ gem 'yajl-ruby'
+ gem 'nokogiri', '>= 1.4.5'
# AR
- gem "sqlite3", "~> 1.3.4"
+ gem 'sqlite3', '~> 1.3.6'
group :db do
- gem "pg", ">= 0.11.0" unless ENV['TRAVIS'] # once pg is on travis this can be removed
- gem "mysql", ">= 2.8.1"
- gem "mysql2", ">= 0.3.6"
+ gem 'pg', '>= 0.11.0'
+ gem 'mysql', '>= 2.8.1'
+ gem 'mysql2', '>= 0.3.10'
end
end
platforms :jruby do
- gem "ruby-debug", ">= 0.10.3"
- gem "json"
- gem "activerecord-jdbcsqlite3-adapter", ">= 1.2.0"
+ gem 'json'
+ gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.0'
# This is needed by now to let tests work on JRuby
# TODO: When the JRuby guys merge jruby-openssl in
# jruby this will be removed
- gem "jruby-openssl"
+ gem 'jruby-openssl'
group :db do
- gem "activerecord-jdbcmysql-adapter", ">= 1.2.0"
- gem "activerecord-jdbcpostgresql-adapter", ">= 1.2.0"
+ gem 'activerecord-jdbcmysql-adapter', '>= 1.2.0'
+ gem 'activerecord-jdbcpostgresql-adapter', '>= 1.2.0'
end
end
# gems that are necessary for ActiveRecord tests with Oracle database
if ENV['ORACLE_ENHANCED_PATH'] || ENV['ORACLE_ENHANCED']
platforms :ruby do
- gem "ruby-oci8", ">= 2.0.4"
+ gem 'ruby-oci8', '>= 2.0.4'
end
if ENV['ORACLE_ENHANCED_PATH']
- gem "activerecord-oracle_enhanced-adapter", :path => ENV['ORACLE_ENHANCED_PATH']
+ gem 'activerecord-oracle_enhanced-adapter', path: ENV['ORACLE_ENHANCED_PATH']
else
- gem "activerecord-oracle_enhanced-adapter", :git => "git://github.com/rsim/oracle-enhanced.git"
+ gem 'activerecord-oracle_enhanced-adapter', github: 'rsim/oracle-enhanced'
end
end
# A gem necessary for ActiveRecord tests with IBM DB
-gem "ibm_db" if ENV['IBM_DB']
+gem 'ibm_db' if ENV['IBM_DB']
diff --git a/RAILS_VERSION b/RAILS_VERSION
index dd8b7cd23b..e1e048d8f0 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-3.2.0.beta
+4.0.0.beta
diff --git a/README.rdoc b/README.rdoc
index 0def4e5d12..c9fb595efb 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -47,7 +47,7 @@ can read more about Action Pack in its {README}[link:/rails/rails/blob/master/ac
cd myapp; rails server
- Run with <tt>--help</tt> for options.
+ Run with <tt>--help</tt> or <tt>-h</tt> for options.
4. Go to http://localhost:3000 and you'll see:
@@ -67,9 +67,12 @@ We encourage you to contribute to Ruby on Rails! Please check out the {Contribut
guide}[http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how
to proceed. {Join us}[http://contributors.rubyonrails.org]!
-== Travis Build Status {<img src="https://secure.travis-ci.org/rails/rails.png"/>}[http://travis-ci.org/rails/rails]
+== Build Status {<img src="https://secure.travis-ci.org/rails/rails.png"/>}[http://travis-ci.org/rails/rails]
+
+== Dependency Status {<img src="https://gemnasium.com/rails/rails.png?travis"/>}[https://gemnasium.com/rails/rails]
== License
-Ruby on Rails is released under the MIT license.
+Ruby on Rails is released under the MIT license:
+* http://www.opensource.org/licenses/MIT
diff --git a/RELEASING_RAILS.rdoc b/RELEASING_RAILS.rdoc
index cbc9d0e1de..7a77f9bba2 100644
--- a/RELEASING_RAILS.rdoc
+++ b/RELEASING_RAILS.rdoc
@@ -81,8 +81,20 @@ You can review the commits for the 3.0.10 release like this:
[aaron@higgins rails (3-0-10)]$ git log v3.0.9..
+If you're doing a stable branch release, you should also ensure that all of
+the CHANGELOG entries in the stable branch are also synced to the master
+branch.
+
=== Update the RAILS_VERSION file to include the RC.
+=== Build and test the gem.
+
+Run `rake install` to generate the gems and install them locally. Then try
+generating a new app and ensure that nothing explodes.
+
+This will stop you from looking silly when you push an RC to rubygems.org and
+then realise it is broken.
+
=== Release the gem.
IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest
@@ -150,6 +162,7 @@ Today, do this stuff in this order:
* Apply security patches to the release branch
* Update CHANGELOG with security fixes.
* Update RAILS_VERSION to remove the rc
+* Build and test the gem
* Release the gems
* Email security lists
* Email general announcement lists
@@ -184,3 +197,34 @@ There are two simple steps for fixing the CI:
2. Fix it
Repeat these steps until the CI is green.
+
+=== Manually trigger docs generation
+
+We have a post-receive hook in GitHub that calls the docs server on pushes.
+It triggers generation and publication of edge docs, updates the contrib app,
+and generates and publishes stable docs if a new stable tag is detected.
+
+The hook unfortunately is not invoked by tag pushing, so once the new stable
+tag has been pushed to origin, please run
+
+ rake publish_docs
+
+You should see something like this:
+
+ Rails master hook tasks scheduled:
+
+ * updates the local checkout
+ * updates Rails Contributors
+ * generates and publishes edge docs
+
+ If a new stable tag is detected it also
+
+ * generates and publishes stable docs
+
+ This needs typically a few minutes.
+
+Note you do not need to specify the tag, the docs server figures it out.
+
+Also, don't worry if you call that multiple times or the hook is triggered
+again by some immediate regular push, if the scripts are running new calls
+are just queued (in a queue of size 1).
diff --git a/Rakefile b/Rakefile
index a47d415abc..21eb60bbe1 100755..100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,5 +1,3 @@
-#!/usr/bin/env rake
-
require 'rdoc/task'
require 'sdoc'
require 'net/http'
@@ -13,7 +11,7 @@ task :build => "all:build"
desc "Release all gems to gemcutter and create a tag"
task :release => "all:release"
-PROJECTS = %w(activesupport activemodel actionpack actionmailer activeresource activerecord railties)
+PROJECTS = %w(activesupport activemodel actionpack actionmailer activerecord railties)
desc 'Run all tests by default'
task :default => %w(test test:isolated)
@@ -77,9 +75,10 @@ RDoc::Task.new do |rdoc|
rdoc_main.gsub!(/^(?=\S).*?\b(?=Rails)\b/) { "#$&\\" }
rdoc_main.gsub!(%r{link:/rails/rails/blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html")
- # Remove Travis build status image from API pages. Only GitHub README page gets this image
- # https build image is used to avoid GitHub caching: http://about.travis-ci.org/docs/user/status-images
- rdoc_main.gsub!(%r{^== Travis.*}, '')
+ # Remove Travis and Gemnasium status images from API pages. Only GitHub
+ # README page gets these images. Travis' https build image is used to avoid
+ # GitHub caching: http://about.travis-ci.org/docs/user/status-images
+ rdoc_main.gsub!(%r{^== (Build|Dependency) Status.*}, '')
File.open(RDOC_MAIN, 'w') do |f|
f.write(rdoc_main)
@@ -93,28 +92,23 @@ RDoc::Task.new do |rdoc|
rdoc.options << '-f' << 'sdoc'
rdoc.options << '-T' << 'rails'
- rdoc.options << '-c' << 'utf-8'
+ rdoc.options << '-e' << 'UTF-8'
rdoc.options << '-g' # SDoc flag, link methods to GitHub
rdoc.options << '-m' << RDOC_MAIN
- rdoc.rdoc_files.include('railties/CHANGELOG')
+ rdoc.rdoc_files.include('railties/CHANGELOG.md')
rdoc.rdoc_files.include('railties/MIT-LICENSE')
rdoc.rdoc_files.include('railties/README.rdoc')
rdoc.rdoc_files.include('railties/lib/**/*.rb')
rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/**/*.rb')
rdoc.rdoc_files.include('activerecord/README.rdoc')
- rdoc.rdoc_files.include('activerecord/CHANGELOG')
+ rdoc.rdoc_files.include('activerecord/CHANGELOG.md')
rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb')
rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*')
- rdoc.rdoc_files.include('activeresource/README.rdoc')
- rdoc.rdoc_files.include('activeresource/CHANGELOG')
- rdoc.rdoc_files.include('activeresource/lib/active_resource.rb')
- rdoc.rdoc_files.include('activeresource/lib/active_resource/*')
-
rdoc.rdoc_files.include('actionpack/README.rdoc')
- rdoc.rdoc_files.include('actionpack/CHANGELOG')
+ rdoc.rdoc_files.include('actionpack/CHANGELOG.md')
rdoc.rdoc_files.include('actionpack/lib/abstract_controller/**/*.rb')
rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb')
rdoc.rdoc_files.include('actionpack/lib/action_dispatch/**/*.rb')
@@ -122,18 +116,18 @@ RDoc::Task.new do |rdoc|
rdoc.rdoc_files.exclude('actionpack/lib/action_controller/vendor/*')
rdoc.rdoc_files.include('actionmailer/README.rdoc')
- rdoc.rdoc_files.include('actionmailer/CHANGELOG')
+ rdoc.rdoc_files.include('actionmailer/CHANGELOG.md')
rdoc.rdoc_files.include('actionmailer/lib/action_mailer/base.rb')
rdoc.rdoc_files.include('actionmailer/lib/action_mailer/mail_helper.rb')
rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*')
rdoc.rdoc_files.include('activesupport/README.rdoc')
- rdoc.rdoc_files.include('activesupport/CHANGELOG')
+ rdoc.rdoc_files.include('activesupport/CHANGELOG.md')
rdoc.rdoc_files.include('activesupport/lib/active_support/**/*.rb')
rdoc.rdoc_files.exclude('activesupport/lib/active_support/vendor/*')
rdoc.rdoc_files.include('activemodel/README.rdoc')
- rdoc.rdoc_files.include('activemodel/CHANGELOG')
+ rdoc.rdoc_files.include('activemodel/CHANGELOG.md')
rdoc.rdoc_files.include('activemodel/lib/active_model/**/*.rb')
end
@@ -156,7 +150,6 @@ task :update_versions do
"activemodel" => "ActiveModel",
"actionpack" => "ActionPack",
"actionmailer" => "ActionMailer",
- "activeresource" => "ActiveResource",
"activerecord" => "ActiveRecord",
"railties" => "Rails"
}
@@ -189,7 +182,7 @@ end
#
desc 'Publishes docs, run this AFTER a new stable tag has been pushed'
task :publish_docs do
- Net::HTTP.new('rails-hooks.hashref.com').start do |http|
+ Net::HTTP.new('api.rubyonrails.org', 8080).start do |http|
request = Net::HTTP::Post.new('/rails-master-hook')
response = http.request(request)
puts response.body
diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG
deleted file mode 100644
index 14c887eb53..0000000000
--- a/actionmailer/CHANGELOG
+++ /dev/null
@@ -1,449 +0,0 @@
-*Rails 3.1.0 (August 30, 2011)*
-
-* No changes
-
-
-*Rails 3.0.7 (April 18, 2011)*
-
-* remove AM delegating register_observer and register_interceptor to Mail [Josh Kalderimis]
-
-
-*Rails 3.0.6 (April 5, 2011)
-
-* Don't allow i18n to change the minor version, version now set to ~> 0.5.0 [Santiago Pastorino]
-
-
-*Rails 3.0.5 (February 26, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.4 (February 8, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.3 (November 16, 2010)*
-
-* No changes.
-
-
-*Rails 3.0.2 (November 15, 2010)*
-
-* No changes
-
-
-*Rails 3.0.1 (October 15, 2010)*
-
-* No Changes, just a version bump.
-
-
-*Rails 3.0.0 (August 29, 2010)*
-
-* subject is automatically looked up on I18n using mailer_name and action_name as scope as in t(".subject") [JK]
-
-* Changed encoding behaviour of mail, so updated tests in actionmailer and bumped mail version to 2.2.1 [ML]
-
-* Added ability to pass Proc objects to the defaults hash [ML]
-
-* Removed all quoting.rb type files from ActionMailer and put Mail 2.2.0 in instead [ML]
-
-* Lot of updates to various test cases that now work better with the new Mail and so have different expectations
-
-* Added interceptors and observers from Mail [ML]
-
- ActionMailer::Base.register_interceptor calls Mail.register_interceptor
- ActionMailer::Base.register_observer calls Mail.register_observer
-
-* Mail::Part now no longer has nil as a default charset, it is always set to something, and defaults to UTF-8
-
-* Added explict setting of charset in set_fields! method to make sure Mail has the user defined default
-
-* Removed quoting.rb and refactored for Mail to take responsibility of all quoting and auto encoding requirements for the header.
-
-* Fixed several tests which had incorrect encoding.
-
-* Changed all utf-8 to UTF-8 for consistency
-
-* Whole new API added with tests. See base.rb for full details. Old API is deprecated.
-
-* The Mail::Message class has helped methods for all the field types that return 'common' defaults for the common use case, so to get the subject, mail.subject will give you a string, mail.date will give you a DateTime object, mail.from will give you an array of address specs (mikel@test.lindsaar.net) etc. If you want to access the field object itself, call mail[:field_name] which will return the field object you want, which you can then chain, like mail[:from].formatted
-
-* Mail#content_type now returns the content_type field as a string. If you want the mime type of a mail, then you call Mail#mime_type (eg, text/plain), if you want the parameters of the content type field, you call Mail#content_type_parameters which gives you a hash, eg {'format' => 'flowed', 'charset' => 'utf-8'}
-
-* ActionMailer::Base :default_implicit_parts_order now is in the sequence of the order you want, no reversing of ordering takes place. The default order now is text/plain, then text/enriched, then text/html and then any other part that is not one of these three.
-
-* Mail does not have "quoted_body", "quoted_subject" etc. All of these are accessed via body.encoded, subject.encoded etc
-
-* Every object in a Mail object returns an object, never a string. So Mail.body returns a Mail::Body class object, need to call #encoded or #decoded to get the string you want.
-* Mail::Message#set_content_type does not exist, it is simply Mail::Message#content_type
-
-* Every mail message gets a unique message_id unless you specify one, had to change all the tests that check for equality with expected.encoded == actual.encoded to first replace their message_ids with control values
-
-* Mail now has a proper concept of parts, remove the ActionMailer::Part and ActionMailer::PartContainer classes
-
-* Calling #encoded on any object returns it as a string ready to go into the output stream of an email, this means it includes the \r\n at the end of the lines and the object is pre-wrapped with \r\n\t if it is a header field. Also, the "encoded" value includes the field name if it is a header field.
-
-* Attachments are only the actual attachment, with filename etc. A part contains an attachment. The part has the content_type etc. So attachments.last.content_type is invalid. But parts.last.content_type
-
-* There is no idea of a "sub_head" in Mail. A part is just a Message with some extra functionality, so it just has a "header" like a normal mail message
-
-
-*2.3.2 [Final] (March 15, 2009)*
-
-* Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 [Matt Jones]
-
-* Fixed RFC-2045 quoted-printable bug #1421 [squadette]
-
-* Fixed that no body charset would be set when there are attachments present #740 [Paweł Kondzior]
-
-
-*2.2.1 [RC2] (November 14th, 2008)*
-
-* Turn on STARTTLS if it is available in Net::SMTP (added in Ruby 1.8.7) and the SMTP server supports it (This is required for Gmail's SMTP server) #1336 [Grant Hollingworth]
-
-
-*2.2.0 [RC1] (October 24th, 2008)*
-
-* Add layout functionality to mailers [Pratik Naik]
-
- Mailer layouts behaves just like controller layouts, except layout names need to
- have '_mailer' postfix for them to be automatically picked up.
-
-
-*2.1.0 (May 31st, 2008)*
-
-* Fixed that a return-path header would be ignored #7572 [joost]
-
-* Less verbose mail logging: just recipients for :info log level; the whole email for :debug only. #8000 [iaddict, Tarmo Tänav]
-
-* Updated TMail to version 1.2.1 [Mikel Lindsaar]
-
-* Fixed that you don't have to call super in ActionMailer::TestCase#setup #10406 [jamesgolick]
-
-
-*2.0.2* (December 16th, 2007)
-
-* Included in Rails 2.0.2
-
-
-*2.0.1* (December 7th, 2007)
-
-* Update ActionMailer so it treats ActionView the same way that ActionController does. Closes #10244 [Rick Olson]
-
- * Pass the template_root as an array as ActionView's view_path
- * Request templates with the "#{mailer_name}/#{action}" as opposed to just "#{action}"
-
-* Fixed that partials would be broken when using text.plain.erb as the extension #10130 [java]
-
-* Update README to use new smtp settings configuration API. Closes #10060 [psq]
-
-* Allow ActionMailer subclasses to individually set their delivery method (so two subclasses can have different delivery methods) #10033 [Zach Dennis]
-
-* Update TMail to v1.1.0. Use an updated version of TMail if available. [Mikel Lindsaar]
-
-* Introduce a new base test class for testing Mailers. ActionMailer::TestCase [Michael Koziarski]
-
-* Fix silent failure of rxml templates. #9879 [jstewart]
-
-* Fix attachment decoding when using the TMail C extension. #7861 [orangechicken]
-
-* Increase mail delivery test coverage. #8692 [Kamal Fariz Mahyuddin]
-
-* Register alternative template engines using ActionMailer::Base.register_template_extension('haml'). #7534 [cwd, Josh Peek]
-
-* Only load ActionController::UrlWriter if ActionController is present [Rick Olson]
-
-* Make sure parsed emails recognized attachments nested inside multipart parts. #6714 [Jamis Buck]
-
-* Allow mailer actions named send by using __send__ internally. #6467 [iGEL]
-
-* Add assert_emails and assert_no_emails to test the number of emails delivered. #6479 [Jonathan Viney]
- # Assert total number of emails delivered:
- assert_emails 0
- ContactMailer.deliver_contact
- assert_emails 1
-
- # Assert number of emails delivered within a block:
- assert_emails 1 do
- post :signup, :name => 'Jonathan'
- end
-
-
-*1.3.3* (March 12th, 2007)
-
-* Depend on Action Pack 1.13.3
-
-
-*1.3.2* (February 5th, 2007)
-
-* Deprecate server_settings renaming it to smtp_settings, add sendmail_settings to allow you to override the arguments to and location of the sendmail executable. [Michael Koziarski]
-
-
-*1.3.1* (January 16th, 2007)
-
-* Depend on Action Pack 1.13.1
-
-
-*1.3.0* (January 16th, 2007)
-
-* Make mime version default to 1.0. closes #2323 [ror@andreas-s.net]
-
-* Make sure quoted-printable text is decoded correctly when only portions of the text are encoded. closes #3154. [jon@siliconcircus.com]
-
-* Make sure DOS newlines in quoted-printable text are normalized to unix newlines before unquoting. closes #4166 and #4452. [Jamis Buck]
-
-* Fixed that iconv decoding should catch InvalidEncoding #3153 [jon@siliconcircus.com]
-
-* Tighten rescue clauses. #5985 [james@grayproductions.net]
-
-* Automatically included ActionController::UrlWriter, such that URL generation can happen within ActionMailer controllers. [David Heinemeier Hansson]
-
-* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
-
-* Mailer template root applies to a class and its subclasses rather than acting globally. #5555 [somekool@gmail.com]
-
-* Resolve action naming collision. #5520 [ssinghi@kreeti.com]
-
-* ActionMailer::Base documentation rewrite. Closes #4991 [Kevin Clark, Marcel Molina Jr.]
-
-* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
-
-* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
-
-* Correct spurious documentation example code which results in a SyntaxError. [Marcel Molina Jr.]
-
-
-*1.2.1* (April 6th, 2006)
-
-* Be part of Rails 1.1.1
-
-
-*1.2.0* (March 27th, 2006)
-
-* Nil charset caused subject line to be improperly quoted in implicitly multipart messages #2662 [ehalvorsen+rails@runbox.com]
-
-* Parse content-type apart before using it so that sub-parts of the header can be set correctly #2918 [Jamis Buck]
-
-* Make custom headers work in subparts #4034 [elan@bluemandrill.com]
-
-* Template paths with dot chars in them no longer mess up implicit template selection for multipart messages #3332 [Chad Fowler]
-
-* Make sure anything with content-disposition of "attachment" is passed to the attachment presenter when parsing an email body [Jamis Buck]
-
-* Make sure TMail#attachments includes anything with content-disposition of "attachment", regardless of content-type [Jamis Buck]
-
-
-*1.1.5* (December 13th, 2005)
-
-* Become part of Rails 1.0
-
-
-*1.1.4* (December 7th, 2005)
-
-* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
-
-* Stricter matching for implicitly multipart filenames excludes files ending in unsupported extensions (such as foo.rhtml.bak) and without a two-part content type (such as foo.text.rhtml or foo.text.really.plain.rhtml). #2398 [Dave Burt <dave@burt.id.au>, Jeremy Kemper]
-
-
-*1.1.3* (November 7th, 2005)
-
-* Allow Mailers to have custom initialize methods that set default instance variables for all mail actions #2563 [mrj@bigpond.net.au]
-
-
-*1.1.2* (October 26th, 2005)
-
-* Upgraded to Action Pack 1.10.2
-
-
-*1.1.1* (October 19th, 2005)
-
-* Upgraded to Action Pack 1.10.1
-
-
-*1.1.0* (October 16th, 2005)
-
-* Update and extend documentation (rdoc)
-
-* Minero Aoki made TMail available to Rails/ActionMailer under the MIT license (instead of LGPL) [RubyConf '05]
-
-* Austin Ziegler made Text::Simple available to Rails/ActionMailer under a MIT-like licens [See rails ML, subject "Text::Format Licence Exception" on Oct 15, 2005]
-
-* Fix vendor require paths to prevent files being required twice
-
-* Don't add charset to content-type header for a part that contains subparts (for AOL compatibility) #2013 [John Long]
-
-* Preserve underscores when unquoting message bodies #1930
-
-* Encode multibyte characters correctly #1894
-
-* Multipart messages specify a MIME-Version header automatically #2003 [John Long]
-
-* Add a unified render method to ActionMailer (delegates to ActionView::Base#render)
-
-* Move mailer initialization to a separate (overridable) method, so that subclasses may alter the various defaults #1727
-
-* Look at content-location header (if available) to determine filename of attachments #1670
-
-* ActionMailer::Base.deliver(email) had been accidentally removed, but was documented in the Rails book #1849
-
-* Fix problem with sendmail delivery where headers should be delimited by \n characters instead of \r\n, which confuses some mail readers #1742 [Kent Sibilev]
-
-
-*1.0.1* (11 July, 2005)
-
-* Bind to Action Pack 1.9.1
-
-
-*1.0.0* (6 July, 2005)
-
-* Avoid adding nil header values #1392
-
-* Better multipart support with implicit multipart/alternative and sorting of subparts [John Long]
-
-* Allow for nested parts in multipart mails #1570 [Flurin Egger]
-
-* Normalize line endings in outgoing mail bodies to "\n" #1536 [John Long]
-
-* Allow template to be explicitly specified #1448 [tuxie@dekadance.se]
-
-* Allow specific "multipart/xxx" content-type to be set on multipart messages #1412 [Flurin Egger]
-
-* Unquoted @ characters in headers are now accepted in spite of RFC 822 #1206
-
-* Helper support (borrowed from ActionPack)
-
-* Silently ignore Errno::EINVAL errors when converting text.
-
-* Don't cause an error when parsing an encoded attachment name #1340 [lon@speedymac.com]
-
-* Nested multipart message parts are correctly processed in TMail::Mail#body
-
-* BCC headers are removed when sending via SMTP #1402
-
-* Added 'content_type' accessor, to allow content type to be set on a per-message basis. content_type defaults to "text/plain".
-
-* Silently ignore Iconv::IllegalSequence errors when converting text #1341 [lon@speedymac.com]
-
-* Support attachments and multipart messages.
-
-* Added new accessors for the various mail properties.
-
-* Fix to only perform the charset conversion if a 'from' and a 'to' charset are given (make no assumptions about what the charset was) #1276 [Jamis Buck]
-
-* Fix attachments and content-type problems #1276 [Jamis Buck]
-
-* Fixed the TMail#body method to look at the content-transfer-encoding header and unquote the body according to the rules it specifies #1265 [Jamis Buck]
-
-* Added unquoting even if the iconv lib can't be loaded--in that case, only the charset conversion is skipped #1265 [Jamis Buck]
-
-* Added automatic decoding of base64 bodies #1214 [Jamis Buck]
-
-* Added that delivery errors are caught in a way so the mail is still returned whether the delivery was successful or not
-
-* Fixed that email address like "Jamis Buck, M.D." <wild.medicine@example.net> would cause the quoter to generate emails resulting in "bad address" errors from the mail server #1220 [Jamis Buck]
-
-
-*0.9.1* (20th April, 2005)
-
-* Depend on Action Pack 1.8.1
-
-
-*0.9.0* (19th April, 2005)
-
-* Added that deliver_* will now return the email that was sent
-
-* Added that quoting to UTF-8 only happens if the characters used are in that range #955 [Jamis Buck]
-
-* Fixed quoting for all address headers, not just to #955 [Jamis Buck]
-
-* Fixed unquoting of emails that doesn't have an explicit charset #1036 [wolfgang@stufenlos.net]
-
-
-*0.8.1* (27th March, 2005)
-
-* Fixed that if charset was found that the end of a mime part declaration TMail would throw an error #919 [lon@speedymac.com]
-
-* Fixed that TMail::Unquoter would fail to recognize quoting method if it was in lowercase #919 [lon@speedymac.com]
-
-* Fixed that TMail::Encoder would fail when it attempts to parse e-mail addresses which are encoded using something other than the messages encoding method #919 [lon@speedymac.com]
-
-* Added rescue for missing iconv library and throws warnings if subject/body is called on a TMail object without it instead
-
-
-*0.8.0* (22th March, 2005)
-
-* Added framework support for processing incoming emails with an Action Mailer class. See example in README.
-
-
-*0.7.1* (7th March, 2005)
-
-* Bind to newest Action Pack (1.5.1)
-
-
-*0.7.0* (24th February, 2005)
-
-* Added support for charsets for both subject and body. The default charset is now UTF-8 #673 [Jamis Buck]. Examples:
-
- def iso_charset(recipient)
- @recipients = recipient
- @subject = "testing iso charsets"
- @from = "system@loudthinking.com"
- @body = "Nothing to see here."
- @charset = "iso-8859-1"
- end
-
- def unencoded_subject(recipient)
- @recipients = recipient
- @subject = "testing unencoded subject"
- @from = "system@loudthinking.com"
- @body = "Nothing to see here."
- @encode_subject = false
- @charset = "iso-8859-1"
- end
-
-
-*0.6.1* (January 18th, 2005)
-
-* Fixed sending of emails to use Tmail#from not the deprecated Tmail#from_address
-
-
-*0.6* (January 17th, 2005)
-
-* Fixed that bcc and cc should be settable through @bcc and @cc -- not just @headers["Bcc"] and @headers["Cc"] #453 [Eric Hodel]
-
-* Fixed Action Mailer to be "warnings safe" so you can run with ruby -w and not get framework warnings #453 [Eric Hodel]
-
-
-*0.5*
-
-* Added access to custom headers, like cc, bcc, and reply-to #268 [Andreas Schwarz]. Example:
-
- def post_notification(recipients, post)
- @recipients = recipients
- @from = post.author.email_address_with_name
- @headers["bcc"] = SYSTEM_ADMINISTRATOR_EMAIL
- @headers["reply-to"] = "notifications@example.com"
- @subject = "[#{post.account.name} #{post.title}]"
- @body["post"] = post
- end
-
-*0.4* (5)
-
-* Consolidated the server configuration options into Base#server_settings= and expanded that with controls for authentication and more [Marten]
- NOTE: This is an API change that could potentially break your application if you used the old application form. Please do change!
-
-* Added Base#deliveries as an accessor for an array of emails sent out through that ActionMailer class when using the :test delivery option. [Jeremy Kemper]
-
-* Added Base#perform_deliveries= which can be set to false to turn off the actual delivery of the email through smtp or sendmail.
- This is especially useful for functional testing that shouldn't send off real emails, but still trigger delivery_* methods.
-
-* Added option to specify delivery method with Base#delivery_method=. Default is :smtp and :sendmail is currently the only other option.
- Sendmail is assumed to be present at "/usr/sbin/sendmail" if that option is used. [Kent Sibilev]
-
-* Dropped "include TMail" as it added to much baggage into the default namespace (like Version) [Chad Fowler]
-
-
-*0.3*
-
-* First release
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md
new file mode 100644
index 0000000000..a33d6e072b
--- /dev/null
+++ b/actionmailer/CHANGELOG.md
@@ -0,0 +1,507 @@
+## Rails 3.2.3 (March 30, 2012) ##
+
+* Upgrade mail version to 2.4.3 *ML*
+
+
+## Rails 3.2.2 (March 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* Upgrade mail version to 2.4.0 *ML*
+
+* Remove Old ActionMailer API *Josh Kalderimis*
+
+
+## Rails 3.1.3 (November 20, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.2 (November 18, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.1 (October 7, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.0 (August 30, 2011) ##
+
+* No changes
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* Mail dependency increased to 2.2.19
+
+
+## Rails 3.0.7 (April 18, 2011) ##
+
+* remove AM delegating register_observer and register_interceptor to Mail *Josh Kalderimis*
+
+
+## Rails 3.0.6 (April 5, 2011) ##
+
+* Don't allow i18n to change the minor version, version now set to ~> 0.5.0 *Santiago Pastorino*
+
+
+## Rails 3.0.5 (February 26, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.4 (February 8, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.3 (November 16, 2010) ##
+
+* No changes.
+
+
+## Rails 3.0.2 (November 15, 2010) ##
+
+* No changes
+
+
+## Rails 3.0.1 (October 15, 2010) ##
+
+* No Changes, just a version bump.
+
+
+## Rails 3.0.0 (August 29, 2010) ##
+
+* subject is automatically looked up on I18n using mailer_name and action_name as scope as in t(".subject") *JK*
+
+* Changed encoding behaviour of mail, so updated tests in actionmailer and bumped mail version to 2.2.1 *ML*
+
+* Added ability to pass Proc objects to the defaults hash *ML*
+
+* Removed all quoting.rb type files from ActionMailer and put Mail 2.2.0 in instead *ML*
+
+* Lot of updates to various test cases that now work better with the new Mail and so have different expectations
+
+* Added interceptors and observers from Mail *ML*
+
+ ActionMailer::Base.register_interceptor calls Mail.register_interceptor
+ ActionMailer::Base.register_observer calls Mail.register_observer
+
+* Mail::Part now no longer has nil as a default charset, it is always set to something, and defaults to UTF-8
+
+* Added explict setting of charset in set_fields! method to make sure Mail has the user defined default
+
+* Removed quoting.rb and refactored for Mail to take responsibility of all quoting and auto encoding requirements for the header.
+
+* Fixed several tests which had incorrect encoding.
+
+* Changed all utf-8 to UTF-8 for consistency
+
+* Whole new API added with tests. See base.rb for full details. Old API is deprecated.
+
+* The Mail::Message class has helped methods for all the field types that return 'common' defaults for the common use case, so to get the subject, mail.subject will give you a string, mail.date will give you a DateTime object, mail.from will give you an array of address specs (mikel@test.lindsaar.net) etc. If you want to access the field object itself, call mail[:field_name] which will return the field object you want, which you can then chain, like mail[:from].formatted
+
+* Mail#content_type now returns the content_type field as a string. If you want the mime type of a mail, then you call Mail#mime_type (eg, text/plain), if you want the parameters of the content type field, you call Mail#content_type_parameters which gives you a hash, eg {'format' => 'flowed', 'charset' => 'utf-8'}
+
+* ActionMailer::Base :default_implicit_parts_order now is in the sequence of the order you want, no reversing of ordering takes place. The default order now is text/plain, then text/enriched, then text/html and then any other part that is not one of these three.
+
+* Mail does not have "quoted_body", "quoted_subject" etc. All of these are accessed via body.encoded, subject.encoded etc
+
+* Every object in a Mail object returns an object, never a string. So Mail.body returns a Mail::Body class object, need to call #encoded or #decoded to get the string you want.
+
+* Mail::Message#set_content_type does not exist, it is simply Mail::Message#content_type
+
+* Every mail message gets a unique message_id unless you specify one, had to change all the tests that check for equality with expected.encoded == actual.encoded to first replace their message_ids with control values
+
+* Mail now has a proper concept of parts, remove the ActionMailer::Part and ActionMailer::PartContainer classes
+
+* Calling #encoded on any object returns it as a string ready to go into the output stream of an email, this means it includes the \r\n at the end of the lines and the object is pre-wrapped with \r\n\t if it is a header field. Also, the "encoded" value includes the field name if it is a header field.
+
+* Attachments are only the actual attachment, with filename etc. A part contains an attachment. The part has the content_type etc. So attachments.last.content_type is invalid. But parts.last.content_type
+
+* There is no idea of a "sub_head" in Mail. A part is just a Message with some extra functionality, so it just has a "header" like a normal mail message
+
+
+## 2.3.2 Final (March 15, 2009) ##
+
+* Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 *Matt Jones*
+
+* Fixed RFC-2045 quoted-printable bug #1421 *squadette*
+
+* Fixed that no body charset would be set when there are attachments present #740 *Paweł Kondzior*
+
+
+## 2.2.1 RC2 (November 14th, 2008) ##
+
+* Turn on STARTTLS if it is available in Net::SMTP (added in Ruby 1.8.7) and the SMTP server supports it (This is required for Gmail's SMTP server) #1336 *Grant Hollingworth*
+
+
+## 2.2.0 RC1 (October 24th, 2008) ##
+
+* Add layout functionality to mailers *Pratik Naik*
+
+ Mailer layouts behaves just like controller layouts, except layout names need to
+ have '_mailer' postfix for them to be automatically picked up.
+
+
+## 2.1.0 (May 31st, 2008) ##
+
+* Fixed that a return-path header would be ignored #7572 *joost*
+
+* Less verbose mail logging: just recipients for :info log level; the whole email for :debug only. #8000 *iaddict, Tarmo Tänav*
+
+* Updated TMail to version 1.2.1 *Mikel Lindsaar*
+
+* Fixed that you don't have to call super in ActionMailer::TestCase#setup #10406 *jamesgolick*
+
+
+## 2.0.2 (December 16th, 2007) ##
+
+* Included in Rails 2.0.2
+
+
+## 2.0.1 (December 7th, 2007) ##
+
+* Update ActionMailer so it treats ActionView the same way that ActionController does. Closes #10244 *Rick Olson*
+
+ * Pass the template_root as an array as ActionView's view_path
+ * Request templates with the "#{mailer_name}/#{action}" as opposed to just "#{action}"
+
+* Fixed that partials would be broken when using text.plain.erb as the extension #10130 *java*
+
+* Update README to use new smtp settings configuration API. Closes #10060 *psq*
+
+* Allow ActionMailer subclasses to individually set their delivery method (so two subclasses can have different delivery methods) #10033 *Zach Dennis*
+
+* Update TMail to v1.1.0. Use an updated version of TMail if available. *Mikel Lindsaar*
+
+* Introduce a new base test class for testing Mailers. ActionMailer::TestCase *Michael Koziarski*
+
+* Fix silent failure of rxml templates. #9879 *jstewart*
+
+* Fix attachment decoding when using the TMail C extension. #7861 *orangechicken*
+
+* Increase mail delivery test coverage. #8692 *Kamal Fariz Mahyuddin*
+
+* Register alternative template engines using ActionMailer::Base.register_template_extension('haml'). #7534 *cwd, Josh Peek*
+
+* Only load ActionController::UrlWriter if ActionController is present *Rick Olson*
+
+* Make sure parsed emails recognized attachments nested inside multipart parts. #6714 *Jamis Buck*
+
+* Allow mailer actions named send by using __send__ internally. #6467 *iGEL*
+
+* Add assert_emails and assert_no_emails to test the number of emails delivered. #6479 *Jonathan Viney*
+ # Assert total number of emails delivered:
+ assert_emails 0
+ ContactMailer.deliver_contact
+ assert_emails 1
+
+ # Assert number of emails delivered within a block:
+ assert_emails 1 do
+ post :signup, :name => 'Jonathan'
+ end
+
+
+## 1.3.3 (March 12th, 2007) ##
+
+* Depend on Action Pack 1.13.3
+
+
+## 1.3.2 (February 5th, 2007) ##
+
+* Deprecate server_settings renaming it to smtp_settings, add sendmail_settings to allow you to override the arguments to and location of the sendmail executable. *Michael Koziarski*
+
+
+## 1.3.1 (January 16th, 2007) ##
+
+* Depend on Action Pack 1.13.1
+
+
+## 1.3.0 (January 16th, 2007) ##
+
+* Make mime version default to 1.0. closes #2323 *ror@andreas-s.net*
+
+* Make sure quoted-printable text is decoded correctly when only portions of the text are encoded. closes #3154. *jon@siliconcircus.com*
+
+* Make sure DOS newlines in quoted-printable text are normalized to unix newlines before unquoting. closes #4166 and #4452. *Jamis Buck*
+
+* Fixed that iconv decoding should catch InvalidEncoding #3153 *jon@siliconcircus.com*
+
+* Tighten rescue clauses. #5985 *james@grayproductions.net*
+
+* Automatically included ActionController::UrlWriter, such that URL generation can happen within ActionMailer controllers. *David Heinemeier Hansson*
+
+* Replace Reloadable with Reloadable::Deprecated. *Nicholas Seckar*
+
+* Mailer template root applies to a class and its subclasses rather than acting globally. #5555 *somekool@gmail.com*
+
+* Resolve action naming collision. #5520 *ssinghi@kreeti.com*
+
+* ActionMailer::Base documentation rewrite. Closes #4991 *Kevin Clark, Marcel Molina Jr.*
+
+* Replace alias method chaining with Module#alias_method_chain. *Marcel Molina Jr.*
+
+* Replace Ruby's deprecated append_features in favor of included. *Marcel Molina Jr.*
+
+* Correct spurious documentation example code which results in a SyntaxError. *Marcel Molina Jr.*
+
+
+## 1.2.1 (April 6th, 2006) ##
+
+* Be part of Rails 1.1.1
+
+
+## 1.2.0 (March 27th, 2006) ##
+
+* Nil charset caused subject line to be improperly quoted in implicitly multipart messages #2662 *ehalvorsen+rails@runbox.com*
+
+* Parse content-type apart before using it so that sub-parts of the header can be set correctly #2918 *Jamis Buck*
+
+* Make custom headers work in subparts #4034 *elan@bluemandrill.com*
+
+* Template paths with dot chars in them no longer mess up implicit template selection for multipart messages #3332 *Chad Fowler*
+
+* Make sure anything with content-disposition of "attachment" is passed to the attachment presenter when parsing an email body *Jamis Buck*
+
+* Make sure TMail#attachments includes anything with content-disposition of "attachment", regardless of content-type *Jamis Buck*
+
+
+## 1.1.5 (December 13th, 2005) ##
+
+* Become part of Rails 1.0
+
+
+## 1.1.4 (December 7th, 2005) ##
+
+* Rename Version constant to VERSION. #2802 *Marcel Molina Jr.*
+
+* Stricter matching for implicitly multipart filenames excludes files ending in unsupported extensions (such as foo.rhtml.bak) and without a two-part content type (such as foo.text.rhtml or foo.text.really.plain.rhtml). #2398 *Dave Burt <dave@burt.id.au>, Jeremy Kemper*
+
+
+## 1.1.3 (November 7th, 2005) ##
+
+* Allow Mailers to have custom initialize methods that set default instance variables for all mail actions #2563 *mrj@bigpond.net.au*
+
+
+## 1.1.2 (October 26th, 2005) ##
+
+* Upgraded to Action Pack 1.10.2
+
+
+## 1.1.1 (October 19th, 2005) ##
+
+* Upgraded to Action Pack 1.10.1
+
+
+## 1.1.0 (October 16th, 2005) ##
+
+* Update and extend documentation (rdoc)
+
+* Minero Aoki made TMail available to Rails/ActionMailer under the MIT license (instead of LGPL) *RubyConf '05*
+
+* Austin Ziegler made Text::Simple available to Rails/ActionMailer under a MIT-like licens *See rails ML, subject "Text::Format Licence Exception" on Oct 15, 2005*
+
+* Fix vendor require paths to prevent files being required twice
+
+* Don't add charset to content-type header for a part that contains subparts (for AOL compatibility) #2013 *John Long*
+
+* Preserve underscores when unquoting message bodies #1930
+
+* Encode multibyte characters correctly #1894
+
+* Multipart messages specify a MIME-Version header automatically #2003 *John Long*
+
+* Add a unified render method to ActionMailer (delegates to ActionView::Base#render)
+
+* Move mailer initialization to a separate (overridable) method, so that subclasses may alter the various defaults #1727
+
+* Look at content-location header (if available) to determine filename of attachments #1670
+
+* ActionMailer::Base.deliver(email) had been accidentally removed, but was documented in the Rails book #1849
+
+* Fix problem with sendmail delivery where headers should be delimited by \n characters instead of \r\n, which confuses some mail readers #1742 *Kent Sibilev*
+
+
+## 1.0.1 (11 July, 2005) ##
+
+* Bind to Action Pack 1.9.1
+
+
+## 1.0.0 (6 July, 2005) ##
+
+* Avoid adding nil header values #1392
+
+* Better multipart support with implicit multipart/alternative and sorting of subparts *John Long*
+
+* Allow for nested parts in multipart mails #1570 *Flurin Egger*
+
+* Normalize line endings in outgoing mail bodies to "\n" #1536 *John Long*
+
+* Allow template to be explicitly specified #1448 *tuxie@dekadance.se*
+
+* Allow specific "multipart/xxx" content-type to be set on multipart messages #1412 *Flurin Egger*
+
+* Unquoted @ characters in headers are now accepted in spite of RFC 822 #1206
+
+* Helper support (borrowed from ActionPack)
+
+* Silently ignore Errno::EINVAL errors when converting text.
+
+* Don't cause an error when parsing an encoded attachment name #1340 *lon@speedymac.com*
+
+* Nested multipart message parts are correctly processed in TMail::Mail#body
+
+* BCC headers are removed when sending via SMTP #1402
+
+* Added 'content_type' accessor, to allow content type to be set on a per-message basis. content_type defaults to "text/plain".
+
+* Silently ignore Iconv::IllegalSequence errors when converting text #1341 *lon@speedymac.com*
+
+* Support attachments and multipart messages.
+
+* Added new accessors for the various mail properties.
+
+* Fix to only perform the charset conversion if a 'from' and a 'to' charset are given (make no assumptions about what the charset was) #1276 *Jamis Buck*
+
+* Fix attachments and content-type problems #1276 *Jamis Buck*
+
+* Fixed the TMail#body method to look at the content-transfer-encoding header and unquote the body according to the rules it specifies #1265 *Jamis Buck*
+
+* Added unquoting even if the iconv lib can't be loaded--in that case, only the charset conversion is skipped #1265 *Jamis Buck*
+
+* Added automatic decoding of base64 bodies #1214 *Jamis Buck*
+
+* Added that delivery errors are caught in a way so the mail is still returned whether the delivery was successful or not
+
+* Fixed that email address like "Jamis Buck, M.D." <wild.medicine@example.net> would cause the quoter to generate emails resulting in "bad address" errors from the mail server #1220 *Jamis Buck*
+
+
+## 0.9.1 (20th April, 2005) ##
+
+* Depend on Action Pack 1.8.1
+
+
+## 0.9.0 (19th April, 2005) ##
+
+* Added that deliver_* will now return the email that was sent
+
+* Added that quoting to UTF-8 only happens if the characters used are in that range #955 *Jamis Buck*
+
+* Fixed quoting for all address headers, not just to #955 *Jamis Buck*
+
+* Fixed unquoting of emails that doesn't have an explicit charset #1036 *wolfgang@stufenlos.net*
+
+
+## 0.8.1 (27th March, 2005) ##
+
+* Fixed that if charset was found that the end of a mime part declaration TMail would throw an error #919 *lon@speedymac.com*
+
+* Fixed that TMail::Unquoter would fail to recognize quoting method if it was in lowercase #919 *lon@speedymac.com*
+
+* Fixed that TMail::Encoder would fail when it attempts to parse e-mail addresses which are encoded using something other than the messages encoding method #919 *lon@speedymac.com*
+
+* Added rescue for missing iconv library and throws warnings if subject/body is called on a TMail object without it instead
+
+
+## 0.8.0 (22th March, 2005) ##
+
+* Added framework support for processing incoming emails with an Action Mailer class. See example in README.
+
+
+## 0.7.1 (7th March, 2005) ##
+
+* Bind to newest Action Pack (1.5.1)
+
+
+## 0.7.0 (24th February, 2005) ##
+
+* Added support for charsets for both subject and body. The default charset is now UTF-8 #673 [Jamis Buck]. Examples:
+
+ def iso_charset(recipient)
+ @recipients = recipient
+ @subject = "testing iso charsets"
+ @from = "system@loudthinking.com"
+ @body = "Nothing to see here."
+ @charset = "iso-8859-1"
+ end
+
+ def unencoded_subject(recipient)
+ @recipients = recipient
+ @subject = "testing unencoded subject"
+ @from = "system@loudthinking.com"
+ @body = "Nothing to see here."
+ @encode_subject = false
+ @charset = "iso-8859-1"
+ end
+
+
+## 0.6.1 (January 18th, 2005) ##
+
+* Fixed sending of emails to use Tmail#from not the deprecated Tmail#from_address
+
+
+## 0.6 (January 17th, 2005) ##
+
+* Fixed that bcc and cc should be settable through @bcc and @cc -- not just @headers["Bcc"] and @headers["Cc"] #453 *Eric Hodel*
+
+* Fixed Action Mailer to be "warnings safe" so you can run with ruby -w and not get framework warnings #453 *Eric Hodel*
+
+
+## 0.5 ##
+
+* Added access to custom headers, like cc, bcc, and reply-to #268 [Andreas Schwarz]. Example:
+
+ def post_notification(recipients, post)
+ @recipients = recipients
+ @from = post.author.email_address_with_name
+ @headers["bcc"] = SYSTEM_ADMINISTRATOR_EMAIL
+ @headers["reply-to"] = "notifications@example.com"
+ @subject = "[#{post.account.name} #{post.title}]"
+ @body["post"] = post
+ end
+
+## 0.4 (5) ##
+
+* Consolidated the server configuration options into Base#server_settings= and expanded that with controls for authentication and more *Marten*
+ NOTE: This is an API change that could potentially break your application if you used the old application form. Please do change!
+
+* Added Base#deliveries as an accessor for an array of emails sent out through that ActionMailer class when using the :test delivery option. *Jeremy Kemper*
+
+* Added Base#perform_deliveries= which can be set to false to turn off the actual delivery of the email through smtp or sendmail.
+ This is especially useful for functional testing that shouldn't send off real emails, but still trigger delivery_* methods.
+
+* Added option to specify delivery method with Base#delivery_method=. Default is :smtp and :sendmail is currently the only other option.
+ Sendmail is assumed to be present at "/usr/sbin/sendmail" if that option is used. *Kent Sibilev*
+
+* Dropped "include TMail" as it added to much baggage into the default namespace (like Version) *Chad Fowler*
+
+
+## 0.3 ##
+
+* First release
diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE
index 7ad1051066..810daf856c 100644
--- a/actionmailer/MIT-LICENSE
+++ b/actionmailer/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2011 David Heinemeier Hansson
+Copyright (c) 2004-2012 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc
index dc74b590f7..cf10bfffdb 100644
--- a/actionmailer/README.rdoc
+++ b/actionmailer/README.rdoc
@@ -22,12 +22,12 @@ the email.
This can be as simple as:
class Notifier < ActionMailer::Base
- delivers_from 'system@loudthinking.com'
+ default from: 'system@loudthinking.com'
def welcome(recipient)
@recipient = recipient
- mail(:to => recipient,
- :subject => "[Signed up] Welcome #{recipient}")
+ mail(to: recipient,
+ subject: "[Signed up] Welcome #{recipient}")
end
end
@@ -42,7 +42,7 @@ So the corresponding body template for the method above could look like this:
Thank you for signing up!
-And if the recipient was given as "david@loudthinking.com", the email
+If the recipient was given as "david@loudthinking.com", the email
generated would look like this:
Date: Mon, 25 Jan 2010 22:48:09 +1100
@@ -62,7 +62,7 @@ generated would look like this:
Thank you for signing up!
In previous version of Rails you would call <tt>create_method_name</tt> and
-<tt>deliver_method_name</tt>. Rails 3.0 has a much simpler interface, you
+<tt>deliver_method_name</tt>. Rails 3.0 has a much simpler interface - you
simply call the method and optionally call +deliver+ on the return value.
Calling the method returns a Mail Message object:
@@ -76,13 +76,13 @@ Or you can just chain the methods together like:
== 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 won't need to worry about that. Finally it is also possible to pass in a Proc that will get evaluated when it is needed.
+It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally, it is also possible to pass in a Proc that will get evaluated when it is needed.
Note that every value you set with this method will get over written if you use the same key in your mailer method.
Example:
- class Authenticationmailer < ActionMailer::Base
+ class AuthenticationMailer < ActionMailer::Base
default :from => "awesome@application.com", :subject => Proc.new { "E-mail was generated at #{Time.now}" }
.....
end
@@ -148,7 +148,9 @@ Source code can be downloaded as part of the Rails project on GitHub
== License
-Action Mailer is released under the MIT license.
+Action Mailer is released under the MIT license:
+
+* http://www.opensource.org/licenses/MIT
== Support
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index e7d8ee299d..c1c4171cdf 100755..100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rubygems/package_task'
@@ -11,6 +10,7 @@ Rake::TestTask.new { |t|
t.libs << "test"
t.pattern = 'test/**/*_test.rb'
t.warning = true
+ t.verbose = true
}
namespace :test do
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index d7f450f751..90503edc20 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -6,16 +6,16 @@ Gem::Specification.new do |s|
s.version = version
s.summary = 'Email composition, delivery, and receiving framework (part of Rails).'
s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.'
- s.required_ruby_version = '>= 1.8.7'
+ s.required_ruby_version = '>= 1.9.3'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
+ s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
s.requirements << 'none'
s.add_dependency('actionpack', version)
- s.add_dependency('mail', '~> 2.3.0')
+ s.add_dependency('mail', '~> 2.4.4')
end
diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 9bd73dd740..e45a1cd5ff 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2011 David Heinemeier Hansson
+# Copyright (c) 2004-2012 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -21,9 +21,6 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-actionpack_path = File.expand_path('../../../actionpack/lib', __FILE__)
-$:.unshift(actionpack_path) if File.directory?(actionpack_path) && !$:.include?(actionpack_path)
-
require 'abstract_controller'
require 'action_view'
require 'action_mailer/version'
@@ -31,7 +28,6 @@ require 'action_mailer/version'
# Common Active Support usage in Action Mailer
require 'active_support/core_ext/class'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/array/uniq_by'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/string/inflections'
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index ac49702ced..4f0cff0612 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -1,10 +1,9 @@
require 'mail'
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 'active_support/core_ext/module/anonymous'
require 'action_mailer/log_subscriber'
module ActionMailer #:nodoc:
@@ -16,15 +15,15 @@ module ActionMailer #:nodoc:
#
# $ rails generate mailer Notifier
#
- # The generated model inherits from <tt>ActionMailer::Base</tt>. Emails are defined by creating methods
- # within the model which are then used to set variables to be used in the mail template, to
- # change options on the mail, or to add attachments.
+ # The generated model inherits from <tt>ActionMailer::Base</tt>. A mailer model defines methods
+ # used to generate an email message. In these methods, you can setup variables to be used in
+ # the mailer views, options on the mail itself such as the <tt>:from</tt> address, and attachments.
#
# Examples:
#
# class Notifier < ActionMailer::Base
# default :from => 'no-reply@example.com',
- # :return_path => 'system@example.com'
+ # :return_path => 'system@example.com'
#
# def welcome(recipient)
# @account = recipient
@@ -122,8 +121,8 @@ module ActionMailer #:nodoc:
#
# <%= users_url(:host => "example.com") %>
#
- # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the
- # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will
+ # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the
+ # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will
# have no concept of a current URL from which to determine a relative path.
#
# It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt>
@@ -132,7 +131,7 @@ module ActionMailer #:nodoc:
# config.action_mailer.default_url_options = { :host => "example.com" }
#
# When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the
- # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper
+ # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper
# will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing
# <tt>:only_path => false</tt> will ensure that absolute URLs are generated.
#
@@ -149,8 +148,8 @@ module ActionMailer #:nodoc:
#
# = Multipart Emails
#
- # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
- # multipart templates, where each template is named after the name of the action, followed by the content
+ # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use
+ # multipart templates, where each template is named after the name of the action, followed by the content
# type. Each such detected template will be added as a separate part to the message.
#
# For example, if the following templates exist:
@@ -269,6 +268,33 @@ module ActionMailer #:nodoc:
# set something in the defaults using a proc, and then set the same thing inside of your
# mailer method, it will get over written by the mailer method.
#
+ # = Callbacks
+ #
+ # You can specify callbacks using before_filter and after_filter for configuring your messages.
+ # This may be useful, for example, when you want to add default inline attachments for all
+ # messages sent out by a certain mailer class:
+ #
+ # class Notifier < ActionMailer::Base
+ # before_filter :add_inline_attachment!
+ #
+ # def welcome
+ # mail
+ # end
+ #
+ # private
+ #
+ # def add_inline_attachment!
+ # attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
+ # end
+ # end
+ #
+ # Callbacks in ActionMailer are implemented using AbstractController::Callbacks, so you
+ # can define and configure callbacks in the same manner that you would use callbacks in
+ # classes that inherit from ActionController::Base.
+ #
+ # Note that unless you have a specific reason to do so, you should prefer using before_filter
+ # rather than after_filter in your ActionMailer classes so that headers are parsed properly.
+ #
# = Configuration options
#
# These options are specified on the class level, like
@@ -332,8 +358,9 @@ module ActionMailer #:nodoc:
include AbstractController::Helpers
include AbstractController::Translation
include AbstractController::AssetPaths
+ include AbstractController::Callbacks
- self.protected_instance_variables = %w(@_action_has_layout)
+ self.protected_instance_variables = [:@_action_has_layout]
helper ActionMailer::MailHelper
@@ -375,7 +402,7 @@ module ActionMailer #:nodoc:
end
def mailer_name
- @mailer_name ||= name.underscore
+ @mailer_name ||= anonymous? ? "anonymous" : name.underscore
end
attr_writer :mailer_name
alias :controller_path :mailer_name
@@ -410,7 +437,7 @@ module ActionMailer #:nodoc:
# and passing a Mail::Message will do nothing except tell the logger you sent the email.
def deliver_mail(mail) #:nodoc:
ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload|
- self.set_payload_for_mail(payload, mail)
+ set_payload_for_mail(payload, mail)
yield # Let Mail do the delivery actions
end
end
@@ -460,7 +487,7 @@ module ActionMailer #:nodoc:
self.class.mailer_name
end
- # Allows you to pass random and unusual headers to the new +Mail::Message+ object
+ # Allows you to pass random and unusual headers to the new <tt>Mail::Message</tt> object
# which will add them to itself.
#
# headers['X-Special-Domain-Specific-Header'] = "SecretValue"
@@ -471,7 +498,7 @@ module ActionMailer #:nodoc:
# headers 'X-Special-Domain-Specific-Header' => "SecretValue",
# 'In-Reply-To' => incoming.message_id
#
- # The resulting Mail::Message will have the following in it's header:
+ # The resulting Mail::Message will have the following in its header:
#
# X-Special-Domain-Specific-Header: SecretValue
def headers(args=nil)
@@ -524,7 +551,7 @@ module ActionMailer #:nodoc:
#
# * <tt>:subject</tt> - The subject of the message, if this is omitted, Action Mailer will
# ask the Rails I18n class for a translated <tt>:subject</tt> in the scope of
- # <tt>[:actionmailer, mailer_scope, action_name]</tt> or if this is missing, will translate the
+ # <tt>[mailer_scope, action_name]</tt> or if this is missing, will translate the
# humanized version of the <tt>action_name</tt>
# * <tt>:to</tt> - Who the message is destined for, can be a string of addresses, or an array
# of addresses.
@@ -603,9 +630,6 @@ module ActionMailer #:nodoc:
# end
#
def mail(headers={}, &block)
- # Guard flag to prevent both the old and the new API from firing
- # Should be removed when old API is removed
- @mail_was_called = true
m = @_message
# At the beginning, do not consider class default for parts order neither content_type
@@ -613,8 +637,9 @@ module ActionMailer #:nodoc:
parts_order = headers[:parts_order]
# Call all the procs (if any)
- default_values = self.class.default.merge(self.class.default) do |k,v|
- v.respond_to?(:call) ? v.bind(self).call : v
+ class_default = self.class.default
+ default_values = class_default.merge(class_default) do |k,v|
+ v.respond_to?(:to_proc) ? instance_eval(&v) : v
end
# Handle defaults
@@ -668,11 +693,11 @@ module ActionMailer #:nodoc:
end
end
- # Translates the +subject+ using Rails I18n class under <tt>[:actionmailer, mailer_scope, action_name]</tt> scope.
+ # Translates the +subject+ using Rails I18n class under <tt>[mailer_scope, action_name]</tt> scope.
# If it does not find a translation for the +subject+ under the specified scope it will default to a
# humanized version of the <tt>action_name</tt>.
def default_i18n_subject #:nodoc:
- mailer_scope = self.class.mailer_name.gsub('/', '.')
+ mailer_scope = self.class.mailer_name.tr('/', '.')
I18n.t(:subject, :scope => [mailer_scope, action_name], :default => action_name.humanize)
end
@@ -707,8 +732,8 @@ module ActionMailer #:nodoc:
end
def each_template(paths, name, &block) #:nodoc:
- templates = lookup_context.find_all(name, Array.wrap(paths))
- templates.uniq_by { |t| t.formats }.each(&block)
+ templates = lookup_context.find_all(name, Array(paths))
+ templates.uniq { |t| t.formats }.each(&block)
end
def create_parts_from_responses(m, responses) #:nodoc:
diff --git a/actionmailer/lib/action_mailer/collector.rb b/actionmailer/lib/action_mailer/collector.rb
index d03e085e83..17b22aea2a 100644
--- a/actionmailer/lib/action_mailer/collector.rb
+++ b/actionmailer/lib/action_mailer/collector.rb
@@ -22,9 +22,9 @@ module ActionMailer #:nodoc:
def custom(mime, options={})
options.reverse_merge!(:content_type => mime.to_s)
- @context.freeze_formats([mime.to_sym])
+ @context.formats = [mime.to_sym]
options[:body] = block_given? ? yield : @default_render.call
@responses << options
end
end
-end \ No newline at end of file
+end
diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb
index d1467fb526..3b38dbccc7 100644
--- a/actionmailer/lib/action_mailer/delivery_methods.rb
+++ b/actionmailer/lib/action_mailer/delivery_methods.rb
@@ -65,7 +65,7 @@ module ActionMailer
when NilClass
raise "Delivery method cannot be nil"
when Symbol
- if klass = delivery_methods[method.to_sym]
+ if klass = delivery_methods[method]
mail.delivery_method(klass, send(:"#{method}_settings"))
else
raise "Invalid delivery method #{method.inspect}"
diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb
index 7ba57b19e0..a6c163832e 100644
--- a/actionmailer/lib/action_mailer/log_subscriber.rb
+++ b/actionmailer/lib/action_mailer/log_subscriber.rb
@@ -1,9 +1,7 @@
-require 'active_support/core_ext/array/wrap'
-
module ActionMailer
class LogSubscriber < ActiveSupport::LogSubscriber
def deliver(event)
- recipients = Array.wrap(event.payload[:to]).join(', ')
+ recipients = Array(event.payload[:to]).join(', ')
info("\nSent mail to #{recipients} (%1.fms)" % event.duration)
debug(event.payload[:mail])
end
@@ -19,4 +17,4 @@ module ActionMailer
end
end
-ActionMailer::LogSubscriber.attach_to :action_mailer \ No newline at end of file
+ActionMailer::LogSubscriber.attach_to :action_mailer
diff --git a/actionmailer/lib/action_mailer/mail_helper.rb b/actionmailer/lib/action_mailer/mail_helper.rb
index 6f22adc479..2036883b22 100644
--- a/actionmailer/lib/action_mailer/mail_helper.rb
+++ b/actionmailer/lib/action_mailer/mail_helper.rb
@@ -1,11 +1,10 @@
module ActionMailer
module MailHelper
- # Uses Text::Format to take the text and format it, indented two spaces for
- # each line, and wrapped at 72 columns.
+ # Take the text and format it, indented two spaces for each line, and wrapped at 72 columns.
def block_format(text)
- formatted = text.split(/\n\r\n/).collect { |paragraph|
+ formatted = text.split(/\n\r?\n/).collect { |paragraph|
format_paragraph(paragraph)
- }.join("\n")
+ }.join("\n\n")
# Make list points stand on their own line
formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
@@ -41,7 +40,7 @@ module ActionMailer
sentences = [[]]
text.split.each do |word|
- if (sentences.last + [word]).join(' ').length > len
+ if sentences.first.present? && (sentences.last + [word]).join(' ').length > len
sentences << [word]
else
sentences.last << word
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 444754d0e9..5c03a29f0f 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -19,8 +19,9 @@ 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
+ options.relative_url_root ||= app.config.relative_url_root
ActiveSupport.on_load(:action_mailer) do
include AbstractController::UrlFor
diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb
index c4de029694..529140dfad 100644
--- a/actionmailer/lib/action_mailer/test_case.rb
+++ b/actionmailer/lib/action_mailer/test_case.rb
@@ -1,3 +1,4 @@
+require 'active_support/test_case'
require 'active_support/core_ext/class/attribute'
module ActionMailer
@@ -15,6 +16,12 @@ module ActionMailer
include TestHelper
+ included do
+ class_attribute :_mailer_class
+ setup :initialize_test_deliveries
+ setup :set_expected_mail
+ end
+
module ClassMethods
def tests(mailer)
case mailer
@@ -42,8 +49,6 @@ module ActionMailer
end
end
- module InstanceMethods
-
protected
def initialize_test_deliveries
@@ -71,16 +76,8 @@ module ActionMailer
def read_fixture(action)
IO.readlines(File.join(Rails.root, 'test', 'fixtures', self.class.mailer_class.name.underscore, action))
end
- end
-
- included do
- class_attribute :_mailer_class
- setup :initialize_test_deliveries
- setup :set_expected_mail
- end
end
include Behavior
-
end
end
diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb
index 5beab87ad2..7204822395 100644
--- a/actionmailer/lib/action_mailer/test_helper.rb
+++ b/actionmailer/lib/action_mailer/test_helper.rb
@@ -1,7 +1,5 @@
module ActionMailer
module TestHelper
- extend ActiveSupport::Concern
-
# Asserts that the number of emails sent matches the given number.
#
# def test_emails
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index b74ba3cb0d..0cb5dc6d64 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -1,7 +1,7 @@
module ActionMailer
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
diff --git a/actionmailer/lib/rails/generators/mailer/USAGE b/actionmailer/lib/rails/generators/mailer/USAGE
index 448ddbd5df..9f1d6b182e 100644
--- a/actionmailer/lib/rails/generators/mailer/USAGE
+++ b/actionmailer/lib/rails/generators/mailer/USAGE
@@ -1,6 +1,6 @@
Description:
============
- Stubs out a new mailer and its views. Pass the mailer name, either
+ Stubs out a new mailer and its views. Passes the mailer name, either
CamelCased or under_scored, and an optional list of emails as arguments.
This generates a mailer class in app/mailers and invokes your template
diff --git a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
index aaa1f79732..edcfb4233d 100644
--- a/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
+++ b/actionmailer/lib/rails/generators/mailer/templates/mailer.rb
@@ -1,17 +1,17 @@
<% module_namespacing do -%>
class <%= class_name %> < ActionMailer::Base
- default <%= key_value :from, '"from@example.com"' %>
+ default from: "from@example.com"
<% actions.each do |action| -%>
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
- # en.<%= file_path.gsub("/",".") %>.<%= action %>.subject
+ # en.<%= file_path.tr("/",".") %>.<%= action %>.subject
#
def <%= action %>
@greeting = "Hi"
- mail <%= key_value :to, '"to@example.org"' %>
+ mail to: "to@example.org"
end
<% end -%>
end
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 0b076e1ff9..99c44179fd 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -1,32 +1,14 @@
-# Pathname has a warning, so require it first while silencing
-# warnings to shut it up.
-#
-# Also, in 1.9, Bundler creates warnings due to overriding
-# Rubygems methods
-begin
- old, $VERBOSE = $VERBOSE, nil
- require 'pathname'
- require File.expand_path('../../../load_paths', __FILE__)
-ensure
- $VERBOSE = old
-end
-
+require File.expand_path('../../../load_paths', __FILE__)
require 'active_support/core_ext/kernel/reporting'
-require 'active_support/core_ext/string/encoding'
-if "ruby".encoding_aware?
- # These are the normal settings that will be set up by Railties
- # TODO: Have these tests support other combinations of these values
- silence_warnings do
- Encoding.default_internal = "UTF-8"
- Encoding.default_external = "UTF-8"
- end
+# These are the normal settings that will be set up by Railties
+# TODO: Have these tests support other combinations of these values
+silence_warnings do
+ Encoding.default_internal = "UTF-8"
+ Encoding.default_external = "UTF-8"
end
-lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
-$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
-
-require 'test/unit'
+require 'minitest/autorun'
require 'action_mailer'
require 'action_mailer/test_case'
diff --git a/actionmailer/test/asset_host_test.rb b/actionmailer/test/asset_host_test.rb
index b24eca5fbb..696a9f1174 100644
--- a/actionmailer/test/asset_host_test.rb
+++ b/actionmailer/test/asset_host_test.rb
@@ -9,7 +9,7 @@ class AssetHostMailer < ActionMailer::Base
end
end
-class AssetHostTest < Test::Unit::TestCase
+class AssetHostTest < ActiveSupport::TestCase
def setup
set_delivery_method :test
ActionMailer::Base.perform_deliveries = true
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 9fdd0e1ced..1d747ed18a 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -1,5 +1,7 @@
# encoding: utf-8
require 'abstract_unit'
+require 'set'
+
require 'active_support/time'
require 'mailers/base_mailer'
@@ -107,7 +109,7 @@ class BaseTest < ActiveSupport::TestCase
assert_equal(1, email.attachments.length)
assert_equal('invoice.jpg', email.attachments[0].filename)
expected = "\312\213\254\232)b"
- expected.force_encoding(Encoding::BINARY) if '1.9'.respond_to?(:force_encoding)
+ expected.force_encoding(Encoding::BINARY)
assert_equal expected, email.attachments['invoice.jpg'].decoded
end
@@ -116,7 +118,7 @@ class BaseTest < ActiveSupport::TestCase
assert_equal(1, email.attachments.length)
assert_equal('invoice.jpg', email.attachments[0].filename)
expected = "\312\213\254\232)b"
- expected.force_encoding(Encoding::BINARY) if '1.9'.respond_to?(:force_encoding)
+ expected.force_encoding(Encoding::BINARY)
assert_equal expected, email.attachments['invoice.jpg'].decoded
end
@@ -550,6 +552,52 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("Thanks for signing up this afternoon", mail.subject)
end
+ test "modifying the mail message with a before_filter" do
+ class BeforeFilterMailer < ActionMailer::Base
+ before_filter :add_special_header!
+
+ def welcome ; mail ; end
+
+ private
+ def add_special_header!
+ headers('X-Special-Header' => 'Wow, so special')
+ end
+ end
+
+ assert_equal('Wow, so special', BeforeFilterMailer.welcome['X-Special-Header'].to_s)
+ end
+
+ test "modifying the mail message with an after_filter" do
+ class AfterFilterMailer < ActionMailer::Base
+ after_filter :add_special_header!
+
+ def welcome ; mail ; end
+
+ private
+ def add_special_header!
+ headers('X-Special-Header' => 'Testing')
+ end
+ end
+
+ assert_equal('Testing', AfterFilterMailer.welcome['X-Special-Header'].to_s)
+ end
+
+ test "adding an inline attachment using a before_filter" do
+ class DefaultInlineAttachmentMailer < ActionMailer::Base
+ before_filter :add_inline_attachment!
+
+ def welcome ; mail ; end
+
+ private
+ def add_inline_attachment!
+ attachments.inline["footer.jpg"] = 'hey there'
+ end
+ end
+
+ mail = DefaultInlineAttachmentMailer.welcome
+ assert_equal('image/jpeg; filename=footer.jpg', mail.attachments.inline.first['Content-Type'].to_s)
+ end
+
test "action methods should be refreshed after defining new method" do
class FooMailer < ActionMailer::Base
# this triggers action_methods
@@ -559,7 +607,20 @@ class BaseTest < ActiveSupport::TestCase
end
end
- assert_equal ["notify"], FooMailer.action_methods
+ assert_equal Set.new(["notify"]), FooMailer.action_methods
+ end
+
+ test "mailer can be anonymous" do
+ mailer = Class.new(ActionMailer::Base) do
+ def welcome
+ mail
+ end
+ end
+
+ assert_equal "anonymous", mailer.mailer_name
+
+ assert_equal "Welcome", mailer.welcome.subject
+ assert_equal "Anonymous mailer body", mailer.welcome.body.encoded.strip
end
protected
diff --git a/actionmailer/test/fixtures/anonymous/welcome.erb b/actionmailer/test/fixtures/anonymous/welcome.erb
new file mode 100644
index 0000000000..8361da62c4
--- /dev/null
+++ b/actionmailer/test/fixtures/anonymous/welcome.erb
@@ -0,0 +1 @@
+Anonymous mailer body
diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb
index 7040ae6f8d..68ed86e0d4 100644
--- a/actionmailer/test/i18n_with_controller_test.rb
+++ b/actionmailer/test/i18n_with_controller_test.rb
@@ -24,7 +24,7 @@ end
class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new
Routes.draw do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
def app
diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb
index 17e9c82045..d8a73e6c46 100644
--- a/actionmailer/test/mail_helper_test.rb
+++ b/actionmailer/test/mail_helper_test.rb
@@ -22,6 +22,14 @@ class HelperMailer < ActionMailer::Base
end
end
+ def use_format_paragraph_with_long_first_word
+ @text = "Antidisestablishmentarianism is very long."
+
+ mail_with_defaults do |format|
+ format.html { render(:inline => "<%= format_paragraph @text, 10, 1 %>") }
+ end
+ end
+
def use_mailer
mail_with_defaults do |format|
format.html { render(:inline => "<%= mailer.message.subject %>") }
@@ -34,6 +42,23 @@ class HelperMailer < ActionMailer::Base
end
end
+ def use_block_format
+ @text = <<-TEXT
+This is the
+first paragraph.
+
+The second
+ paragraph.
+
+* item1 * item2
+ * item3
+ TEXT
+
+ mail_with_defaults do |format|
+ format.html { render(:inline => "<%= block_format @text %>") }
+ end
+ end
+
protected
def mail_with_defaults(&block)
@@ -63,5 +88,24 @@ class MailerHelperTest < ActionMailer::TestCase
mail = HelperMailer.use_format_paragraph
assert_match " But soft! What\r\n light through\r\n yonder window\r\n breaks?", mail.body.encoded
end
+
+ def test_use_format_paragraph_with_long_first_word
+ mail = HelperMailer.use_format_paragraph_with_long_first_word
+ assert_equal " Antidisestablishmentarianism\r\n is very\r\n long.", mail.body.encoded
+ end
+
+ def test_use_block_format
+ mail = HelperMailer.use_block_format
+ expected = <<-TEXT
+ This is the first paragraph.
+
+ The second paragraph.
+
+ * item1
+ * item2
+ * item3
+ TEXT
+ assert_equal expected.gsub("\n", "\r\n"), mail.body.encoded
+ end
end
diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb
index def8da81b8..71e93c29f1 100644
--- a/actionmailer/test/mail_layout_test.rb
+++ b/actionmailer/test/mail_layout_test.rb
@@ -43,7 +43,7 @@ class ExplicitLayoutMailer < ActionMailer::Base
end
end
-class LayoutMailerTest < Test::Unit::TestCase
+class LayoutMailerTest < ActiveSupport::TestCase
def setup
set_delivery_method :test
ActionMailer::Base.perform_deliveries = true
diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb
index 0536e83098..2ea1723434 100644
--- a/actionmailer/test/url_test.rb
+++ b/actionmailer/test/url_test.rb
@@ -57,8 +57,8 @@ class ActionMailerUrlTest < ActionMailer::TestCase
UrlTestMailer.delivery_method = :test
AppRoutes.draw do
- match ':controller(/:action(/:id))'
- match '/welcome' => "foo#bar", :as => "welcome"
+ get ':controller(/:action(/:id))'
+ get '/welcome' => "foo#bar", :as => "welcome"
end
expected = new_mail
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
deleted file mode 100644
index 29992a36b1..0000000000
--- a/actionpack/CHANGELOG
+++ /dev/null
@@ -1,5685 +0,0 @@
-*Rails 3.2.0 (unreleased)*
-
-* Limit the number of options for select_year to 1000.
-
- Pass the :max_years_allowed option to set your own limit.
-
- [Libo Cannici]
-
-* Passing formats or handlers to render :template and friends is deprecated. For example: [Nick Sutterer & José Valim]
-
- render :template => "foo.html.erb"
-
- Instead, you can provide :handlers and :formats directly as option:
-
- render :template => "foo", :formats => [:html, :js], :handlers => :erb
-
-* Changed log level of warning for missing CSRF token from :debug to :warn. [Mike Dillon]
-
-* content_tag_for and div_for can now take the collection of records. It will also yield the record as the first argument if you set a receiving argument in your block [Prem Sichanugrist]
-
- So instead of having to do this:
-
- @items.each do |item|
- content_tag_for(:li, item) do
- Title: <%= item.title %>
- end
- end
-
- You can now do this:
-
- content_tag_for(:li, @items) do |item|
- Title: <%= item.title %>
- end
-
-* send_file now guess the mime type [Esad Hajdarevic]
-
-* Mime type entries for PDF, ZIP and other formats were added [Esad Hajdarevic]
-
-* Generate hidden input before select with :multiple option set to true.
- This is useful when you rely on the fact that when no options is set,
- the state of select will be sent to rails application. Without hidden field
- nothing is sent according to HTML spec [Bogdan Gusiev]
-
-* Refactor ActionController::TestCase cookies [Andrew White]
-
- Assigning cookies for test cases should now use cookies[], e.g:
-
- cookies[:email] = 'user@example.com'
- get :index
- assert_equal 'user@example.com', cookies[:email]
-
- To clear the cookies, use clear, e.g:
-
- cookies.clear
- get :index
- assert_nil cookies[:email]
-
- We now no longer write out HTTP_COOKIE and the cookie jar is
- persistent between requests so if you need to manipulate the environment
- for your test you need to do it before the cookie jar is created.
-
-
-*Rails 3.1.1 (unreleased)*
-
-* javascript_path and stylesheet_path now refer to /assets if asset pipelining
-is on. [Santiago Pastorino]
-
-* button_to support form option. Now you're able to pass for example
-'data-type' => 'json'. [ihower]
-
-* image_path and image_tag should use /assets if asset pipelining is turned
-on. Closes #3126 [Santiago Pastorino and christos]
-
-* Avoid use of existing precompiled assets during rake assets:precompile run.
-Closes #3119 [Guillermo Iguaran]
-
-* Copy assets to nondigested filenames too [Santiago Pastorino]
-
-* Give precedence to `config.digest = false` over the existence of
-manifest.yml asset digests [christos]
-
-* escape options for the stylesheet_link_tag method [Alexey Vakhov]
-
-* Re-launch assets:precompile task using (Rake.)ruby instead of Kernel.exec so
-it works on Windows [cablegram]
-
-* env var passed to process shouldn't be modified in process method. [Santiago
-Pastorino]
-
-* `rake assets:precompile` loads the application but does not initialize
-it.
-
- To the app developer, this means configuration add in
- config/initializers/* will not be executed.
-
- Plugins developers need to special case their initializers that are
- meant to be run in the assets group by adding :group => :assets. [José Valim]
-
-* Sprockets uses config.assets.prefix for asset_path [asee]
-
-* FileStore key_file_path properly limit filenames to 255 characters. [phuibonhoa]
-
-* Fix Hash#to_query edge case with html_safe strings. [brainopia]
-
-* Allow asset tag helper methods to accept :digest => false option in order to completely avoid the digest generation.
-Useful for linking assets from static html files or from emails when the user
-could probably look at an older html email with an older asset. [Santiago Pastorino]
-
-* Don't mount Sprockets server at config.assets.prefix if config.assets.compile is false. [Mark J. Titorenko]
-
-* Set relative url root in assets when controller isn't available for Sprockets (eg. Sass files using asset_path). Fixes #2435 [Guillermo Iguaran]
-
-* Fix basic auth credential generation to not make newlines. GH #2882
-
-* Fixed the behavior of asset pipeline when config.assets.digest and config.assets.compile are false and requested asset isn't precompiled.
- Before the requested asset were compiled anyway ignoring that the config.assets.compile flag is false. [Guillermo Iguaran]
-
-* CookieJar is now Enumerable. Fixes #2795
-
-* Fixed AssetNotPrecompiled error raised when rake assets:precompile is compiling certain .erb files. See GH #2763 #2765 #2805 [Guillermo Iguaran]
-
-* Manifest is correctly placed in assets path when default assets prefix is changed. Fixes #2776 [Guillermo Iguaran]
-
-* Fixed stylesheet_link_tag and javascript_include_tag to respect additional options passed by the users when debug is on. [Guillermo Iguaran]
-
-
-*Rails 3.1.0 (August 30, 2011)*
-
-* Param values are `paramified` in controller tests. [David Chelimsky]
-
-* x_sendfile_header now defaults to nil and config/environments/production.rb doesn't set a particular value for it. This allows servers to set it through X-Sendfile-Type. [Santiago Pastorino]
-
-* The submit form helper does not generate an id "object_name_id" anymore. [fbrusatti]
-
-* Make sure respond_with with :js tries to render a template in all cases [José Valim]
-
-* json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove]
-
-* Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string [Prem Sichanugrist]
-
-* Fix escape_js to work correctly with the new SafeBuffer restriction [Paul Gallagher]
-
-* Brought back alternative convention for namespaced models in i18n [thoefer]
-
- Now the key can be either "namespace.model" or "namespace/model" until further deprecation.
-
-* It is prohibited to perform a in-place SafeBuffer mutation [tenderlove]
-
- The old behavior of SafeBuffer allowed you to mutate string in place via
- method like `sub!`. These methods can add unsafe strings to a safe buffer,
- and the safe buffer will continue to be marked as safe.
-
- An example problem would be something like this:
-
- <%= link_to('hello world', @user).sub!(/hello/, params[:xss]) %>
-
- In the above example, an untrusted string (`params[:xss]`) is added to the
- safe buffer returned by `link_to`, and the untrusted content is successfully
- sent to the client without being escaped. To prevent this from happening
- `sub!` and other similar methods will now raise an exception when they are called on a safe buffer.
-
- In addition to the in-place versions, some of the versions of these methods which return a copy of the string will incorrectly mark strings as safe. For example:
-
- <%= link_to('hello world', @user).sub(/hello/, params[:xss]) %>
-
- The new versions will now ensure that *all* strings returned by these methods on safe buffers are marked unsafe.
-
- You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb
-
-* Warn if we cannot verify CSRF token authenticity [José Valim]
-
-* Allow AM/PM format in datetime selectors [Aditya Sanghi]
-
-* Only show dump of regular env methods on exception screen (not all the rack crap) [DHH]
-
-* auto_link has been removed with no replacement. If you still use auto_link
- please install the rails_autolink gem:
- http://github.com/tenderlove/rails_autolink
-
- [tenderlove]
-
-* Added streaming support, you can enable it with: [José Valim]
-
- class PostsController < ActionController::Base
- stream :only => :index
- end
-
- Please read the docs at `ActionController::Streaming` for more information.
-
-* Added `ActionDispatch::Request.ignore_accept_header` to ignore accept headers and only consider the format given as parameter [José Valim]
-
-* Created `ActionView::Renderer` and specified an API for `ActionView::Context`, check those objects for more information [José Valim]
-
-* Added `ActionController::ParamsWrapper` to wrap parameters into a nested hash, and will be turned on for JSON request in new applications by default [Prem Sichanugrist]
-
- This can be customized by setting `ActionController::Base.wrap_parameters` in `config/initializer/wrap_parameters.rb`
-
-* RJS has been extracted out to a gem. [fxn]
-
-* Implicit actions named not_implemented can be rendered. [Santiago Pastorino]
-
-* Wildcard route will always match the optional format segment by default. [Prem Sichanugrist]
-
- For example if you have this route:
-
- match '*pages' => 'pages#show'
-
- by requesting '/foo/bar.json', your `params[:pages]` will be equals to "foo/bar" with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `:format => false` like this:
-
- match '*pages' => 'pages#show', :format => false
-
-* Added Base.http_basic_authenticate_with to do simple http basic authentication with a single class method call [DHH]
-
- class PostsController < ApplicationController
- USER_NAME, PASSWORD = "dhh", "secret"
-
- before_filter :authenticate, :except => [ :index ]
-
- def index
- render :text => "Everyone can see me!"
- end
-
- def edit
- render :text => "I'm only accessible if you know the password"
- end
-
- private
- def authenticate
- authenticate_or_request_with_http_basic do |user_name, password|
- user_name == USER_NAME && password == PASSWORD
- end
- end
- end
-
- ..can now be written as
-
- class PostsController < ApplicationController
- http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
-
- def index
- render :text => "Everyone can see me!"
- end
-
- def edit
- render :text => "I'm only accessible if you know the password"
- end
- end
-
-* Allow you to add `force_ssl` into controller to force browser to transfer data via HTTPS protocol on that particular controller. You can also specify `:only` or `:except` to specific it to particular action. [DHH and Prem Sichanugrist]
-
-* Allow FormHelper#form_for to specify the :method as a direct option instead of through the :html hash [DHH]
-
- form_for(@post, remote: true, method: :delete) instead of form_for(@post, remote: true, html: { method: :delete })
-
-* Make JavaScriptHelper#j() an alias for JavaScriptHelper#escape_javascript() -- note this then supersedes the Object#j() method that the JSON gem adds within templates using the JavaScriptHelper [DHH]
-
-* Sensitive query string parameters (specified in config.filter_parameters) will now be filtered out from the request paths in the log file. [Prem Sichanugrist, fxn]
-
-* URL parameters which return false for to_param now appear in the query string (previously they were removed) [Andrew White]
-
-* URL parameters which return nil for to_param are now removed from the query string [Andrew White]
-
-* ActionDispatch::MiddlewareStack now uses composition over inheritance. It is
-no longer an array which means there may be methods missing that were not
-tested.
-
-* Add an :authenticity_token option to form_tag for custom handling or to omit the token (pass :authenticity_token => false). [Jakub Kuźma, Igor Wiedler]
-
-* HTML5 button_tag helper. [Rizwan Reza]
-
-* Template lookup now searches further up in the inheritance chain. [Artemave]
-
-* Brought back config.action_view.cache_template_loading, which allows to decide whether templates should be cached or not. [Piotr Sarnacki]
-
-* url_for and named url helpers now accept :subdomain and :domain as options, [Josh Kalderimis]
-
-* The redirect route method now also accepts a hash of options which will only change the parts of the url in question, or an object which responds to call, allowing for redirects to be reused (check the documentation for examples). [Josh Kalderimis]
-
-* Added config.action_controller.include_all_helpers. By default 'helper :all' is done in ActionController::Base, which includes all the helpers by default. Setting include_all_helpers to false will result in including only application_helper and helper corresponding to controller (like foo_helper for foo_controller). [Piotr Sarnacki]
-
-* Added a convenience idiom to generate HTML5 data-* attributes in tag helpers from a :data hash of options:
-
- tag("div", :data => {:name => 'Stephen', :city_state => %w(Chicago IL)})
- # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
-
- Keys are dasherized. Values are JSON-encoded, except for strings and symbols. [Stephen Celis]
-
-* Deprecate old template handler API. The new API simply requires a template handler to respond to call. [José Valim]
-
-* :rhtml and :rxml were finally removed as template handlers. [José Valim]
-
-* Moved etag responsibility from ActionDispatch::Response to the middleware stack. [José Valim]
-
-* Rely on Rack::Session stores API for more compatibility across the Ruby world. This is backwards incompatible since Rack::Session expects #get_session to accept 4 arguments and requires #destroy_session instead of simply #destroy. [José Valim]
-
-* file_field automatically adds :multipart => true to the enclosing form. [Santiago Pastorino]
-
-* Renames csrf_meta_tag -> csrf_meta_tags, and aliases csrf_meta_tag for backwards compatibility. [fxn]
-
-* Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. [Yehuda Katz, Carl Lerche]
-
-
-*Rails 3.0.7 (April 18, 2011)*
-
-*No changes.
-
-
-*Rails 3.0.6 (April 5, 2011)
-
-* Fixed XSS vulnerability in `auto_link`. `auto_link` no longer marks input as
- html safe. Please make sure that calls to auto_link() are wrapped in a
- sanitize(), or a raw() depending on the type of input passed to auto_link().
- For example:
-
- <%= sanitize(auto_link(some_user_input)) %>
-
- Thanks to Torben Schulz for reporting this. The fix can be found here:
- 61ee3449674c591747db95f9b3472c5c3bd9e84d
-
-* Fixes the output of `rake routes` to be correctly match to the behavior of the application, as the regular expression used to match the path is greedy and won't capture the format part by default [Prem Sichanugrist]
-
-* Fixes an issue with number_to_human when converting values which are less than 1 but greater than -1 [Josh Kalderimis]
-
-* Sensitive query string parameters (specified in config.filter_parameters) will now be filtered out from the request paths in the log file. [Prem Sichanugrist, fxn]
-
-* URL parameters which return nil for to_param are now removed from the query string [Andrew White]
-
-* Don't allow i18n to change the minor version, version now set to ~> 0.5.0 [Santiago Pastorino]
-
-* Make TranslationHelper#translate use the :rescue_format option in I18n 0.5.0 [Sven Fuchs]
-
-* Fix regression: javascript_include_tag shouldn't raise if you register an expansion key with nil or [] value [Santiago Pastorino]
-
-* Fix Action caching bug where an action that has a non-cacheable response always renders a nil response body. It now correctly renders the response body. [Cheah Chu Yeow]
-
-
-*Rails 3.0.5 (February 26, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.4 (February 8, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.3 (November 16, 2010)*
-
-* When ActiveRecord::Base objects are sent to predicate methods, the id of the object should be sent to ARel, not the ActiveRecord::Base object.
-
-* :constraints routing should only do sanity checks against regular expressions. String arguments are OK.
-
-
-*Rails 3.0.2 (November 15, 2010)*
-
-* The helper number_to_currency accepts a new :negative_format option to be able to configure how to render negative amounts. [Don Wilson]
-
-
-*Rails 3.0.1 (October 15, 2010)*
-
-* No Changes, just a version bump.
-
-
-*Rails 3.0.0 (August 29, 2010)*
-
-* password_field renders with nil value by default making the use of passwords secure by default, if you want to render you should do for instance f.password_field(:password, :value => @user.password) [Santiago Pastorino]
-
-* Symbols and strings in routes should yield the same behavior. Note this may break existing apps that were using symbols with the new routes API. [José Valim]
-
-* Add clear_helpers as a way to clean up all helpers added to this controller, maintaining just the helper with the same name as the controller. [José Valim]
-
-* Support routing constraints in functional tests. [Andrew White]
-
-* Add a header that tells Internet Explorer (all versions) to use the best available standards support. [Yehuda Katz]
-
-* Allow stylesheet/javascript extensions to be changed through railties. [Josh Kalderimis]
-
-* link_to, button_to, and tag/tag_options now rely on html_escape instead of escape_once. [fxn]
-
-* url_for returns always unescaped strings, and the :escape option is gone. [fxn]
-
-* Added accept-charset parameter and _snowman hidden field to force the contents
- of Rails POSTed forms to be in UTF-8 [Yehuda Katz]
-
-* Upgrade to Rack 1.2.1 [Jeremy Kemper]
-
-* Allow :path to be given to match/get/post/put/delete instead of :path_names in the new router [Carlos Antônio da Silva]
-
-* Added resources_path_names to the new router DSL [José Valim]
-
-* Allow options to be given to the namespace method in the new router [Carlos Antônio da Silva]
-
-* Deprecate :name_prefix in the new router DSL [José Valim]
-
-* Add shallow routes back to the new router [Diego Carrion, Andrew White]
-
- resources :posts do
- shallow do
- resources :comments
- end
- end
-
- You can now use comment_path for /comments/1 instead of post_comment_path for /posts/1/comments/1.
-
-* Add support for multi-subdomain session by setting cookie host in session cookie so you can share session between www.example.com, example.com and user.example.com. #4818 [Guillermo Álvarez]
-
-* Removed textilize, textilize_without_paragraph and markdown helpers. [Santiago Pastorino]
-
-* Remove middleware laziness [José Valim]
-
-* Make session stores rely on request.cookie_jar and change set_session semantics to return the cookie value instead of a boolean. [José Valim]
-
-* OAuth 2: HTTP Token Authorization support to complement Basic and Digest Authorization. [Rick Olson]
-
-* Fixed inconsistencies in form builder and view helpers #4432 [Neeraj Singh]
-
-* Both :xml and :json renderers now forwards the given options to the model, allowing you to invoke them as render :xml => @projects, :include => :tasks [José Valim, Yehuda Katz]
-
-* Renamed the field error CSS class from fieldWithErrors to field_with_errors for consistency. [Jeremy Kemper]
-
-* Add support for shorthand routes like /projects/status(.:format) #4423 [Diego Carrion]
-
-* Changed translate helper so that it doesn’t mark every translation as safe HTML. Only keys with a "_html" suffix and keys named "html" are considered to be safe HTML. All other translations are left untouched. [Craig Davey]
-
-* New option :as added to form_for allows to change the object name. The old <% form_for :client, @post %> becomes <% form_for @post, :as => :client %> [spastorino]
-
-* Removed verify method in controllers. [JV]
- It's now available as a plugin at http://github.com/rails/verification
-
-* Removed input, form, error_messages_for and error_message_on from views. [JV]
- It's now available as a plugin at http://github.com/rails/dynamic_form
-
-* Routes can be scoped by controller module. [Jeremy Kemper]
-
- # /session => Auth::SessionsController
- scope :module => 'auth' do
- resource :session
- end
-
-* Added #favicon_link_tag, it uses #image_path so in particular the favicon gets an asset ID [fxn]
-
-* Fixed that default locale templates should be used if the current locale template is missing [DHH]
-
-* Added all the new HTML5 form types as individual form tag methods (search, url, number, etc) #3646 [Stephen Celis]
-
-* Changed the object used in routing constraints to be an instance of
- ActionDispatch::Request rather than Rack::Request [YK]
-
-* Changed ActionDispatch::Request#method to return a String, to be compatible
- with Rack::Request. Added ActionDispatch::Request#method_symbol to
- return a symbol form of the request method. [YK]
-
-* Changed ActionDispatch::Request#method to return the original
- method and #request_method to return the overridden method in the
- case of methodoverride being used (this means that #method returns
- "HEAD" and #request_method returns "GET" in HEAD requests). This
- is for compatibility with Rack::Request [YK]
-
-* #concat is now deprecated in favor of using <%= %> helpers [YK]
-
-* Block helpers now return Strings, so you can use <%= form_for @foo do |f| %>.
- <% form_for do |f| %> still works with deprecation notices [YK]
-
-* Add a new #mount method on the router that does not anchor the PATH_INFO
- at the end [YK & CL]
-
-* Create a new LookupContext object that is responsible for performantly
- finding a template for a given pattern [JV]
-
-* Removed relative_url_for in favor of respecting SCRIPT_NAME [YK & CL]
-
-* Changed file streaming to use Rack::Sendfile middleware [YK]
-
-* ActionDispatch::Request#content_type returns a String to be compatible with
- Rack::Request. Use #content_mime_type for the Mime::Type instance [YK]
-
-* Updated Prototype to 1.6.1 and Scriptaculous to 1.8.3 [ML]
-
-* Change the preferred way that URL helpers are included into a class[YK & CL]
-
- # for all helpers including named routes
- include Rails.application.router.url_helpers
-
- # for just url_for
- include Rails.application.router.url_for
-
-* Fixed that PrototypeHelper#update_page should return html_safe [DHH]
-
-* Fixed that much of DateHelper wouldn't return html_safe? strings [DHH]
-
-* Fixed that fragment caching should return a cache hit as html_safe (or it would all just get escaped) [DHH]
-
-* Added that ActionController::Base now does helper :all instead of relying on the default ApplicationController in Rails to do it [DHH]
-
-* Added ActionDispatch::Request#authorization to access the http authentication header regardless of its proxy hiding [DHH]
-
-* Added :alert, :notice, and :flash as options to ActionController::Base#redirect_to that'll automatically set the proper flash before the redirection [DHH]. Examples:
-
- flash[:notice] = 'Post was created'
- redirect_to(@post)
-
- ...becomes:
-
- redirect_to(@post, :notice => 'Post was created')
-
-* Added ActionController::Base#notice/= and ActionController::Base#alert/= as a convenience accessors in both the controller and the view for flash[:notice]/= and flash[:alert]/= [DHH]
-
-
-* Introduce grouped_collection_select helper. #1249 [Dan Codeape, Erik Ostrom]
-
-* Make sure javascript_include_tag/stylesheet_link_tag does not append ".js" or ".css" onto external urls. #1664 [Matthew Rudy Jacobs]
-
-* Ruby 1.9: fix Content-Length for multibyte send_data streaming. #2661 [Sava Chankov]
-
-* Ruby 1.9: ERB template encoding using a magic comment at the top of the file. [Jeremy Kemper]
- <%# encoding: utf-8 %>
-
-* Change integration test helpers to accept Rack environment instead of just HTTP Headers [Pratik Naik]
-
- Before : get '/path', {}, 'Accept' => 'text/javascript'
- After : get '/path', {}, 'HTTP_ACCEPT' => 'text/javascript'
-
-* Instead of checking Rails.env.test? in Failsafe middleware, check env["rails.raise_exceptions"] [Bryan Helmkamp]
-
-* Fixed that TestResponse.cookies was returning cookies unescaped #1867 [Doug McInnes]
-
-
-*2.3.2 [Final] (March 15, 2009)*
-
-* Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #<Post:0x23150b8>") [DHH]
-
-* Don't check authenticity tokens for any AJAX requests [Ross Kaffenberger/Bryan Helmkamp]
-
-* Added ability to pass in :public => true to fresh_when, stale?, and expires_in to make the request proxy cachable #2095 [Gregg Pollack]
-
-* Fixed that passing a custom form builder would be forwarded to nested fields_for calls #2023 [Eloy Duran/Nate Wiger]
-
-* Form option helpers now support disabled option tags and the use of lambdas for selecting/disabling option tags from collections #837 [Tekin]
-
-* Added partial scoping to TranslationHelper#translate, so if you call translate(".foo") from the people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo") [DHH]
-
-* Fix a syntax error in current_page?() that was prevent matches against URL's with multiple query parameters #1385, #1868 [chris finne/Andrew White]
-
-* Added localized rescue template when I18n.locale is set (ex: public/404.da.html) #1835 [José Valim]
-
-* Make the form_for and fields_for helpers support the new Active Record nested update options. #1202 [Eloy Duran]
-
- <% form_for @person do |person_form| %>
- ...
- <% person_form.fields_for :projects do |project_fields| %>
- <% if project_fields.object.active? %>
- Name: <%= project_fields.text_field :name %>
- <% end %>
- <% end %>
- <% end %>
-
-
-* Added grouped_options_for_select helper method for wrapping option tags in optgroups. #977 [Jon Crawford]
-
-* Implement HTTP Digest authentication. #1230 [Gregg Kellogg, Pratik Naik] Example :
-
- class DummyDigestController < ActionController::Base
- USERS = { "lifo" => 'world' }
-
- before_filter :authenticate
-
- def index
- render :text => "Hello Secret"
- end
-
- private
-
- def authenticate
- authenticate_or_request_with_http_digest("Super Secret") do |username|
- # Return the user's password
- USERS[username]
- end
- end
- end
-
-* Improved i18n support for the number_to_human_size helper. Changes the storage_units translation data; update your translations accordingly. #1634 [Yaroslav Markin]
- storage_units:
- # %u is the storage unit, %n is the number (default: 2 MB)
- format: "%n %u"
- units:
- byte:
- one: "Byte"
- other: "Bytes"
- kb: "KB"
- mb: "MB"
- gb: "GB"
- tb: "TB"
-
-* Added :silence option to BenchmarkHelper#benchmark and turned log_level into a hash parameter and deprecated the old use [DHH]
-
-* Fixed the AssetTagHelper cache to use the computed asset host as part of the cache key instead of just assuming the its a string #1299 [DHH]
-
-* Make ActionController#render(string) work as a shortcut for render :file/:template/:action => string. [#1435] [Pratik Naik] Examples:
-
- # Instead of render(:action => 'other_action')
- render('other_action') # argument has no '/'
- render(:other_action)
-
- # Instead of render(:template => 'controller/action')
- render('controller/action') # argument must not begin with a '/', but contain a '/'
-
- # Instead of render(:file => '/Users/lifo/home.html.erb')
- render('/Users/lifo/home.html.erb') # argument must begin with a '/'
-
-* Add :prompt option to date/time select helpers. #561 [Sam Oliver]
-
-* Fixed that send_file shouldn't set an etag #1578 [Hongli Lai]
-
-* Allow users to opt out of the spoofing checks in Request#remote_ip. Useful for sites whose traffic regularly triggers false positives. [Darren Boyd]
-
-* Deprecated formatted_polymorphic_url. [Jeremy Kemper]
-
-* Added the option to declare an asset_host as an object that responds to call (see http://github.com/dhh/asset-hosting-with-minimum-ssl for an example) [David Heinemeier Hansson]
-
-* Added support for multiple routes.rb files (useful for plugin engines). This also means that draw will no longer clear the route set, you have to do that by hand (shouldn't make a difference to you unless you're doing some funky stuff) [David Heinemeier Hansson]
-
-* Dropped formatted_* routes in favor of just passing in :format as an option. This cuts resource routes generation in half #1359 [aaronbatalion]
-
-* Remove support for old double-encoded cookies from the cookie store. These values haven't been generated since before 2.1.0, and any users who have visited the app in the intervening 6 months will have had their cookie upgraded. [Michael Koziarski]
-
-* Allow helpers directory to be overridden via ActionController::Base.helpers_dir #1424 [Sam Pohlenz]
-
-* Remove deprecated ActionController::Base#assign_default_content_type_and_charset
-
-* Changed the default of ActionView#render to assume partials instead of files when not given an options hash [David Heinemeier Hansson]. Examples:
-
- # Instead of <%= render :partial => "account" %>
- <%= render "account" %>
-
- # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
- <%= render "account", :account => @buyer %>
-
- # @account is an Account instance, so it uses the RecordIdentifier to replace
- # <%= render :partial => "accounts/account", :locals => { :account => @account } %>
- <%= render(@account) %>
-
- # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
- # <%= render :partial => "posts/post", :collection => @posts %>
- <%= render(@posts) %>
-
-* Remove deprecated render_component. Please use the plugin from http://github.com/rails/render_component/tree/master [Pratik Naik]
-
-* Fixed RedCloth and BlueCloth shouldn't preload. Instead just assume that they're available if you want to use textilize and markdown and let autoload require them [David Heinemeier Hansson]
-
-
-*2.2.2 (November 21st, 2008)*
-
-* I18n: translate number_to_human_size. Add storage_units: [Bytes, KB, MB, GB, TB] to your translations. #1448 [Yaroslav Markin]
-
-* Restore backwards compatible functionality for setting relative_url_root. Include deprecation
-
-* Switched the CSRF module to use the request content type to decide if the request is forgeable. #1145 [Jeff Cohen]
-
-* Added :only and :except to map.resources to let people cut down on the number of redundant routes in an application. Typically only useful for huge routesets. #1215 [Tom Stuart]
-
- map.resources :products, :only => :show do |product|
- product.resources :images, :except => :destroy
- end
-
-* Added render :js for people who want to render inline JavaScript replies without using RJS [David Heinemeier Hansson]
-
-* Fixed that polymorphic_url should compact given array #1317 [hiroshi]
-
-* Fixed the sanitize helper to avoid double escaping already properly escaped entities #683 [antonmos/Ryan McGeary]
-
-* Fixed that FormTagHelper generated illegal html if name contained square brackets #1238 [Vladimir Dobriakov]
-
-* Fix regression bug that made date_select and datetime_select raise a Null Pointer Exception when a nil date/datetime was passed and only month and year were displayed #1289 [Bernardo Padua/Tor Erik]
-
-* Simplified the logging format for parameters (don't include controller, action, and format as duplicates) [David Heinemeier Hansson]
-
-* Remove the logging of the Session ID when the session store is CookieStore [David Heinemeier Hansson]
-
-* Fixed regex in redirect_to to fully support URI schemes #1247 [Seth Fitzsimmons]
-
-* Fixed bug with asset timestamping when using relative_url_root #1265 [Joe Goldwasser]
-
-
-*2.2.0 [RC1] (October 24th, 2008)*
-
-* Fix incorrect closing CDATA delimiter and that HTML::Node.parse would blow up on unclosed CDATA sections [packagethief]
-
-* Added stale? and fresh_when methods to provide a layer of abstraction above request.fresh? and friends [David Heinemeier Hansson]. Example:
-
- class ArticlesController < ApplicationController
- def show_with_respond_to_block
- @article = Article.find(params[:id])
-
-
- # If the request sends headers that differs from the options provided to stale?, then
- # the request is indeed stale and the respond_to block is triggered (and the options
- # to the stale? call is set on the response).
- #
- # If the request headers match, then the request is fresh and the respond_to block is
- # not triggered. Instead the default render will occur, which will check the last-modified
- # and etag headers and conclude that it only needs to send a "304 Not Modified" instead
- # of rendering the template.
- if stale?(:last_modified => @article.published_at.utc, :etag => @article)
- respond_to do |wants|
- # normal response processing
- end
- end
- end
-
- def show_with_implied_render
- @article = Article.find(params[:id])
-
- # Sets the response headers and checks them against the request, if the request is stale
- # (i.e. no match of either etag or last-modified), then the default render of the template happens.
- # If the request is fresh, then the default render will return a "304 Not Modified"
- # instead of rendering the template.
- fresh_when(:last_modified => @article.published_at.utc, :etag => @article)
- end
- end
-
-
-* Added inline builder yield to atom_feed_helper tags where appropriate [Sam Ruby]. Example:
-
- entry.summary :type => 'xhtml' do |xhtml|
- xhtml.p pluralize(order.line_items.count, "line item")
- xhtml.p "Shipped to #{order.address}"
- xhtml.p "Paid by #{order.pay_type}"
- end
-
-* Make PrototypeHelper#submit_to_remote a wrapper around PrototypeHelper#button_to_remote. [Tarmo Tänav]
-
-* Set HttpOnly for the cookie session store's cookie. #1046
-
-* Added FormTagHelper#image_submit_tag confirm option #784 [Alastair Brunton]
-
-* Fixed FormTagHelper#submit_tag with :disable_with option wouldn't submit the button's value when was clicked #633 [Jose Fernandez]
-
-* Stopped logging template compiles as it only clogs up the log [David Heinemeier Hansson]
-
-* Changed the X-Runtime header to report in milliseconds [David Heinemeier Hansson]
-
-* Changed BenchmarkHelper#benchmark to report in milliseconds [David Heinemeier Hansson]
-
-* Changed logging format to be millisecond based and skip misleading stats [David Heinemeier Hansson]. Went from:
-
- Completed in 0.10000 (4 reqs/sec) | Rendering: 0.04000 (40%) | DB: 0.00400 (4%) | 200 OK [http://example.com]
-
- ...to:
-
- Completed in 100ms (View: 40, DB: 4) | 200 OK [http://example.com]
-
-* Add support for shallow nesting of routes. #838 [S. Brent Faulkner]
-
- Example :
-
- map.resources :users, :shallow => true do |user|
- user.resources :posts
- end
-
- - GET /users/1/posts (maps to PostsController#index action as usual)
- named route "user_posts" is added as usual.
-
- - GET /posts/2 (maps to PostsController#show action as if it were not nested)
- Additionally, named route "post" is added too.
-
-* Added button_to_remote helper. #3641 [Donald Piret, Tarmo Tänav]
-
-* Deprecate render_component. Please use render_component plugin from http://github.com/rails/render_component/tree/master [Pratik Naik]
-
-* Routes may be restricted to lists of HTTP methods instead of a single method or :any. #407 [Brennan Dunn, Gaius Centus Novus]
- map.resource :posts, :collection => { :search => [:get, :post] }
- map.session 'session', :requirements => { :method => [:get, :post, :delete] }
-
-* Deprecated implicit local assignments when rendering partials [Josh Peek]
-
-* Introduce current_cycle helper method to return the current value without bumping the cycle. #417 [Ken Collins]
-
-* Allow polymorphic_url helper to take url options. #880 [Tarmo Tänav]
-
-* Switched integration test runner to use Rack processor instead of CGI [Josh Peek]
-
-* Made AbstractRequest.if_modified_sense return nil if the header could not be parsed [Jamis Buck]
-
-* Added back ActionController::Base.allow_concurrency flag [Josh Peek]
-
-* AbstractRequest.relative_url_root is no longer automatically configured by a HTTP header. It can now be set in your configuration environment with config.action_controller.relative_url_root [Josh Peek]
-
-* Update Prototype to 1.6.0.2 #599 [Patrick Joyce]
-
-* Conditional GET utility methods. [Jeremy Kemper]
- response.last_modified = @post.updated_at
- response.etag = [:admin, @post, current_user]
-
- if request.fresh?(response)
- head :not_modified
- else
- # render ...
- end
-
-* All 2xx requests are considered successful [Josh Peek]
-
-* Fixed that AssetTagHelper#compute_public_path shouldn't cache the asset_host along with the source or per-request proc's won't run [David Heinemeier Hansson]
-
-* Removed config.action_view.cache_template_loading, use config.cache_classes instead [Josh Peek]
-
-* Get buffer for fragment cache from template's @output_buffer [Josh Peek]
-
-* Set config.action_view.warn_cache_misses = true to receive a warning if you perform an action that results in an expensive disk operation that could be cached [Josh Peek]
-
-* Refactor template preloading. New abstractions include Renderable mixins and a refactored Template class [Josh Peek]
-
-* Changed ActionView::TemplateHandler#render API method signature to render(template, local_assigns = {}) [Josh Peek]
-
-* Changed PrototypeHelper#submit_to_remote to PrototypeHelper#button_to_remote to stay consistent with link_to_remote (submit_to_remote still works as an alias) #8994 [clemens]
-
-* Add :recursive option to javascript_include_tag and stylesheet_link_tag to be used along with :all. #480 [Damian Janowski]
-
-* Allow users to disable the use of the Accept header [Michael Koziarski]
-
- The accept header is poorly implemented by browsers and causes strange
- errors when used on public sites where crawlers make requests too. You
- can use formatted urls (e.g. /people/1.xml) to support API clients in a
- much simpler way.
-
- To disable the header you need to set:
-
- config.action_controller.use_accept_header = false
-
-* Do not stat template files in production mode before rendering. You will no longer be able to modify templates in production mode without restarting the server [Josh Peek]
-
-* Deprecated TemplateHandler line offset [Josh Peek]
-
-* Allow caches_action to accept cache store options. #416. [José Valim]. Example:
-
- caches_action :index, :redirected, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
-
-* Remove define_javascript_functions, javascript_include_tag and friends are far superior. [Michael Koziarski]
-
-* Deprecate :use_full_path render option. The supplying the option no longer has an effect [Josh Peek]
-
-* Add :as option to render a collection of partials with a custom local variable name. #509 [Simon Jefford, Pratik Naik]
-
- render :partial => 'other_people', :collection => @people, :as => :person
-
- This will let you access objects of @people as 'person' local variable inside 'other_people' partial template.
-
-* time_zone_select: support for regexp matching of priority zones. Resolves #195 [Ernie Miller]
-
-* Made ActionView::Base#render_file private [Josh Peek]
-
-* Refactor and simplify the implementation of assert_redirected_to. Arguments are now normalised relative to the controller being tested, not the root of the application. [Michael Koziarski]
-
- This could cause some erroneous test failures if you were redirecting between controllers
- in different namespaces and wrote your assertions relative to the root of the application.
-
-* Remove follow_redirect from controller functional tests.
-
- If you want to follow redirects you can use integration tests. The functional test
- version was only useful if you were using redirect_to :id=>...
-
-* Fix polymorphic_url with singleton resources. #461 [Tammer Saleh]
-
-* Replaced TemplateFinder abstraction with ViewLoadPaths [Josh Peek]
-
-* Added block-call style to link_to [Sam Stephenson/David Heinemeier Hansson]. Example:
-
- <% link_to(@profile) do %>
- <strong><%= @profile.name %></strong> -- <span>Check it out!!</span>
- <% end %>
-
-* Performance: integration test benchmarking and profiling. [Jeremy Kemper]
-
-* Make caching more aware of mime types. Ensure request format is not considered while expiring cache. [Jonathan del Strother]
-
-* Drop ActionController::Base.allow_concurrency flag [Josh Peek]
-
-* More efficient concat and capture helpers. Remove ActionView::Base.erb_variable. [Jeremy Kemper]
-
-* Added page.reload functionality. Resolves #277. [Sean Huber]
-
-* Fixed Request#remote_ip to only raise hell if the HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR doesn't match (not just if they're both present) [Mark Imbriaco, Bradford Folkens]
-
-* Allow caches_action to accept a layout option [José Valim]
-
-* Added Rack processor [Ezra Zygmuntowicz, Josh Peek]
-
-
-*2.1.0 (May 31st, 2008)*
-
-* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing]
-
-* Fixed that forgery protection can be used without session tracking (Peter Jones) [#139]
-
-* Added session(:on) to turn session management back on in a controller subclass if the superclass turned it off (Peter Jones) [#136]
-
-* Change the request forgery protection to go by Content-Type instead of request.format so that you can't bypass it by POSTing to "#{request.uri}.xml" [Rick Olson]
-* InstanceTag#default_time_from_options with hash args uses Time.current as default; respects hash settings when time falls in system local spring DST gap [Geoff Buesing]
-
-* select_date defaults to Time.zone.today when config.time_zone is set [Geoff Buesing]
-
-* Fixed that TextHelper#text_field would corrypt when raw HTML was used as the value (mchenryc, Kevin Glowacz) [#80]
-
-* Added ActionController::TestCase#rescue_action_in_public! to control whether the action under test should use the regular rescue_action path instead of simply raising the exception inline (great for error testing) [David Heinemeier Hansson]
-
-* Reduce number of instance variables being copied from controller to view. [Pratik Naik]
-
-* select_datetime and select_time default to Time.zone.now when config.time_zone is set [Geoff Buesing]
-
-* datetime_select defaults to Time.zone.now when config.time_zone is set [Geoff Buesing]
-
-* Remove ActionController::Base#view_controller_internals flag. [Pratik Naik]
-
-* Add conditional options to caches_page method. [Paul Horsfall]
-
-* Move missing template logic to ActionView. [Pratik Naik]
-
-* Introduce ActionView::InlineTemplate class. [Pratik Naik]
-
-* Automatically parse posted JSON content for Mime::JSON requests. [Rick Olson]
-
- POST /posts
- {"post": {"title": "Breaking News"}}
-
- def create
- @post = Post.create params[:post]
- # ...
- end
-
-* add json_escape ERB util to escape html entities in json strings that are output in HTML pages. [Rick Olson]
-
-* Provide a helper proxy to access helper methods from outside views. Closes #10839 [Josh Peek]
- e.g. ApplicationController.helpers.simple_format(text)
-
-* Improve documentation. [Xavier Noria, leethal, jerome]
-
-* Ensure RJS redirect_to doesn't html-escapes string argument. Closes #8546 [Josh Peek, eventualbuddha, Pratik Naik]
-
-* Support render :partial => collection of heterogeneous elements. #11491 [Zach Dennis]
-
-* Avoid remote_ip spoofing. [Brian Candler]
-
-* Added support for regexp flags like ignoring case in the :requirements part of routes declarations #11421 [NeilW]
-
-* Fixed that ActionController::Base#read_multipart would fail if boundary was exactly 10240 bytes #10886 [ariejan]
-
-* Fixed HTML::Tokenizer (used in sanitize helper) didn't handle unclosed CDATA tags #10071 [esad, packagethief]
-
-* Improve documentation. [Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert]
-
-* Fixed that FormHelper#radio_button would produce invalid ids #11298 [harlancrystal]
-
-* Added :confirm option to submit_tag #11415 [Emilio Tagua]
-
-* Fixed NumberHelper#number_with_precision to properly round in a way that works equally on Mac, Windows, Linux (closes #11409, #8275, #10090, #8027) [zhangyuanyi]
-
-* Allow the #simple_format text_helper to take an html_options hash for each paragraph. #2448 [François Beausoleil, Chris O'Sullivan]
-
-* Fix regression from filter refactoring where re-adding a skipped filter resulted in it being called twice. [Rick Olson]
-
-* Refactor filters to use Active Support callbacks. #11235 [Josh Peek]
-
-* Fixed that polymorphic routes would modify the input array #11363 [thomas.lee]
-
-* Added :format option to NumberHelper#number_to_currency to enable better localization support #11149 [lylo]
-
-* Fixed that TextHelper#excerpt would include one character too many #11268 [Irfy]
-
-* Fix more obscure nested parameter hash parsing bug. #10797 [thomas.lee]
-
-* Added ActionView::Helpers::register_javascript/stylesheet_expansion to make it easier for plugin developers to inject multiple assets. #10350 [lotswholetime]
-
-* Fix nested parameter hash parsing bug. #10797 [thomas.lee]
-
-* Allow using named routes in ActionController::TestCase before any request has been made. Closes #11273 [Eloy Duran]
-
-* Fixed that sweepers defined by cache_sweeper will be added regardless of the perform_caching setting. Instead, control whether the sweeper should be run with the perform_caching setting. This makes testing easier when you want to turn perform_caching on/off [David Heinemeier Hansson]
-
-* Make MimeResponds::Responder#any work without explicit types. Closes #11140 [jaw6]
-
-* Better error message for type conflicts when parsing params. Closes #7962 [spicycode, matt]
-
-* Remove unused ActionController::Base.template_class. Closes #10787 [Pratik Naik]
-
-* Moved template handlers related code from ActionView::Base to ActionView::Template. [Pratik Naik]
-
-* Tests for div_for and content_tag_for helpers. Closes #11223 [Chris O'Sullivan]
-
-* Allow file uploads in Integration Tests. Closes #11091 [RubyRedRick]
-
-* Refactor partial rendering into a PartialTemplate class. [Pratik Naik]
-
-* Added that requests with JavaScript as the priority mime type in the accept header and no format extension in the parameters will be treated as though their format was :js when it comes to determining which template to render. This makes it possible for JS requests to automatically render action.js.rjs files without an explicit respond_to block [David Heinemeier Hansson]
-
-* Tests for distance_of_time_in_words with TimeWithZone instances. Closes #10914 [Ernesto Jimenez]
-
-* Remove support for multivalued (e.g., '&'-delimited) cookies. [Jamis Buck]
-
-* Fix problem with render :partial collections, records, and locals. #11057 [lotswholetime]
-
-* Added support for naming concrete classes in sweeper declarations [David Heinemeier Hansson]
-
-* Remove ERB trim variables from trace template in case ActionView::Base.erb_trim_mode is changed in the application. #10098 [Tim Pope, Chris Kampmeier]
-
-* Fix typo in form_helper documentation. #10650 [Xavier Shay, Chris Kampmeier]
-
-* Fix bug with setting Request#format= after the getter has cached the value. #10889 [cch1]
-
-* Correct inconsistencies in RequestForgeryProtection docs. #11032 [Mislav Marohnić]
-
-* Introduce a Template class to ActionView. #11024 [Pratik Naik]
-
-* Introduce the :index option for form_for and fields_for to simplify multi-model forms (see http://railscasts.com/episodes/75). #9883 [rmm5t]
-
-* Introduce map.resources :cards, :as => 'tarjetas' to use a custom resource name in the URL: cards_path == '/tarjetas'. #10578 [blj]
-
-* TestSession supports indifferent access. #7372 [tamc, Arsen7, mhackett, julik, jean.helou]
-
-* Make assert_routing aware of the HTTP method used. #8039 [mpalmer]
- e.g. assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
-
-* Make map.root accept a single symbol as an argument to declare an alias. #10818 [bscofield]
-
- e.g. map.dashboard '/dashboard', :controller=>'dashboard'
- map.root :dashboard
-
-* Handle corner case with image_tag when passed 'messed up' image names. #9018 [Duncan Beevers, mpalmer]
-
-* Add label_tag helper for generating elements. #10802 [DefV]
-
-* Introduce TemplateFinder to handle view paths and lookups. #10800 [Pratik Naik]
-
-* Performance: optimize route recognition. Large speedup for apps with many resource routes. #10835 [oleganza]
-
-* Make render :partial recognise form builders and use the _form partial. #10814 [Damian Janowski]
-
-* Allow users to declare other namespaces when using the atom feed helpers. #10304 [david.calavera]
-
-* Introduce send_file :x_sendfile => true to send an X-Sendfile response header. [Jeremy Kemper]
-
-* Fixed ActionView::Helpers::ActiveRecordHelper::form for when protect_from_forgery is used #10739 [Jeremy Evans]
-
-* Provide nicer access to HTTP Headers. Instead of request.env["HTTP_REFERRER"] you can now use request.headers["Referrer"]. [Michael Koziarski]
-
-* UrlWriter respects relative_url_root. #10748 [Cheah Chu Yeow]
-
-* The asset_host block takes the controller request as an optional second argument. Example: use a single asset host for SSL requests. #10549 [Cheah Chu Yeow, Peter B, Tom Taylor]
-
-* Support render :text => nil. #6684 [tjennings, PotatoSalad, Cheah Chu Yeow]
-
-* assert_response failures include the exception message. #10688 [Seth Rasmussen]
-
-* All fragment cache keys are now by default prefixed with the "views/" namespace [David Heinemeier Hansson]
-
-* Moved the caching stores from ActionController::Caching::Fragments::* to ActiveSupport::Cache::*. If you're explicitly referring to a store, like ActionController::Caching::Fragments::MemoryStore, you need to update that reference with ActiveSupport::Cache::MemoryStore [David Heinemeier Hansson]
-
-* Deprecated ActionController::Base.fragment_cache_store for ActionController::Base.cache_store [David Heinemeier Hansson]
-
-* Made fragment caching in views work for rjs and builder as well #6642 [Dee Zsombor]
-
-* Fixed rendering of partials with layout when done from site layout #9209 [antramm]
-
-* Fix atom_feed_helper to comply with the atom spec. Closes #10672 [Xavier Shay]
-
- * The tags created do not contain a date (http://feedvalidator.org/docs/error/InvalidTAG.html)
- * IDs are not guaranteed unique
- * A default self link was not provided, contrary to the documentation
- * NOTE: This changes tags for existing atom entries, but at least they validate now.
-
-* Correct indentation in tests. Closes #10671 [Luca Guidi]
-
-* Fix that auto_link looks for ='s in url paths (Amazon urls have them). Closes #10640 [Brad Greenlee]
-
-* Ensure that test case setup is run even if overridden. #10382 [Josh Peek]
-
-* Fix HTML Sanitizer to allow trailing spaces in CSS style attributes. Closes #10566 [wesley.moxam]
-
-* Add :default option to time_zone_select. #10590 [Matt Aimonetti]
-
-
-*2.0.2* (December 16th, 2007)
-
-* Added delete_via_redirect and put_via_redirect to integration testing #10497 [philodespotos]
-
-* Allow headers['Accept'] to be set by hand when calling xml_http_request #10461 [BMorearty]
-
-* Added OPTIONS to list of default accepted HTTP methods #10449 [holoway]
-
-* Added option to pass proc to ActionController::Base.asset_host for maximum configurability #10521 [Cheah Chu Yeow]. Example:
-
- ActionController::Base.asset_host = Proc.new { |source|
- if source.starts_with?('/images')
- "http://images.example.com"
- else
- "http://assets.example.com"
- end
- }
-
-* Fixed that ActionView#file_exists? would be incorrect if @first_render is set #10569 [dbussink]
-
-* Added that Array#to_param calls to_param on all it's elements #10473 [brandon]
-
-* Ensure asset cache directories are automatically created. #10337 [Josh Peek, Cheah Chu Yeow]
-
-* render :xml and :json preserve custom content types. #10388 [jmettraux, Cheah Chu Yeow]
-
-* Refactor Action View template handlers. #10437, #10455 [Josh Peek]
-
-* Fix DoubleRenderError message and leave out mention of returning false from filters. Closes #10380 [Frederick Cheung]
-
-* Clean up some cruft around ActionController::Base#head. Closes #10417 [ssoroka]
-
-
-*2.0.1* (December 7th, 2007)
-
-* Fixed send_file/binary_content for testing #8044 [tolsen]
-
-* When a NonInferrableControllerError is raised, make the proposed fix clearer in the error message. Closes #10199 [Jack Danger Canty]
-
-* Update Prototype to 1.6.0.1. [sam]
-
-* Update script.aculo.us to 1.8.0.1. [madrobby]
-
-* Add 'disabled' attribute to <OPTION> separators used in time zone and country selects. Closes #10354 [Josh Susser]
-
-* Added the same record identification guessing rules to fields_for as form_for has [David Heinemeier Hansson]
-
-* Fixed that verification violations with no specified action didn't halt the chain (now they do with a 400 Bad Request) [David Heinemeier Hansson]
-
-* Raise UnknownHttpMethod exception for unknown HTTP methods. Closes #10303 [Tarmo Tänav]
-
-* Update to Prototype -r8232. [sam]
-
-* Make sure the optimisation code for routes doesn't get used if :host, :anchor or :port are provided in the hash arguments. [pager, Michael Koziarski] #10292
-
-* Added protection from trailing slashes on page caching #10229 [devrieda]
-
-* Asset timestamps are appended, not prepended. Closes #10276 [Mike Naberezny]
-
-* Minor inconsistency in description of render example. Closes #10029 [ScottSchram]
-
-* Add #prepend_view_path and #append_view_path instance methods on ActionController::Base for consistency with the class methods. [Rick Olson]
-
-* Refactor sanitizer helpers into HTML classes and make it easy to swap them out with custom implementations. Closes #10129. [Rick Olson]
-
-* Add deprecation for old subtemplate syntax for ActionMailer templates, use render :partial [Rick Olson]
-
-* Fix TemplateError so it doesn't bomb on exceptions while running tests [Rick Olson]
-
-* Fixed that named routes living under resources shouldn't have double slashes #10198 [Isaac Feliu]
-
-* Make sure that cookie sessions use a secret that is at least 30 chars in length. [Michael Koziarski]
-
-* Fixed that partial rendering should look at the type of the first render to determine its own type if no other clues are available (like when using text.plain.erb as the extension in AM) #10130 [java]
-
-* Fixed that has_many :through associations should render as collections too #9051 [mathie/Jack Danger Canty]
-
-* Added :mouseover short-cut to AssetTagHelper#image_tag for doing easy image swaps #6893 [joost]
-
-* Fixed handling of non-domain hosts #9479 [purp]
-
-* Fix syntax error in documentation example for cycle method. Closes #8735 [foca]
-
-* Document :with option for link_to_remote. Closes #8765 [Ryan Bates]
-
-* Document :minute_step option for time_select. Closes #8814 [brupm]
-
-* Explain how to use the :href option for link_to_remote to degrade gracefully in the absence of JavaScript. Closes #8911 [vlad]
-
-* Disambiguate :size option for text area tag. Closes #8955 [redbeard]
-
-* Fix broken tag in assert_tag documentation. Closes #9037 [mfazekas]
-
-* Add documentation for route conditions. Closes #9041 [innu, Manfred Stienstra]
-
-* Fix typo left over from previous typo fix in url helper. Closes #9414 [Henrik N]
-
-* Fixed that ActionController::CgiRequest#host_with_port() should handle standard port #10082 [moro]
-
-* Update Prototype to 1.6.0 and script.aculo.us to 1.8.0. [sam, madrobby]
-
-* Expose the cookie jar as a helper method (before the view would just get the raw cookie hash) [David Heinemeier Hansson]
-
-* Integration tests: get_ and post_via_redirect take a headers hash. #9130 [simonjefford]
-
-* Simplfy #view_paths implementation. ActionView templates get the exact object, not a dup. [Rick Olson]
-
-* Update tests for ActiveSupport's JSON escaping change. [Rick Olson]
-
-* FormHelper's auto_index should use #to_param instead of #id_before_type_cast. Closes #9994 [mattly]
-
-* Doc typo fixes for ActiveRecordHelper. Closes #9973 [mikong]
-
-* Make example parameters in restful routing docs idiomatic. Closes #9993 [Jack Danger Canty]
-
-* Make documentation comment for mime responders match documentation example. Closes #9357 [yon]
-
-* Introduce a new test case class for functional tests. ActionController::TestCase. [Michael Koziarski]
-
-* Fix incorrect path in helper rdoc. Closes #9926 [viktor tron]
-
-* Partials also set 'object' to the default partial variable. #8823 [Nick Retallack, Jeremy Kemper]
-
-* Request profiler. [Jeremy Kemper]
- $ cat login_session.rb
- get_with_redirect '/'
- say "GET / => #{path}"
- post_with_redirect '/sessions', :username => 'john', :password => 'doe'
- say "POST /sessions => #{path}"
- $ ./script/performance/request -n 10 login_session.rb
-
-* Disabled checkboxes don't submit a form value. #9301 [vladr, robinjfisher]
-
-* Added tests for options to ActiveRecordHelper#form. Closes #7213 [richcollins, mikong, Mislav Marohnić]
-
-* Changed before_filter halting to happen automatically on render or redirect but no longer on simply returning false [David Heinemeier Hansson]
-
-* Ensure that cookies handle array values correctly. Closes #9937 [queso]
-
-* Make sure resource routes don't clash with internal helpers like javascript_path, image_path etc. #9928 [Geoff Buesing]
-
-* caches_page uses a single after_filter instead of one per action. #9891 [Pratik Naik]
-
-* Update Prototype to 1.6.0_rc1 and script.aculo.us to 1.8.0 preview 0. [sam, madrobby]
-
-* Dispatcher: fix that to_prepare should only run once in production. #9889 [Nathaniel Talbott]
-
-* Memcached sessions: add session data on initialization; don't silently discard exceptions; add unit tests. #9823 [kamk]
-
-* error_messages_for also takes :message and :header_message options which defaults to the old "There were problems with the following fields:" and "<count> errors prohibited this <object_name> from being saved". #8270 [rmm5t, zach-inglis-lt3]
-
-* Make sure that custom inflections are picked up by map.resources. #9815 [Mislav Marohnić]
-
-* Changed SanitizeHelper#sanitize to only allow the custom attributes and tags when specified in the call [David Heinemeier Hansson]
-
-* Extracted sanitization methods from TextHelper to SanitizeHelper [David Heinemeier Hansson]
-
-* rescue_from accepts :with => lambda { |exception| ... } or a normal block. #9827 [Pratik Naik]
-
-* Add :status to redirect_to allowing users to choose their own response code without manually setting headers. #8297 [Coda Hale, chasgrundy]
-
-* Add link_to :back which uses your referrer with a fallback to a javascript link. #7366 [eventualbuddha, Tarmo Tänav]
-
-* error_messages_for and friends also work with local variables. #9699 [Frederick Cheung]
-
-* Fix url_for, redirect_to, etc. with :controller => :symbol instead of 'string'. #8562, #9525 [Justin Lynn, Tarmo Tänav, shoe]
-
-* Use #require_library_or_gem to load the memcache library for the MemCache session and fragment cache stores. Closes #8662. [Rick Olson]
-
-* Move ActionController::Routing.optimise_named_routes to ActionController::Base.optimise_named_routes. Now you can set it in the config. [Rick Olson]
-
- config.action_controller.optimise_named_routes = false
-
-* ActionController::Routing::DynamicSegment#interpolation_chunk should call #to_s on all values before calling URI.escape. [Rick Olson]
-
-* Only accept session ids from cookies, prevents session fixation attacks. [bradediger]
-
-
-*2.0.0 [Preview Release]* (September 29th, 2007) [Includes duplicates of changes from 1.12.2 - 1.13.3]
-
-* Fixed that render template did not honor exempt_from_layout #9698 [pezra]
-
-* Better error messages if you leave out the :secret option for request forgery protection. Closes #9670 [Rick Olson]
-
-* Allow ability to disable request forgery protection, disable it in test mode by default. Closes #9693 [Pratik Naik]
-
-* Avoid calling is_missing on LoadErrors. Closes #7460. [ntalbott]
-
-* Move Railties' Dispatcher to ActionController::Dispatcher, introduce before_ and after_dispatch callbacks, and warm up to non-CGI requests. [Jeremy Kemper]
-
-* The tag helper may bypass escaping. [Jeremy Kemper]
-
-* Cache asset ids. [Jeremy Kemper]
-
-* Optimized named routes respect AbstractRequest.relative_url_root. #9612 [Daniel Morrison, Jeremy Kemper]
-
-* Introduce ActionController::Base.rescue_from to declare exception-handling methods. Cleaner style than the case-heavy rescue_action_in_public. #9449 [Norbert Crombach]
-
-* Rename some RequestForgeryProtection methods. The class method is now #protect_from_forgery, and the default parameter is now 'authenticity_token'. [Rick Olson]
-
-* Merge csrf_killer plugin into rails. Adds RequestForgeryProtection model that verifies session-specific _tokens for non-GET requests. [Rick Olson]
-
-* Secure #sanitize, #strip_tags, and #strip_links helpers against xss attacks. Closes #8877. [Rick Olson, Pratik Naik, Jacques Distler]
-
- This merges and renames the popular white_list helper (along with some css sanitizing from Jacques Distler version of the same plugin).
- Also applied updated versions of #strip_tags and #strip_links from #8877.
-
-* Remove use of & logic operator. Closes #8114. [watson]
-
-* Fixed JavaScriptHelper#escape_javascript to also escape closing tags #8023 [Ruy Asan]
-
-* Fixed TextHelper#word_wrap for multiline strings with extra carrier returns #8663 [seth]
-
-* Fixed that setting the :host option in url_for would automatically turn off :only_path (since :host would otherwise not be shown) #9586 [Bounga]
-
-* Added FormHelper#label. #8641, #9850 [jcoglan, Jarkko Laine]
-
-* Added AtomFeedHelper (slightly improved from the atom_feed_helper plugin) [David Heinemeier Hansson]
-
-* Prevent errors when generating routes for uncountable resources, (i.e. sheep where plural == singluar). map.resources :sheep now creates sheep_index_url for the collection and sheep_url for the specific item. [Michael Koziarski]
-
-* Added support for HTTP Only cookies (works in IE6+ and FF 2.0.5+) as an improvement for XSS attacks #8895 [Pratik Naik, Mark Somerville]
-
-* Don't warn when a path segment precedes a required segment. Closes #9615. [Nicholas Seckar]
-
-* Fixed CaptureHelper#content_for to work with the optional content parameter instead of just the block #9434 [sandofsky/wildchild].
-
-* Added Mime::Type.register_alias for dealing with different formats using the same mime type [David Heinemeier Hansson]. Example:
-
- class PostsController < ApplicationController
- before_filter :adjust_format_for_iphone
-
- def index
- @posts = Post.find(:all)
-
- respond_to do |format|
- format.html # => renders index.html.erb and uses "text/html" as the content type
- format.iphone # => renders index.iphone.erb and uses "text/html" as the content type
- end
- end
-
-
- private
- def adjust_format_for_iphone
- if request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/iPhone/]
- request.format = :iphone
- end
- end
- end
-
-* Added that render :json will automatically call .to_json unless it's being passed a string [David Heinemeier Hansson].
-
-* Autolink behaves well with emails embedded in URLs. #7313 [Jeremy McAnally, Tarmo Tänav]
-
-* Fixed that default layouts did not take the format into account #9564 [Pratik Naik]
-
-* Fixed optimized route segment escaping. #9562 [wildchild, Jeremy Kemper]
-
-* Added block acceptance to JavaScriptHelper#javascript_tag. #7527 [Bob Silva, Tarmo Tänav, rmm5t]
-
-* root_path returns '/' not ''. #9563 [Pratik Naik]
-
-* Fixed that setting request.format should also affect respond_to blocks [David Heinemeier Hansson]
-
-* Add option to force binary mode on tempfile used for fixture_file_upload. #6380 [Jonathan Viney]
-
-* Fixed that resource namespaces wouldn't stick to all nested resources #9399 [pixeltrix]
-
-* Moved ActionController::Macros::AutoComplete into the auto_complete plugin on the official Rails svn. #9512 [Pratik Naik]
-
-* Moved ActionController::Macros::InPlaceEditing into the in_place_editor plugin on the official Rails svn. #9513 [Pratik Naik]
-
-* Removed deprecated form of calling xml_http_request/xhr without the first argument being the http verb [David Heinemeier Hansson]
-
-* Removed deprecated methods [David Heinemeier Hansson]:
-
- - ActionController::Base#keep_flash (use flash.keep instead)
- - ActionController::Base#expire_matched_fragments (just call expire_fragment with a regular expression)
- - ActionController::Base.template_root/= methods (use ActionController#Base.view_paths/= instead)
- - ActionController::Base.cookie (use ActionController#Base.cookies[]= instead)
-
-* Removed the deprecated behavior of appending ".png" to image_tag/image_path calls without an existing extension [David Heinemeier Hansson]
-
-* Removed ActionController::Base.scaffold -- it went through the whole idea of scaffolding (card board walls you remove and tweak one by one). Use the scaffold generator instead (it does resources too now!) [David Heinemeier Hansson]
-
-* Optimise named route generation when using positional arguments. [Michael Koziarski]
-
- This change delivers significant performance benefits for the most
- common usage scenarios for modern rails applications by avoiding the
- costly trip through url_for. Initial benchmarks indicate this is
- between 6 and 20 times as fast.
-
-* Explicitly require active_record/query_cache before using it. [Jeremy Kemper]
-
-* Fix layout overriding response status. #9476 [lotswholetime]
-
-* Add field_set_tag for generating field_sets, closes #9477. [Damian Janowski]
-
-* Allow additional parameters to be passed to named route helpers when using positional arguments. Closes #8930 [Ian White]
-
-* Make render :partial work with a :collection of Hashes, previously this wasn't possible due to backwards compatibility restrictions. [Pratik Naik]
-
-* request.host works with IPv6 addresses. #9458 [yuya]
-
-* Fix bug where action caching sets the content type to the ActionCachePath object. Closes #9282 [mindforge]
-
-* Find layouts even if they're not in the first view_paths directory. Closes #9258 [caio]
-
-* Major improvement to the documentation for the options / select form helpers. Closes #9038 [Chris Kampmeier, jardeon, wesg]
-
-* Fix number_to_human_size when using different precisions. Closes #7536. [RichardStrand, mpalmer]
-
-* Added partial layouts (see example in action_view/lib/partials.rb) [David Heinemeier Hansson]
-
-* Allow you to set custom :conditions on resource routes. [Rick Olson]
-
-* Fixed that file.content_type for uploaded files would include a trailing \r #9053 [Brad Greenlee]
-
-* url_for now accepts a series of symbols representing the namespace of the record [Josh Knowles]
-
-* Make :trailing_slash work with query parameters for url_for. Closes #4004 [nov]
-
-* Make sure missing template exceptions actually say which template they were looking for. Closes #8683 [dasil003]
-
-* Fix errors with around_filters which do not yield, restore 1.1 behaviour with after filters. Closes #8891 [Stefan Kaes]
-
- After filters will *no longer* be run if an around_filter fails to yield, users relying on
- this behaviour are advised to put the code in question after a yield statement in an around filter.
-
-
-* Allow you to delete cookies with options. Closes #3685 [Josh Peek, Chris Wanstrath]
-
-* Allow you to render views with periods in the name. Closes #8076 [Norbert Crombach]
-
- render :partial => 'show.html.erb'
-
-* Improve capture helper documentation. #8796 [Chris Kampmeier]
-
-* Prefix nested resource named routes with their action name, e.g. new_group_user_path(@group) instead of group_new_user_path(@group). The old nested action named route is deprecated in Rails 1.2.4. #8558 [David Chelimsky]
-
-* Allow sweepers to be created solely for expiring after controller actions, not model changes [David Heinemeier Hansson]
-
-* Added assigns method to ActionController::Caching::Sweeper to easily access instance variables on the controller [David Heinemeier Hansson]
-
-* Give the legacy X-POST_DATA_FORMAT header greater precedence during params parsing for backward compatibility. [Jeremy Kemper]
-
-* Fixed that link_to with an href of # when using :method will not allow for click-through without JavaScript #7037 [Steven Bristol, Josh Peek]
-
-* Fixed that radio_button_tag should generate unique ids #3353 [Bob Silva, Rebecca, Josh Peek]
-
-* Fixed that HTTP authentication should work if the header is called REDIRECT_X_HTTP_AUTHORIZATION as well #6754 [Mislav Marohnić]
-
-* Don't mistakenly interpret the request uri as the query string. #8731 [Pratik Naik, Jeremy Kemper]
-
-* Make ActionView#view_paths an attr_accessor for real this time. Also, don't perform an unnecessary #compact on the @view_paths array in #initialize. Closes #8582 [dasil003, julik, Rick Olson]
-
-* Tolerate missing content type on multipart file uploads. Fix for Safari 3. [Jeremy Kemper]
-
-* Deprecation: remove pagination. Install the classic_pagination plugin for forward compatibility, or move to the superior will_paginate plugin. #8157 [Josh Peek]
-
-* Action caching is limited to GET requests returning 200 OK status. #3335 [tom@craz8.com, halfbyte, Dan Kubb, Josh Peek]
-
-* Improve Text Helper test coverage. #7274 [Rob Sanheim, Josh Peek]
-
-* Improve helper test coverage. #7208, #7212, #7215, #7233, #7234, #7235, #7236, #7237, #7238, #7241, #7243, #7244 [Rich Collins, Josh Peek]
-
-* Improve UrlRewriter tests. #7207 [Rich Collins]
-
-* Resources: url_for([parent, child]) generates /parents/1/children/2 for the nested resource. Likewise with the other simply helpful methods like form_for and link_to. #6432 [mhw, Jonathan Vaught, lotswholetime]
-
-* Assume html format when rendering partials in RJS. #8076 [Rick Olson]
-
-* Don't double-escape url_for in views. #8144 [Rich Collins, Josh Peek]
-
-* Allow JSON-style values for the :with option of observe_field. Closes #8557 [kommen]
-
-* Remove RAILS_ROOT from backtrace paths. #8540 [Tim Pope]
-
-* Routing: map.resource :logo routes to LogosController so the controller may be reused for multiple nestings or namespaces. [Jeremy Kemper]
-
-* render :partial recognizes Active Record associations as Arrays. #8538 [Kamal Fariz Mahyuddin]
-
-* Routing: drop semicolon and comma as route separators. [Jeremy Kemper]
-
-* request.remote_ip understands X-Forwarded-For addresses with nonstandard whitespace. #7386 [moses]
-
-* Don't prepare response when rendering a component. #8493 [jsierles]
-
-* Reduce file stat calls when checking for template changes. #7736 [alex]
-
-* Added custom path cache_page/expire_page parameters in addition to the options hashes [David Heinemeier Hansson]. Example:
-
- def index
- caches_page(response.body, "/index.html")
- end
-
-* Action Caching speedup. #8231 [Stefan Kaes]
-
-* Wordsmith resources documentation. #8484 [marclove]
-
-* Fix syntax error in code example for routing documentation. #8377. [Norbert Crombach]
-
-* Routing: respond with 405 Method Not Allowed status when the route path matches but the HTTP method does not. #6953 [Josh Peek, defeated, Dan Kubb, Coda Hale]
-
-* Add support for assert_select_rjs with :show and :hide. #7780 [dchelimsky]
-
-* Make assert_select's failure messages clearer about what failed. #7779 [dchelimsky]
-
-* Introduce a default respond_to block for custom types. #8174 [Josh Peek]
-
-* auto_complete_field takes a :method option so you can GET or POST. #8120 [zapnap]
-
-* Added option to suppress :size when using :maxlength for FormTagHelper#text_field #3112 [Tim Pope]
-
-* catch possible WSOD when trying to render a missing partial. Closes #8454 [Jonathan del Strother]
-
-* Rewind request body after reading it, if possible. #8438 [s450r1]
-
-* Resource namespaces are inherited by their has_many subresources. #8280 [marclove, Geoff Garside]
-
-* Fix filtered parameter logging with nil parameter values. #8422 [choonkeat]
-
-* Integration tests: alias xhr to xml_http_request and add a request_method argument instead of always using POST. #7124 [Nik Wakelin, François Beausoleil, Wizard]
-
-* Document caches_action. #5419 [Jarkko Laine]
-
-* Update to Prototype 1.5.1. [Sam Stephenson]
-
-* Allow routes to be decalred under namespaces [Tobias Lütke]:
-
- map.namespace :admin do |admin|
- admin.root :controller => "products"
- admin.feed 'feed.xml', :controller => 'products', :action => 'feed', :format => 'xml'
- end
-
-* Update to script.aculo.us 1.7.1_beta3. [Thomas Fuchs]
-
-* observe_form always sends the serialized form. #5271 [Manfred Stienstra, normelton@gmail.com]
-
-* Parse url-encoded and multipart requests ourselves instead of delegating to CGI. [Jeremy Kemper]
-
-* select :include_blank option can be set to a string instead of true, which just uses an empty string. #7664 [Wizard]
-
-* Added url_for usage on render :location, which allows for record identification [David Heinemeier Hansson]. Example:
-
- render :xml => person, :status => :created, :location => person
-
- ...expands the location to person_url(person).
-
-* Introduce the request.body stream. Lazy-read to parse parameters rather than always setting RAW_POST_DATA. Reduces the memory footprint of large binary PUT requests. [Jeremy Kemper]
-
-* Add some performance enhancements to ActionView.
-
- * Cache base_paths in @@cached_base_paths
- * Cache template extensions in @@cached_template_extension
- * Remove unnecessary rescues
-
-* Assume that rendered partials go by the HTML format by default
-
- def my_partial
- render :update do |page|
- # in this order
- # _foo.html.erb
- # _foo.erb
- # _foo.rhtml
- page.replace :foo, :partial => 'foo'
- end
- end
-
-* Added record identifications to FormHelper#form_for and PrototypeHelper#remote_form_for [David Heinemeier Hansson]. Examples:
-
- <% form_for(@post) do |f| %>
- ...
- <% end %>
-
- This will expand to be the same as:
-
- <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
- ...
- <% end %>
-
- And for new records:
-
- <% form_for(Post.new) do |f| %>
- ...
- <% end %>
-
- This will expand to be the same as:
-
- <% form_for :post, @post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
- ...
- <% end %>
-
-* Rationalize route path escaping according to RFC 2396 section 3.3. #7544, #8307. [Jeremy Kemper, Chris Roos, begemot, jugend]
-
-* Added record identification with polymorphic routes for ActionController::Base#url_for and ActionView::Base#url_for [David Heinemeier Hansson]. Examples:
-
- redirect_to(post) # => redirect_to(posts_url(post)) => Location: http://example.com/posts/1
- link_to(post.title, post) # => link_to(post.title, posts_url(post)) => <a href="/posts/1">Hello world</a>
-
- Any method that calls url_for on its parameters will automatically benefit from this.
-
-* Removed deprecated parameters_for_method_reference concept (legacy from before named routes) [David Heinemeier Hansson]
-
-* Add ActionController::Routing::Helpers, a module to contain common URL helpers such as polymorphic_url. [Nicholas Seckar]
-
-* Included the HttpAuthentication plugin as part of core (ActionController::HttpAuthentication::Basic) [David Heinemeier Hansson]
-
-* Modernize documentation for form helpers. [Jeremy McAnally]
-
-* Add brief introduction to REST to the resources documentation. [fearoffish]
-
-* Fix various documentation typos throughout ActionPack. [Henrik N]
-
-* Enhance documentation and add examples for url_for. [Jeremy McAnally]
-
-* Fix documentation typo in routes. [Norbert Crombach, pam]
-
-* Sweep flash when filter chain is halted. [Caio Chassot <lists@v2studio.com>]
-
-* Fixed that content_tag with a block will just return the result instead of concate it if not used in a ERb view #7857, #7432 [michael.niessner]
-
-* Replace the current block/continuation filter chain handling by an implementation based on a simple loop. #8226 [Stefan Kaes]
-
-* Update UrlWriter to accept :anchor parameter. Closes #6771. [Chris McGrath]
-
-* Added RecordTagHelper for using RecordIdentifier conventions on divs and other container elements [David Heinemeier Hansson]. Example:
-
- <% div_for(post) do %> <div id="post_45" class="post">
- <%= post.body %> What a wonderful world!
- <% end %> </div>
-
-* Added page[record] accessor to JavaScriptGenerator that relies on RecordIdentifier to find the right dom id [David Heinemeier Hansson]. Example:
-
- format.js do
- # Calls: new Effect.fade('post_45');
- render(:update) { |page| page[post].visual_effect(:fade) }
- end
-
-* Added RecordIdentifier to enforce view conventions on records for dom ids, classes, and partial paths [David Heinemeier Hansson]
-
-* Added map.namespace to deal with the common situation of admin sections and the like [David Heinemeier Hansson]
-
- Before:
-
- map.resources :products, :path_prefix => "admin", :controller => "admin/products", :collection => { :inventory => :get }, :member => { :duplicate => :post }
- map.resources :tags, :name_prefix => 'admin_product_', :path_prefix => "admin/products/:product_id", :controller => "admin/product_tags"
- map.resources :images, :name_prefix => 'admin_product_', :path_prefix => "admin/products/:product_id", :controller => "admin/product_images"
- map.resources :variants, :name_prefix => 'admin_product_', :path_prefix => "admin/products/:product_id", :controller => "admin/product_variants"
-
- After:
-
- map.namespace(:admin) do |admin|
- admin.resources :products,
- :collection => { :inventory => :get },
- :member => { :duplicate => :post },
- :has_many => [ :tags, :images, :variants ]
- end
-
-* Added :name_prefix as standard for nested resources [David Heinemeier Hansson]. WARNING: May be backwards incompatible with your app
-
- Before:
-
- map.resources :emails do |emails|
- emails.resources :comments, :name_prefix => "email_"
- emails.resources :attachments, :name_prefix => "email_"
- end
-
- After:
-
- map.resources :emails do |emails|
- emails.resources :comments
- emails.resources :attachments
- end
-
- This does mean that if you intended to have comments_url go to /emails/5/comments, then you'll have to set :name_prefix to nil explicitly.
-
-* Added :has_many and :has_one for declaring plural and singular resources beneath the current [David Heinemeier Hansson]
-
- Before:
-
- map.resources :notes do |notes|
- notes.resources :comments
- notes.resources :attachments
- notes.resource :author
- end
-
- After:
-
- map.resources :notes, :has_many => [ :comments, :attachments ], :has_one => :author
-
-* Added that render :xml will try to call to_xml if it can [David Heinemeier Hansson]. Makes these work:
-
- render :xml => post
- render :xml => comments
-
-* Added :location option to render so that the common pattern of rendering a response after creating a new resource is now a 1-liner [David Heinemeier Hansson]
-
- render :xml => post.to_xml, :status => :created, :location => post_url(post)
-
-* Ensure that render_text only adds string content to the body of the response [David Heinemeier Hansson]
-
-* Return the string representation from an Xml Builder when rendering a partial. Closes #5044 [Tim Pope]
-
-* Fixed that parameters from XML should also be presented in a hash with indifferent access [David Heinemeier Hansson]
-
-* Tweak template format rules so that the ACCEPT header is only used if it's text/javascript. This is so ajax actions without a :format param get recognized as Mime::JS. [Rick Olson]
-
-* The default respond_to blocks don't set a specific extension anymore, so that both 'show.rjs' and 'show.js.rjs' will work. [Rick Olson]
-
-* Allow layouts with extension of .html.erb. Closes #8032 [Josh Knowles]
-
-* Change default respond_to templates for xml and rjs formats. [Rick Olson]
-
- * Default xml template goes from #{action_name}.rxml => #{action_name}.xml.builder.
- * Default rjs template goes from #{action_name}.rjs => #{action_name}.js.rjs.
-
- You can still specify your old templates:
-
- respond_to do |format|
- format.xml do
- render :action => "#{action_name}.rxml"
- end
- end
-
-* Fix WSOD due to modification of a formatted template extension so that requests to templates like 'foo.html.erb' fail on the second hit. [Rick Olson]
-
-* Fix WSOD when template compilation fails [Rick Olson]
-
-* Change ActionView template defaults. Look for templates using the request format first, such as "show.html.erb" or "show.xml.builder", before looking for the old defaults like "show.erb" or "show.builder" [Rick Olson]
-
-* Highlight helper highlights one or many terms in a single pass. [Jeremy Kemper]
-
-* Dropped the use of ; as a separator of non-crud actions on resources and went back to the vanilla slash. It was a neat idea, but lots of the non-crud actions turned out not to be RPC (as the ; was primarily intended to discourage), but legitimate sub-resources, like /parties/recent, which didn't deserve the uglification of /parties;recent. Further more, the semicolon caused issues with caching and HTTP authentication in Safari. Just Not Worth It [David Heinemeier Hansson]
-
-* Added that FormTagHelper#submit_tag will return to its original state if the submit fails and you're using :disable_with [David Heinemeier Hansson]
-
-* Cleaned up, corrected, and mildly expanded ActionPack documentation. Closes #7190 [Jeremy McAnally]
-
-* Small collection of ActionController documentation cleanups. Closes #7319 [Jeremy McAnally]
-
-* Make sure the route expiry hash is constructed by comparing the to_param-ized values of each hash. [Jamis Buck]
-
-* Allow configuration of the default action cache path for #caches_action calls. [Rick Olson]
-
- class ListsController < ApplicationController
- caches_action :index, :cache_path => Proc.new { |controller|
- controller.params[:user_id] ?
- controller.send(:user_lists_url, c.params[:user_id]) :
- controller.send(:lists_url) }
- end
-
-* Performance: patch cgi/session/pstore to require digest/md5 once rather than per #initialize. #7583 [Stefan Kaes]
-
-* Cookie session store: ensure that new sessions doesn't reuse data from a deleted session in the same request. [Jeremy Kemper]
-
-* Deprecation: verification with :redirect_to => :named_route shouldn't be deprecated. #7525 [Justin French]
-
-* Cookie session store: raise ArgumentError when :session_key is blank. [Jeremy Kemper]
-
-* Deprecation: remove deprecated request, redirect, and dependency methods. Remove deprecated instance variables. Remove deprecated url_for(:symbol, *args) and redirect_to(:symbol, *args) in favor of named routes. Remove uses_component_template_root for toplevel components directory. Privatize deprecated render_partial and render_partial_collection view methods. Remove deprecated link_to_image, link_image_to, update_element_function, start_form_tag, and end_form_tag helper methods. Remove deprecated human_size helper alias. [Jeremy Kemper]
-
-* Consistent public/protected/private visibility for chained methods. #7813 [Dan Manges]
-
-* Prefer MIME constants to strings. #7707 [Dan Kubb]
-
-* Allow array and hash query parameters. Array route parameters are converted/to/a/path as before. #6765, #7047, #7462 [bgipsy, Jeremy McAnally, Dan Kubb, brendan]
-
-# Add a #dbman attr_reader for CGI::Session and make CGI::Session::CookieStore#generate_digest public so it's easy to generate digests
-using the cookie store's secret. [Rick Olson]
-
-* Added Request#url that returns the complete URL used for the request [David Heinemeier Hansson]
-
-* Extract dynamic scaffolding into a plugin. #7700 [Josh Peek]
-
-* Added user/password options for url_for to add http authentication in a URL [David Heinemeier Hansson]
-
-* Fixed that FormTagHelper#text_area_tag should disregard :size option if it's not a string [Brendon Davidson]
-
-* Set the original button value in an attribute of the button when using the :disable_with key with submit_tag, so that the original can be restored later. [Jamis Buck]
-
-* session_enabled? works with session :off. #6680 [Jonathan del Strother]
-
-* Added :port and :host handling to UrlRewriter (which unified url_for usage, regardless of whether it's called in view or controller) #7616 [alancfrancis]
-
-* Allow send_file/send_data to use a registered mime type as the :type parameter #7620 [jonathan]
-
-* Allow routing requirements on map.resource(s) #7633 [quixoten]. Example:
-
- map.resources :network_interfaces, :requirements => { :id => /^\d+\.\d+\.\d+\.\d+$/ }
-
-* Cookie session store: empty and unchanged sessions don't write a cookie. [Jeremy Kemper]
-
-* Added helper(:all) as a way to include all helpers from app/helpers/**/*.rb in ApplicationController [David Heinemeier Hansson]
-
-* Integration tests: introduce methods for other HTTP methods. #6353 [caboose]
-
-* Routing: better support for escaped values in route segments. #7544 [Chris
-Roos]
-
-* Introduce a cookie-based session store as the Rails default. Sessions typically contain at most a user_id and flash message; both fit within the 4K cookie size limit. A secure message digest is included with the cookie to ensure data integrity (a user cannot alter his user_id without knowing the secret key included in the digest). If you have more than 4K of session data or don't want your data to be visible to the user, pick another session store. Cookie-based sessions are dramatically faster than the alternatives. [Jeremy Kemper]
-
- Example config/environment.rb:
- # Use an application-wide secret key and the default SHA1 message digest.
- config.action_controller.session = { :secret => "can't touch this" }
-
- # Store a secret key per user and employ a stronger message digest.
- config.action_controller.session = {
- :digest => 'SHA512',
- :secret => Proc.new { User.current.secret_key }
- }
-
-* Added .erb and .builder as preferred aliases to the now deprecated .rhtml and .rxml extensions [Chad Fowler]. This is done to separate the renderer from the mime type. .erb templates are often used to render emails, atom, csv, whatever. So labeling them .rhtml doesn't make too much sense. The same goes for .rxml, which can be used to build everything from HTML to Atom to whatever. .rhtml and .rxml will continue to work until Rails 3.0, though. So this is a slow phasing out. All generators and examples will start using the new aliases, though.
-
-* Added caching option to AssetTagHelper#stylesheet_link_tag and AssetTagHelper#javascript_include_tag [David Heinemeier Hansson]. Examples:
-
- stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
- <link href="/stylesheets/style1.css" media="screen" rel="Stylesheet" type="text/css" />
- <link href="/stylesheets/styleB.css" media="screen" rel="Stylesheet" type="text/css" />
- <link href="/stylesheets/styleX2.css" media="screen" rel="Stylesheet" type="text/css" />
-
- stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
- <link href="/stylesheets/all.css" media="screen" rel="Stylesheet" type="text/css" />
-
- ...when caching is on, all.css is the concatenation of style1.css, styleB.css, and styleX2.css.
- Same deal for JavaScripts.
-
-* Work around the two connection per host browser limit: use asset%d.myapp.com to distribute asset requests among asset[0123].myapp.com. Use a DNS wildcard or CNAMEs to map these hosts to your asset server. See http://www.die.net/musings/page_load_time/ for background. [Jeremy Kemper]
-
-* Added default mime type for CSS (Mime::CSS) [David Heinemeier Hansson]
-
-* Added that rendering will automatically insert the etag header on 200 OK responses. The etag is calculated using MD5 of the response body. If a request comes in that has a matching etag, the response will be changed to a 304 Not Modified and the response body will be set to an empty string. [David Heinemeier Hansson]
-
-* Added X-Runtime to all responses with the request run time [David Heinemeier Hansson]
-
-* Add Mime::Type convenience methods to check the current mime type. [Rick Olson]
-
- request.format.html? # => true if Mime::HTML
- request.format.jpg? # => true if Mime::JPG
-
- # ActionController sample usage:
- # the session will be disabled for non html/ajax requests
- session :off, :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
-
-* Performance: patch cgi/session to require digest/md5 once rather than per #create_new_id. [Stefan Kaes]
-
-* Add a :url_based_filename => true option to ActionController::Streaming::send_file, which allows URL-based filenames. [Thomas Fuchs]
-
-* Fix that FormTagHelper#submit_tag using :disable_with should trigger the onsubmit handler of its form if available [David Heinemeier Hansson]
-
-* Fix #render_file so that TemplateError is called with the correct params and you don't get the WSOD. [Rick Olson]
-
-* Fix issue with deprecation messing up #template_root= usage. Add #prepend_view_path and #append_view_path to allow modification of a copy of the
-superclass' view_paths. [Rick Olson]
-
-* Allow Controllers to have multiple view_paths instead of a single template_root. Closes #2754 [John Long]
-
-* Add much-needed html-scanner tests. Fixed CDATA parsing bug. [Rick Olson]
-
-* improve error message for Routing for named routes. Closes #7346 [Rob Sanheim]
-
-* Added enhanced docs to routing assertions. Closes #7359 [Rob Sanheim]
-
-* fix form_for example in ActionController::Resources documentation. Closes #7362 [gnarg]
-
-* Make sure that the string returned by TextHelper#truncate is actually a string, not a char proxy -- that should only be used internally while working on a multibyte-safe way of truncating [David Heinemeier Hansson]
-
-* Added FormBuilder#submit as a delegate for FormTagHelper#submit_tag [David Heinemeier Hansson]
-
-* Allow Routes to generate all urls for a set of options by specifying :generate_all => true. Allows caching to properly set or expire all paths for a resource. References #1739. [Nicholas Seckar]
-
-* Change the query parser to map empty GET params to "" rather than nil. Closes #5694. [Nicholas Seckar]
-
-* date_select and datetime_select take a :default option. #7052 [Nik Wakelin]
- date_select "post", "written_on", :default => 3.days.from_now
- date_select "credit_card", "bill_due", :default => { :day => 20 }
-
-* select :multiple => true suffixes the attribute name with [] unless already suffixed. #6977 [nik.kakelin, ben, julik]
-
-* Improve routes documentation. #7095 [zackchandler]
-
-* mail_to :encode => 'hex' also encodes the mailto: part of the href attribute as well as the linked email when no name is given. #2061 [Jarkko Laine, pfc.pille@gmx.net]
-
-* Resource member routes require :id, eliminating the ambiguous overlap with collection routes. #7229 [dkubb]
-
-* Remove deprecated assertions. [Jeremy Kemper]
-
-* Change session restoration to allow namespaced models to be autoloaded. Closes #6348. [Nicholas Seckar]
-
-* Fix doubly appearing parameters due to string and symbol mixups. Closes #2551. [Anthony Eden]
-
-* Fix overly greedy rescues when loading helpers. Fixes #6268. [Nicholas Seckar]
-
-* Fixed NumberHelper#number_with_delimiter to use "." always for splitting the original number, not the delimiter parameter #7389 [ceefour]
-
-* Autolinking recognizes trailing and embedded . , : ; #7354 [Jarkko Laine]
-
-* Make TextHelper::auto_link recognize URLs with colons in path correctly, fixes #7268. [imajes]
-
-* Update to script.aculo.us 1.7.0. [Thomas Fuchs]
-
-* Modernize cookie testing code, and increase coverage (Heckle++) #7101 [Kevin Clark]
-
-* Improve Test Coverage for ActionController::Routing::Route#matches_controller_and_action? (Heckle++) #7115 [Kevin Clark]
-
-* Heckling ActionController::Resources::Resource revealed that set_prefixes didn't break when :name_prefix was munged. #7081 [Kevin Clark]
-
-* Fix #distance_of_time_in_words to report accurately against the Duration class. #7114 [eventualbuddha]
-
-* Refactor #form_tag to allow easy extending. [Rick Olson]
-
-* Update to Prototype 1.5.0. [Sam Stephenson]
-
-* RecordInvalid, RecordNotSaved => 422 Unprocessable Entity, StaleObjectError => 409 Conflict. #7097 [dkubb]
-
-* Allow fields_for to be nested inside form_for, so that the name and id get properly constructed [Jamis Buck]
-
-* Allow inGroupsOf and eachSlice to be called through rjs. #7046 [Cody Fauser]
-
-* Allow exempt_from_layout :rhtml. #6742, #7026 [Dan Manges, Squeegy]
-
-* Recognize the .txt extension as Mime::TEXT [Rick Olson]
-
-* Fix parsing of array[] CGI parameters so extra empty values aren't included. #6252 [Nicholas Seckar, aiwilliams, brentrowland]
-
-* link_to_unless_current works with full URLs as well as paths. #6891 [Jarkko Laine, Manfred Stienstra, idrifter]
-
-* Lookup the mime type for #auto_discovery_link_tag in the Mime::Type class. Closes #6941 [Josh Peek]
-
-* Fix bug where nested resources ignore a parent singleton parent's path prefix. Closes #6940 [Dan Kubb]
-
-* Fix no method error with error_messages_on. Closes #6935 [nik.wakelin Koz]
-
-* Slight doc tweak to the ActionView::Helpers::PrototypeHelper#replace docs. Closes #6922 [Steven Bristol]
-
-* Slight doc tweak to #prepend_filter. Closes #6493 [Jeremy Voorhis]
-
-* Add more extensive documentation to the AssetTagHelper. Closes #6452 [Bob Silva]
-
-* Clean up multiple calls to #stringify_keys in TagHelper, add better documentation and testing for TagHelper. Closes #6394 [Bob Silva]
-
-* [DOCS] fix reference to ActionController::Macros::AutoComplete for #text_field_with_auto_complete. Closes #2578 [Jan Prill]
-
-* Make sure html_document is reset between integration test requests. [ctm]
-
-* Set session to an empty hash if :new_session => false and no session cookie or param is present. CGI::Session was raising an unrescued ArgumentError. [Josh Susser]
-
-* Routing uses URI escaping for path components and CGI escaping for query parameters. [darix, Jeremy Kemper]
-
-* Fix assert_redirected_to bug where redirecting from a nested to to a top-level controller incorrectly added the current controller's nesting. Closes #6128. [Rick Olson]
-
-* Singleton resources: POST /singleton => create, GET /singleton/new => new. [Jeremy Kemper]
-
-* Use 400 Bad Request status for unrescued ActiveRecord::RecordInvalid exceptions. [Jeremy Kemper]
-
-* Silence log_error deprecation warnings from inspecting deprecated instance variables. [Nate Wiger]
-
-* Only cache GET requests with a 200 OK response. #6514, #6743 [RSL, anamba]
-
-* Add a 'referer' attribute to TestRequest. [Jamis Buck]
-
-* Ensure render :json => ... skips the layout. Closes #6808 [Josh Peek]
-
-* Fix HTML::Node to output double quotes instead of single quotes. Closes #6845 [mitreandy]
-
-* Correctly report which filter halted the chain. #6699 [Martin Emde]
-
-* Fix a bug in Routing where a parameter taken from the path of the current request could not be used as a query parameter for the next. Closes #6752. [Nicholas Seckar]
-
-* Unrescued ActiveRecord::RecordNotFound responds with 404 instead of 500. [Jeremy Kemper]
-
-* Improved auto_link to match more valid urls correctly [Tobias Lütke]
-
-* Add singleton resources. [Rick Olson]
-
- map.resource :account
-
- GET /account
- GET /account;edit
- UPDATE /account
- DELETE /account
-
-* respond_to recognizes JSON. render :json => @person.to_json automatically sets the content type and takes a :callback option to specify a client-side function to call using the rendered JSON as an argument. #4185 [Scott Raymond, eventualbuddha]
- # application/json response with body 'Element.show({:name: "David"})'
- respond_to do |format|
- format.json { render :json => { :name => "David" }.to_json, :callback => 'Element.show' }
- end
-
-* Makes :discard_year work without breaking multi-attribute parsing in AR. #1260, #3800 [sean@ardismg.com, jmartin@desertflood.com, stephen@touset.org, Bob Silva]
-
-* Adds html id attribute to date helper elements. #1050, #1382 [mortonda@dgrmm.net, David North, Bob Silva]
-
-* Add :index and @auto_index capability to model driven date/time selects. #847, #2655 [moriq, Doug Fales, Bob Silva]
-
-* Add :order to datetime_select, select_datetime, and select_date. #1427 [Timothee Peignier, Patrick Lenz, Bob Silva]
-
-* Added time_select to work with time values in models. Update scaffolding. #2489, #2833 [Justin Palmer, Andre Caum, Bob Silva]
-
-* Added :include_seconds to select_datetime, datetime_select and time_select. #2998 [csn, Bob Silva]
-
-* All date/datetime selects can now accept an array of month names with :use_month_names. Allows for localization. #363 [tomasj, Bob Silva]
-
-* Adds :time_separator to select_time and :date_separator to select_datetime. Preserves BC. #3811 [Bob Silva]
-
-* Added map.root as an alias for map.connect '' [David Heinemeier Hansson]
-
-* Added Request#format to return the format used for the request as a mime type. If no format is specified, the first Request#accepts type is used. This means you can stop using respond_to for anything else than responses [David Heinemeier Hansson]. Examples:
-
- GET /posts/5.xml | request.format => Mime::XML
- GET /posts/5.xhtml | request.format => Mime::HTML
- GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
-
-* Added the option for extension aliases to mime type registration [David Heinemeier Hansson]. Example (already in the default routes):
-
- Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
-
- ...will respond on both .html and .xhtml.
-
-* @response.redirect_url works with 201 Created responses: just return headers['Location'] rather than checking the response status. [Jeremy Kemper]
-
-* Added CSV to Mime::SET so that respond_to csv will work [Cody Fauser]
-
-* Fixed that HEAD should return the proper Content-Length header (that is, actually use @body.size, not just 0) [David Heinemeier Hansson]
-
-* Added GET-masquarading for HEAD, so request.method will return :get even for HEADs. This will help anyone relying on case request.method to automatically work with HEAD and map.resources will also allow HEADs to all GET actions. Rails automatically throws away the response content in a reply to HEAD, so you don't even need to worry about that. If you, for whatever reason, still need to distinguish between GET and HEAD in some edge case, you can use Request#head? and even Request.headers["REQUEST_METHOD"] for get the "real" answer. Closes #6694 [David Heinemeier Hansson]
-
-* Update Routing to complain when :controller is not specified by a route. Closes #6669. [Nicholas Seckar]
-
-* Ensure render_to_string cleans up after itself when an exception is raised. #6658 [Rob Sanheim]
-
-* Extract template_changed_since? from compile_template? so plugins may override its behavior for non-file-based templates. #6651 [Jeff Barczewski]
-
-* Update to Prototype and script.aculo.us [5579]. [Thomas Fuchs]
-
-* simple_format helper doesn't choke on nil. #6644 [jerry426]
-
-* Update to Prototype 1.5.0_rc2 [5550] which makes it work in Opera again [Thomas Fuchs]
-
-* Reuse named route helper module between Routing reloads. Use remove_method to delete named route methods after each load. Since the module is never collected, this fixes a significant memory leak. [Nicholas Seckar]
-
-* ActionView::Base.erb_variable accessor names the buffer variable used to render templates. Defaults to _erbout; use _buf for erubis. [Rick Olson]
-
-* assert_select_rjs :remove. [Dylan Egan]
-
-* Always clear model associations from session. #4795 [sd@notso.net, andylien@gmail.com]
-
-* Update to Prototype 1.5.0_rc2. [Sam Stephenson]
-
-* Remove JavaScriptLiteral in favor of ActiveSupport::JSON::Variable. [Sam Stephenson]
-
-* Sync ActionController::StatusCodes::STATUS_CODES with http://www.iana.org/assignments/http-status-codes. #6586 [dkubb]
-
-* Multipart form values may have a content type without being treated as uploaded files if they do not provide a filename. #6401 [Andreas Schwarz, Jeremy Kemper]
-
-* assert_response supports symbolic status codes. #6569 [Kevin Clark]
- assert_response :ok
- assert_response :not_found
- assert_response :forbidden
-
-* Cache parsed query parameters. #6559 [Stefan Kaes]
-
-* Deprecate JavaScriptHelper#update_element_function, which is superseeded by RJS [Thomas Fuchs]
-
-* pluralize helper interprets nil as zero. #6474 [Tim Pope]
-
-* Fix invalid test fixture exposed by stricter Ruby 1.8.5 multipart parsing. #6524 [Bob Silva]
-
-* Set ActionView::Base.default_form_builder once rather than passing the :builder option to every form or overriding the form helper methods. [Jeremy Kemper]
-
-* Deprecate expire_matched_fragments. Use expire_fragment instead. #6535 [Bob Silva]
-
-* Update to latest Prototype, which doesn't serialize disabled form elements, adds clone() to arrays, empty/non-string Element.update() and adds a fixes excessive error reporting in WebKit beta versions [Thomas Fuchs]
-
-* Deprecate start_form_tag and end_form_tag. Use form_tag / '</form>' from now on. [Rick Olson]
-
-* Added block-usage to PrototypeHelper#form_remote_tag, document block-usage of FormTagHelper#form_tag [Rick Olson]
-
-* Add a 0 margin/padding div around the hidden _method input tag that form_tag outputs. [Rick Olson]
-
-* Added block-usage to TagHelper#content_tag [David Heinemeier Hansson]. Example:
-
- <% content_tag :div, :class => "strong" %>
- Hello world!
- <% end %>
-
- Will output:
- <div class="strong">Hello world!</div>
-
-* Deprecated UrlHelper#link_to_image and UrlHelper#link_to :post => true #6409 [Bob Silva]
-
-* Upgraded NumberHelper with number_to_phone support international formats to comply with ITU E.123 by supporting area codes with less than 3 digits, added precision argument to number_to_human_size (defaults to 1) #6421 [Bob Silva]
-
-* Fixed that setting RAILS_ASSET_ID to "" should not add a trailing slash after assets #6454 [Bob Silva/chrismear]
-
-* Force *_url named routes to show the host in ActionView [Rick Olson]
-
- <%= url_for ... %> # no host
- <%= foo_path %> # no host
- <%= foo_url %> # host!
-
-* Add support for converting blocks into function arguments to JavaScriptGenerator#call and JavaScriptProxy#call. [Sam Stephenson]
-
-* Add JavaScriptGenerator#literal for wrapping a string in an object whose #to_json is the string itself. [Sam Stephenson]
-
-* Add <%= escape_once html %> to escape html while leaving any currently escaped entities alone. Fix button_to double-escaping issue. [Rick Olson]
-
-* Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc. [Rick Olson]
-
-* Fix deprecation warnings when rendering the template error template. [Nicholas Seckar]
-
-* Fix routing to correctly determine when generation fails. Closes #6300. [psross].
-
-* Fix broken assert_generates when extra keys are being checked. [Jamis Buck]
-
-* Replace KCODE checks with String#chars for truncate. Closes #6385 [Manfred Stienstra]
-
-* Make page caching respect the format of the resource that is being requested even if the current route is the default route so that, e.g. posts.rss is not transformed by url_for to '/' and subsequently cached as '/index.html' when it should be cached as '/posts.rss'. [Marcel Molina Jr.]
-
-* Use String#chars in TextHelper::excerpt. Closes #6386 [Manfred Stienstra]
-
-* Install named routes into ActionView::Base instead of proxying them to the view via helper_method. Closes #5932. [Nicholas Seckar]
-
-* Update to latest Prototype and script.aculo.us trunk versions [Thomas Fuchs]
-
-* Fix relative URL root matching problems. [Mark Imbriaco]
-
-* Fix filter skipping in controller subclasses. #5949, #6297, #6299 [Martin Emde]
-
-* render_text may optionally append to the response body. render_javascript appends by default. This allows you to chain multiple render :update calls by setting @performed_render = false between them (awaiting a better public API). [Jeremy Kemper]
-
-* Rename test assertion to prevent shadowing. Closes #6306. [psross]
-
-* Fixed that NumberHelper#number_to_delimiter should respect precision of higher than two digits #6231 [Philip Hallstrom]
-
-* Fixed that FormHelper#radio_button didn't respect an :id being passed in #6266 [evansj]
-
-* Added an html_options hash parameter to javascript_tag() and update_page_tag() helpers #6311 [tzaharia]. Example:
-
- update_page_tag :defer => 'true' { |page| ... }
-
- Gives:
-
- <script defer="true" type="text/javascript">...</script>
-
- Which is needed for dealing with the IE6 DOM when it's not yet fully loaded.
-
-* Fixed that rescue template path shouldn't be hardcoded, then it's easier to hook in your own #6295 [Mike Naberezny]
-
-* Fixed escaping of backslashes in JavaScriptHelper#escape_javascript #6302 [sven@c3d2.de]
-
-* Fixed that some 500 rescues would cause 500's themselves because the response had not yet been generated #6329 [cmselmer]
-
-* respond_to :html doesn't assume .rhtml. #6281 [Hampton Catlin]
-
-* Fixed some deprecation warnings in ActionPack [Rick Olson]
-
-* assert_select_rjs decodes escaped unicode chars since the Javascript generators encode them. #6240 [japgolly]
-
-* Deprecation: @cookies, @headers, @request, @response will be removed after 1.2. Use the corresponding method instead. [Jeremy Kemper]
-
-* Make the :status parameter expand to the default message for that status code if it is an integer. Also support symbol statuses. [Jamis Buck]. Examples:
-
- head :status => 404 # expands to "404 Not Found"
- head :status => :not_found # expands to "404 Not Found"
- head :status => :created # expands to "201 Created"
-
-* Add head(options = {}) for responses that have no body. [Jamis Buck]. Examples:
-
- head :status => 404 # return an empty response with a 404 status
- head :location => person_path(@person), :status => 201
-
-* Fix bug that kept any before_filter except the first one from being able to halt the before_filter chain. [Rick Olson]
-
-* strip_links is case-insensitive. #6285 [tagoh, Bob Silva]
-
-* Clear the cache of possible controllers whenever Routes are reloaded. [Nicholas Seckar]
-
-* Filters overhaul including meantime filter support using around filters + blocks. #5949 [Martin Emde, Roman Le Negrate, Stefan Kaes, Jeremy Kemper]
-
-* Update RJS render tests. [sam]
-
-* Update CGI process to allow sessions to contain namespaced models. Closes #4638. [dfelstead@site5.com]
-
-* Fix routing to respect user provided requirements and defaults when assigning default routing options (such as :action => 'index'). Closes #5950. [Nicholas Seckar]
-
-* Rescue Errno::ECONNRESET to handle an unexpectedly closed socket connection. Improves SCGI reliability. #3368, #6226 [sdsykes, fhanshaw@vesaria.com]
-
-* Added that respond_to blocks will automatically set the content type to be the same as is requested [David Heinemeier Hansson]. Examples:
-
- respond_to do |format|
- format.html { render :text => "I'm being sent as text/html" }
- format.rss { render :text => "I'm being sent as application/rss+xml" }
- format.atom { render :text => "I'm being sent as application/xml", :content_type => Mime::XML }
- end
-
-* Added utf-8 as the default charset for all renders. You can change this default using ActionController::Base.default_charset=(encoding) [David Heinemeier Hansson]
-
-* Added proper getters and setters for content type and charset [David Heinemeier Hansson]. Example of what we used to do:
-
- response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
-
- ...now:
-
- response.content_type = Mime::ATOM
- response.charset = "utf-8"
-
-* Updated prototype.js to 1.5.0_rc1 with latest fixes. [Rick Olson]
-
- - XPATH support
- - Make Form.getElements() return elements in the correct order
- - fix broken Form.serialize return
-
-* Declare file extensions exempt from layouts. #6219 [brandon]
- Example: ActionController::Base.exempt_from_layout 'rpdf'
-
-* Add chained replace/update support for assert_select_rjs [Rick Olson]
-
- Given RJS like...
-
- page['test1'].replace "<div id=\"1\">foo</div>"
- page['test2'].replace_html "<div id=\"2\">foo</div>"
-
- Test it with...
-
- assert_select_rjs :chained_replace
- assert_select_rjs :chained_replace, "test1"
-
- assert_select_rjs :chained_replace_html
- assert_select_rjs :chained_replace_html, "test2"
-
-* Load helpers in alphabetical order for consistency. Resolve cyclic javascript_helper dependency. #6132, #6178 [choonkeat@gmail.com]
-
-* Skip params with empty names, such as the &=Save query string from <input type="submit"/>. #2569 [Manfred Stienstra, raphinou@yahoo.com]
-
-* Fix assert_tag so that :content => "foo" does not match substrings, but only exact strings. Use :content => /foo/ to match substrings. #2799 [Eric Hodel]
-
-* Add descriptive messages to the exceptions thrown by cgi_methods. #6091, #6103 [Nicholas Seckar, Bob Silva]
-
-* Update JavaScriptGenerator#show/hide/toggle/remove to new Prototype syntax for multiple ids, #6068 [petermichaux@gmail.com]
-
-* Update UrlWriter to support :only_path. [Nicholas Seckar, Dave Thomas]
-
-* Fixed JavaScriptHelper#link_to_function and JavaScriptHelper#button_to_function to have the script argument be optional [David Heinemeier Hansson]. So what used to require a nil, like this:
-
- link_to("Hider", nil, :class => "hider_link") { |p| p[:something].hide }
-
- ...can be written like this:
-
- link_to("Hider", :class => "hider_link") { |p| p[:something].hide }
-
-* Update to script.aculo.us 1.6.3 [Thomas Fuchs]
-
-* Update to Prototype 1.5.0_rc1 [sam]
-
-* Added access to nested attributes in RJS #4548 [richcollins@gmail.com]. 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';
-
-* Fixed that AssetTagHelper#image_tag and others using compute_public_path should not modify the incoming source argument (closes #5102) [eule@space.ch]
-
-* Deprecated the auto-appending of .png to AssetTagHelper#image_tag calls that doesn't have an extension [David Heinemeier Hansson]
-
-* Fixed FormOptionsHelper#select to respect :selected value #5813
-
-* Fixed TextHelper#simple_format to deal with multiple single returns within a single paragraph #5835 [moriq@moriq.com]
-
-* Fixed TextHelper#pluralize to handle 1 as a string #5909 [rails@bencurtis.com]
-
-* Improved resolution of DateHelper#distance_of_time_in_words for better precision #5994 [Bob Silva]
-
-* Changed that uncaught exceptions raised any where in the application will cause RAILS_ROOT/public/500.html to be read and shown instead of just the static "Application error (Rails)" [David Heinemeier Hansson]
-
-* Integration tests: thoroughly test ActionController::Integration::Session. #6022 [Kevin Clark]
- (tests skipped unless you `gem install mocha`)
-
-* Added deprecation language for pagination which will become a plugin by Rails 2.0 [David Heinemeier Hansson]
-
-* Added deprecation language for in_place_editor and auto_complete_field that both pieces will become plugins by Rails 2.0 [David Heinemeier Hansson]
-
-* Deprecated all of ActionController::Dependencies. All dependency loading is now handled from Active Support [David Heinemeier Hansson]
-
-* Added assert_select* for CSS selector-based testing (deprecates assert_tag) #5936 [assaf.arkin@gmail.com]
-
-* radio_button_tag generates unique id attributes. #3353 [Bob Silva, somekool@gmail.com]
-
-* strip_tags passes through blank args such as nil or "". #2229, #6702 [duncan@whomwah.com, dharana]
-
-* Cleanup assert_tag :children counting. #2181 [jamie@bravenet.com]
-
-* button_to accepts :method so you can PUT and DELETE with it. #6005 [Dan Webb]
-
-* Update sanitize text helper to strip plaintext tags, and <img src="javascript:bang">. [Rick Olson]
-
-* Update routing documentation. Closes #6017 [Nathan Witmer]
-
-* Add routing tests to assert that RoutingError is raised when conditions aren't met. Closes #6016 [Nathan Witmer]
-
-* Deprecation: update docs. #5998 [Jakob Skjerning, Kevin Clark]
-
-* Make auto_link parse a greater subset of valid url formats. [Jamis Buck]
-
-* Integration tests: headers beginning with X aren't excluded from the HTTP_ prefix, so X-Requested-With becomes HTTP_X_REQUESTED_WITH as expected. [Mike Clark]
-
-* Tighten rescue clauses. #5985 [james@grayproductions.net]
-
-* Fix send_data documentation typo. #5982 [brad@madriska.com]
-
-* Switch to using FormEncodedPairParser for parsing request parameters. [Nicholas Seckar, David Heinemeier Hansson]
-
-* respond_to .html now always renders #{action_name}.rhtml so that registered custom template handlers do not override it in priority. Custom mime types require a block and throw proper error now. [Tobias Lütke]
-
-* Deprecation: test deprecated instance vars in partials. [Jeremy Kemper]
-
-* Add UrlWriter to allow writing urls from Mailers and scripts. [Nicholas Seckar]
-
-* Clean up and run the Active Record integration tests by default. #5854 [Kevin Clark, Jeremy Kemper]
-
-* Correct example in cookies docs. #5832 [jessemerriman@warpmail.net]
-
-* Updated to script.aculo.us 1.6.2 [Thomas Fuchs]
-
-* Relax Routing's anchor pattern warning; it was preventing use of [^/] inside restrictions. [Nicholas Seckar]
-
-* Add controller_paths variable to Routing. [Nicholas Seckar]
-
-* Fix assert_redirected_to issue with named routes for module controllers. [Rick Olson]
-
-* Tweak RoutingError message to show option diffs, not just missing named route significant keys. [Rick Olson]
-
-* Invoke method_missing directly on hidden actions. Closes #3030. [Nicholas Seckar]
-
-* Require Tempfile explicitly for TestUploadedFile due to changes in class auto loading. [Rick Olson]
-
-* Add RoutingError exception when RouteSet fails to generate a path from a Named Route. [Rick Olson]
-
-* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
-
-* Deprecation: check whether instance variables have been monkeyed with before assigning them to deprecation proxies. Raises a RuntimeError if so. [Jeremy Kemper]
-
-* Add support for the param_name parameter to the auto_complete_field helper. #5026 [david.a.williams@gmail.com]
-
-* Deprecation! @params, @session, @flash will be removed after 1.2. Use the corresponding instance methods instead. You'll get printed warnings during tests and logged warnings in dev mode when you access either instance variable directly. [Jeremy Kemper]
-
-* Make Routing noisy when an anchor regexp is assigned to a segment. #5674 [François Beausoleil]
-
-* Added months and years to the resolution of DateHelper#distance_of_time_in_words, such that "60 days ago" becomes "2 months ago" #5611 [pjhyett@gmail.com]
-
-* Short documentation to mention use of Mime::Type.register. #5710 [choonkeat@gmail.com]
-
-* Make controller_path available as an instance method. #5724 [jmckible@gmail.com]
-
-* Update query parser to support adjacent hashes. [Nicholas Seckar]
-
-* Make action caching aware of different formats for the same action so that, e.g. foo.xml is cached separately from foo.html. Implicitly set content type when reading in cached content with mime revealing extensions so the entire onous isn't on the webserver. [Marcel Molina Jr.]
-
-* Restrict Request Method hacking with ?_method to POST requests. [Rick Olson]
-
-* Fix bug when passing multiple options to SimplyRestful, like :new => { :preview => :get, :draft => :get }. [Rick Olson, Josh Susser, Lars Pind]
-
-* Dup the options passed to map.resources so that multiple resources get the same options. [Rick Olson]
-
-* Fixed the new_#{resource}_url route and added named route tests for Simply Restful. [Rick Olson]
-
-* Added map.resources from the Simply Restful plugin [David Heinemeier Hansson]. Examples (the API has changed to use plurals!):
-
- map.resources :messages
- map.resources :messages, :comments
- map.resources :messages, :new => { :preview => :post }
-
-* Fixed that integration simulation of XHRs should set Accept header as well [Edward Frederick]
-
-* TestRequest#reset_session should restore a TestSession, not a hash [Michael Koziarski]
-
-* Don't search a load-path of '.' for controller files [Jamis Buck]
-
-* Update integration.rb to require test_process explicitly instead of via Dependencies. [Nicholas Seckar]
-
-* Fixed that you can still access the flash after the flash has been reset in reset_session. Closes #5584 [lmarlow]
-
-* Allow form_for and fields_for to work with indexed form inputs. [Jeremy Kemper, Matt Lyon]
-
- <% form_for 'post[]', @post do |f| -%>
- <% end -%>
-
-* Remove leak in development mode by replacing define_method with module_eval. [Nicholas Seckar]
-
-* Provide support for decimal columns to form helpers. Closes #5672. [Dave Thomas]
-
-* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
-
-* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com]
-
-* Reset @html_document between requests so assert_tag works. #4810 [Jarkko Laine, easleydp@gmail.com]
-
-* Update render :partial documentation. #5646 [matt@mattmargolis.net]
-
-* Integration tests behave well with render_component. #4632 [edward.frederick@revolution.com, dev.rubyonrails@maxdunn.com]
-
-* Added exception handling of missing layouts #5373 [chris@ozmm.org]
-
-* Fixed that real files and symlinks should be treated the same when compiling templates #5438 [zachary@panandscan.com]
-
-* Fixed that the flash should be reset when reset_session is called #5584 [Shugo Maeda]
-
-* Added special case for "1 Byte" in NumberHelper#number_to_human_size #5593 [murpyh@rubychan.de]
-
-* Fixed proper form-encoded parameter parsing for requests with "Content-Type: application/x-www-form-urlencoded; charset=utf-8" (note the presence of a charset directive) [David Heinemeier Hansson]
-
-* Add route_name_path method to generate only the path for a named routes. For example, map.person will add person_path. [Nicholas Seckar]
-
-* Avoid naming collision among compiled view methods. [Jeremy Kemper]
-
-* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [Mislav Marohnić]
-
-* Determine the correct template_root for deeply nested components. #2841 [s.brink@web.de]
-
-* Fix that routes with *path segments in the recall can generate URLs. [Rick Olson]
-
-* Fix strip_links so that it doesn't hang on multiline <acronym> tags [Jamis Buck]
-
-* Remove problematic control chars in rescue template. #5316 [Stefan Kaes]
-
-* Make sure passed routing options are not mutated by routing code. #5314 [Blair Zajac]
-
-* Make sure changing the controller from foo/bar to bing/bang does not change relative to foo. [Jamis Buck]
-
-* Escape the path before routing recognition. #3671
-
-* Make sure :id and friends are unescaped properly. #5275 [me@julik.nl]
-
-* Fix documentation for with_routing to reflect new reality. #5281 [rramdas@gmail.com]
-
-* Rewind readable CGI params so others may reread them (such as CGI::Session when passing the session id in a multipart form). #210 [mklame@atxeu.com, matthew@walker.wattle.id.au]
-
-* Added Mime::TEXT (text/plain) and Mime::ICS (text/calendar) as new default types [David Heinemeier Hansson]
-
-* Added Mime::Type.register(string, symbol, synonyms = []) for adding new custom mime types [David Heinemeier Hansson]. Example: Mime::Type.register("image/gif", :gif)
-
-* Added support for Mime objects in render :content_type option [David Heinemeier Hansson]. Example: render :text => some_atom, :content_type => Mime::ATOM
-
-* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 [Manfred Stienstra <m.stienstra@fngtps.com>]
-
-* Routing rewrite. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes. [Nicholas Seckar, Jamis Buck]
-
- map.connect '/foo/:id', :controller => '...', :action => '...'
- map.connect '/foo/:id.:format', :controller => '...', :action => '...'
- map.connect '/foo/:id', ..., :conditions => { :method => :get }
-
-* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper]
-
-* Accept multipart PUT parameters. #5235 [guy.naor@famundo.com]
-
-* Added interrogation of params[:format] to determine Accept type. If :format is specified and matches a declared extension, like "rss" or "xml", that mime type will be put in front of the accept handler. This means you can link to the same action from different extensions and use that fact to determine output [David Heinemeier Hansson]. Example:
-
- class WeblogController < ActionController::Base
- def index
- @posts = Post.find :all
-
- respond_to do |format|
- format.html
- format.xml { render :xml => @posts.to_xml }
- format.rss { render :action => "feed.rxml" }
- end
- end
- end
-
- # returns HTML when requested by a browser, since the browser
- # has the HTML mimetype at the top of its priority list
- Accept: text/html
- GET /weblog
-
- # returns the XML
- Accept: application/xml
- GET /weblog
-
- # returns the HTML
- Accept: application/xml
- GET /weblog.html
-
- # returns the XML
- Accept: text/html
- GET /weblog.xml
-
- All this relies on the fact that you have a route that includes .:format.
-
-* Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post [David Heinemeier Hansson]
-
-* Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete [David Heinemeier Hansson]
-
-* follow_redirect doesn't complain about being redirected to the same controller. #5153 [dymo@mk.ukrtelecom.ua]
-
-* Add layout attribute to response object with the name of the layout that was rendered, or nil if none rendered. [Kevin Clark]
-
-* Fix NoMethodError when parsing params like &&. [Adam Greenfield]
-
-* Fix flip flopped logic in docs for url_for's :only_path option. Closes #4998. [esad@esse.at]
-
-* form.text_area handles the :size option just like the original text_area (:size => '60x10' becomes cols="60" rows="10"). [Jeremy Kemper]
-
-* Excise ingrown code from FormOptionsHelper#options_for_select. #5008 [anonymous]
-
-* Small fix in routing to allow dynamic routes (broken after [4242]) [Rick Olson]
-
- map.connect '*path', :controller => 'files', :action => 'show'
-
-* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
-
-* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
-
-* Use #flush between switching from #write to #syswrite. Closes #4907. [Blair Zajac <blair@orcaware.com>]
-
-* Documentation fix: integration test scripts don't require integration_test. Closes #4914. [Frederick Ros <sl33p3r@free.fr>]
-
-* ActionController::Base Summary documentation rewrite. Closes #4900. [Kevin Clark]
-
-* Fix text_helper.rb documentation rendering. Closes #4725. [Frederick Ros]
-
-* Fixes bad rendering of JavaScriptMacrosHelper rdoc (closes #4910) [Frederick Ros]
-
-* Allow error_messages_for to report errors for multiple objects, as well as support for customizing the name of the object in the error summary header. Closes #4186. [andrew@redlinesoftware.com, Marcel Molina Jr.]
-
- error_messages_for :account, :user, :subscription, :object_name => :account
-
-* Enhance documentation for setting headers in integration tests. Skip auto HTTP prepending when its already there. Closes #4079. [Rick Olson]
-
-* Documentation for AbstractRequest. Closes #4895. [Kevin Clark]
-
-* Refactor various InstanceTag instance method to class methods. Closes #4800. [Stefan Kaes]
-
-* Remove all remaining references to @params in the documentation. [Marcel Molina Jr.]
-
-* Add documentation for redirect_to :back's RedirectBackError exception. [Marcel Molina Jr.]
-
-* Update layout and content_for documentation to use yield rather than magic @content_for instance variables. [Marcel Molina Jr.]
-
-* Fix assert_redirected_to tests according to real-world usage. Also, don't fail if you add an extra :controller option: [Rick Olson]
-
- redirect_to :action => 'new'
- assert_redirected_to :controller => 'monkeys', :action => 'new'
-
-* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. [Rick Olson]
-
-* Diff compared routing options. Allow #assert_recognizes to take a second arg as a hash to specify optional request method [Rick Olson]
-
- assert_recognizes({:controller => 'users', :action => 'index'}, 'users')
- assert_recognizes({:controller => 'users', :action => 'create'}, {:path => 'users', :method => :post})
-
-* Diff compared options with #assert_redirected_to [Rick Olson]
-
-* Add support in routes for semicolon delimited "subpaths", like /books/:id;:action [Jamis Buck]
-
-* Change link_to_function and button_to_function to (optionally) take an update_page block instead of a JavaScript string. Closes #4804. [zraii@comcast.net, Sam Stephenson]
-
-* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for [David Heinemeier Hansson]
-
-* Modify routing so that you can say :require => { :method => :post } for a route, and the route will never be selected unless the request method is POST. Only works for route recognition, not for route generation. [Jamis Buck]
-
-* Added :add_headers option to verify which merges a hash of name/value pairs into the response's headers hash if the prerequisites cannot be satisfied. [Sam Stephenson]
- ex. verify :only => :speak, :method => :post,
- :render => { :status => 405, :text => "Must be post" },
- :add_headers => { "Allow" => "POST" }
-
-* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled #1897 [jeremye@bsa.ca.gov]
-
-
-*1.13.3* (March 12th, 2007)
-
-* Apply [5709] to stable.
-
-* session_enabled? works with session :off. #6680 [Jonathan del Strother]
-
-* Performance: patch cgi/session to require digest/md5 once rather than per #create_new_id. [Stefan Kaes]
-
-
-*1.13.2* (February 5th, 2007)
-
-* Add much-needed html-scanner tests. Fixed CDATA parsing bug. [Rick Olson]
-
-* improve error message for Routing for named routes. [Rob Sanheim]
-
-* Added enhanced docs to routing assertions. [Rob Sanheim]
-
-* fix form_for example in ActionController::Resources documentation. [gnarg]
-
-* Add singleton resources from trunk [Rick Olson]
-
-* select :multiple => true suffixes the attribute name with [] unless already suffixed. #6977 [nik.kakelin, ben, julik]
-
-* Improve routes documentation. #7095 [zackchandler]
-
-* Resource member routes require :id, eliminating the ambiguous overlap with collection routes. #7229 [dkubb]
-
-* Fixed NumberHelper#number_with_delimiter to use "." always for splitting the original number, not the delimiter parameter #7389 [ceefour]
-
-* Autolinking recognizes trailing and embedded . , : ; #7354 [Jarkko Laine]
-
-* Make TextHelper::auto_link recognize URLs with colons in path correctly, fixes #7268. [imajes]
-
-* Improved auto_link to match more valid urls correctly [Tobias Lütke]
-
-
-*1.13.1* (January 18th, 2007)
-
-* Fixed content-type bug in Prototype [sam]
-
-
-*1.13.0* (January 16th, 2007)
-
-* Modernize cookie testing code, and increase coverage (Heckle++) #7101 [Kevin Clark]
-
-* Heckling ActionController::Resources::Resource revealed that set_prefixes didn't break when :name_prefix was munged. #7081 [Kevin Clark]
-
-* Update to Prototype 1.5.0. [Sam Stephenson]
-
-* Allow exempt_from_layout :rhtml. #6742, #7026 [Dan Manges, Squeegy]
-
-* Fix parsing of array[] CGI parameters so extra empty values aren't included. #6252 [Nicholas Seckar, aiwilliams, brentrowland]
-
-* link_to_unless_current works with full URLs as well as paths. #6891 [Jarkko Laine, Manfred Stienstra, idrifter]
-
-* Fix HTML::Node to output double quotes instead of single quotes. Closes #6845 [mitreandy]
-
-* Fix no method error with error_messages_on. Closes #6935 [nik.wakelin Koz]
-
-* Slight doc tweak to the ActionView::Helpers::PrototypeHelper#replace docs. Closes #6922 [Steven Bristol]
-
-* Slight doc tweak to #prepend_filter. Closes #6493 [Jeremy Voorhis]
-
-* Add more extensive documentation to the AssetTagHelper. Closes #6452 [Bob Silva]
-
-* Clean up multiple calls to #stringify_keys in TagHelper, add better documentation and testing for TagHelper. Closes #6394 [Bob Silva]
-
-* [DOCS] fix reference to ActionController::Macros::AutoComplete for #text_field_with_auto_complete. Closes #2578 [Jan Prill]
-
-* Make sure html_document is reset between integration test requests. [ctm]
-
-* Set session to an empty hash if :new_session => false and no session cookie or param is present. CGI::Session was raising an unrescued ArgumentError. [Josh Susser]
-
-* Fix assert_redirected_to bug where redirecting from a nested to to a top-level controller incorrectly added the current controller's nesting. Closes #6128. [Rick Olson]
-
-* Ensure render :json => ... skips the layout. #6808 [Josh Peek]
-
-* Silence log_error deprecation warnings from inspecting deprecated instance variables. [Nate Wiger]
-
-* Only cache GET requests with a 200 OK response. #6514, #6743 [RSL, anamba]
-
-* Correctly report which filter halted the chain. #6699 [Martin Emde]
-
-* respond_to recognizes JSON. render :json => @person.to_json automatically sets the content type and takes a :callback option to specify a client-side function to call using the rendered JSON as an argument. #4185 [Scott Raymond, eventualbuddha]
- # application/json response with body 'Element.show({:name: "David"})'
- respond_to do |format|
- format.json { render :json => { :name => "David" }.to_json, :callback => 'Element.show' }
- end
-
-* Makes :discard_year work without breaking multi-attribute parsing in AR. #1260, #3800 [sean@ardismg.com, jmartin@desertflood.com, stephen@touset.org, Bob Silva]
-
-* Adds html id attribute to date helper elements. #1050, #1382 [mortonda@dgrmm.net, David North, Bob Silva]
-
-* Add :index and @auto_index capability to model driven date/time selects. #847, #2655 [moriq, Doug Fales, Bob Silva]
-
-* Add :order to datetime_select, select_datetime, and select_date. #1427 [Timothee Peignier, Patrick Lenz, Bob Silva]
-
-* Added time_select to work with time values in models. Update scaffolding. #2489, #2833 [Justin Palmer, Andre Caum, Bob Silva]
-
-* Added :include_seconds to select_datetime, datetime_select and time_select. #2998 [csn, Bob Silva]
-
-* All date/datetime selects can now accept an array of month names with :use_month_names. Allows for localization. #363 [tomasj, Bob Silva]
-
-* Adds :time_separator to select_time and :date_separator to select_datetime. Preserves BC. #3811 [Bob Silva]
-
-* @response.redirect_url works with 201 Created responses: just return headers['Location'] rather than checking the response status. [Jeremy Kemper]
-
-* Fixed that HEAD should return the proper Content-Length header (that is, actually use @body.size, not just 0) [David Heinemeier Hansson]
-
-* Added GET-masquarading for HEAD, so request.method will return :get even for HEADs. This will help anyone relying on case request.method to automatically work with HEAD and map.resources will also allow HEADs to all GET actions. Rails automatically throws away the response content in a reply to HEAD, so you don't even need to worry about that. If you, for whatever reason, still need to distinguish between GET and HEAD in some edge case, you can use Request#head? and even Request.headers["REQUEST_METHOD"] for get the "real" answer. Closes #6694 [David Heinemeier Hansson]
-
-
-*1.13.0 RC1* (r5619, November 22nd, 2006)
-
-* Update Routing to complain when :controller is not specified by a route. Closes #6669. [Nicholas Seckar]
-
-* Ensure render_to_string cleans up after itself when an exception is raised. #6658 [rsanheim]
-
-* Update to Prototype and script.aculo.us [5579]. [Sam Stephenson, Thomas Fuchs]
-
-* simple_format helper doesn't choke on nil. #6644 [jerry426]
-
-* Reuse named route helper module between Routing reloads. Use remove_method to delete named route methods after each load. Since the module is never collected, this fixes a significant memory leak. [Nicholas Seckar]
-
-* Deprecate standalone components. [Jeremy Kemper]
-
-* Always clear model associations from session. #4795 [sd@notso.net, andylien@gmail.com]
-
-* Remove JavaScriptLiteral in favor of ActiveSupport::JSON::Variable. [Sam Stephenson]
-
-* Sync ActionController::StatusCodes::STATUS_CODES with http://www.iana.org/assignments/http-status-codes. #6586 [dkubb]
-
-* Multipart form values may have a content type without being treated as uploaded files if they do not provide a filename. #6401 [Andreas Schwarz, Jeremy Kemper]
-
-* assert_response supports symbolic status codes. #6569 [Kevin Clark]
- assert_response :ok
- assert_response :not_found
- assert_response :forbidden
-
-* Cache parsed query parameters. #6559 [Stefan Kaes]
-
-* Deprecate JavaScriptHelper#update_element_function, which is superseeded by RJS [Thomas Fuchs]
-
-* Fix invalid test fixture exposed by stricter Ruby 1.8.5 multipart parsing. #6524 [Bob Silva]
-
-* Set ActionView::Base.default_form_builder once rather than passing the :builder option to every form or overriding the form helper methods. [Jeremy Kemper]
-
-* Deprecate expire_matched_fragments. Use expire_fragment instead. #6535 [Bob Silva]
-
-* Deprecate start_form_tag and end_form_tag. Use form_tag / '</form>' from now on. [Rick Olson]
-
-* Added block-usage to PrototypeHelper#form_remote_tag, document block-usage of FormTagHelper#form_tag [Rick Olson]
-
-* Add a 0 margin/padding div around the hidden _method input tag that form_tag outputs. [Rick Olson]
-
-* Added block-usage to TagHelper#content_tag [David Heinemeier Hansson]. Example:
-
- <% content_tag :div, :class => "strong" %>
- Hello world!
- <% end %>
-
- Will output:
- <div class="strong">Hello world!</div>
-
-* Deprecated UrlHelper#link_to_image and UrlHelper#link_to :post => true #6409 [Bob Silva]
-
-* Upgraded NumberHelper with number_to_phone support international formats to comply with ITU E.123 by supporting area codes with less than 3 digits, added precision argument to number_to_human_size (defaults to 1) #6421 [Bob Silva]
-
-* Fixed that setting RAILS_ASSET_ID to "" should not add a trailing slash after assets #6454 [Bob Silva/chrismear]
-
-* Force *_url named routes to show the host in ActionView [Rick Olson]
-
- <%= url_for ... %> # no host
- <%= foo_path %> # no host
- <%= foo_url %> # host!
-
-* Add support for converting blocks into function arguments to JavaScriptGenerator#call and JavaScriptProxy#call. [Sam Stephenson]
-
-* Add JavaScriptGenerator#literal for wrapping a string in an object whose #to_json is the string itself. [Sam Stephenson]
-
-* Add <%= escape_once html %> to escape html while leaving any currently escaped entities alone. Fix button_to double-escaping issue. [Rick Olson]
-
-* Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc. [Rick Olson]
-
-* Fix routing to correctly determine when generation fails. Closes #6300. [psross].
-
-* Fix broken assert_generates when extra keys are being checked. [Jamis Buck]
-
-* Replace KCODE checks with String#chars for truncate. Closes #6385 [Manfred Stienstra]
-
-* Make page caching respect the format of the resource that is being requested even if the current route is the default route so that, e.g. posts.rss is not transformed by url_for to '/' and subsequently cached as '/index.html' when it should be cached as '/posts.rss'. [Marcel Molina Jr.]
-
-* Use String#chars in TextHelper::excerpt. Closes #6386 [Manfred Stienstra]
-
-* Fix relative URL root matching problems. [Mark Imbriaco]
-
-* Fix filter skipping in controller subclasses. #5949, #6297, #6299 [Martin Emde]
-
-* render_text may optionally append to the response body. render_javascript appends by default. This allows you to chain multiple render :update calls by setting @performed_render = false between them (awaiting a better public API). [Jeremy Kemper]
-
-* Rename test assertion to prevent shadowing. Closes #6306. [psross]
-
-* Fixed that NumberHelper#number_to_delimiter should respect precision of higher than two digits #6231 [Philip Hallstrom]
-
-* Fixed that FormHelper#radio_button didn't respect an :id being passed in #6266 [evansj]
-
-* Added an html_options hash parameter to javascript_tag() and update_page_tag() helpers #6311 [tzaharia]. Example:
-
- update_page_tag :defer => 'true' { |page| ... }
-
- Gives:
-
- <script defer="true" type="text/javascript">...</script>
-
- Which is needed for dealing with the IE6 DOM when it's not yet fully loaded.
-
-* Fixed that rescue template path shouldn't be hardcoded, then it's easier to hook in your own #6295 [Mike Naberezny]
-
-* Fixed escaping of backslashes in JavaScriptHelper#escape_javascript #6302 [sven@c3d2.de]
-
-* Fixed that some 500 rescues would cause 500's themselves because the response had not yet been generated #6329 [cmselmer]
-
-* respond_to :html doesn't assume .rhtml. #6281 [Hampton Catlin]
-
-* Fixed some deprecation warnings in ActionPack [Rick Olson]
-
-* assert_select_rjs decodes escaped unicode chars since the Javascript generators encode them. #6240 [japgolly]
-
-* Deprecation: @cookies, @headers, @request, @response will be removed after 1.2. Use the corresponding method instead. [Jeremy Kemper]
-
-* Make the :status parameter expand to the default message for that status code if it is an integer. Also support symbol statuses. [Jamis Buck]. Examples:
-
- head :status => 404 # expands to "404 Not Found"
- head :status => :not_found # expands to "404 Not Found"
- head :status => :created # expands to "201 Created"
-
-* Add head(options = {}) for responses that have no body. [Jamis Buck]. Examples:
-
- head :status => 404 # return an empty response with a 404 status
- head :location => person_path(@person), :status => 201
-
-* Fix bug that kept any before_filter except the first one from being able to halt the before_filter chain. [Rick Olson]
-
-* strip_links is case-insensitive. #6285 [tagoh, Bob Silva]
-
-* Clear the cache of possible controllers whenever Routes are reloaded. [Nicholas Seckar]
-
-* Filters overhaul including meantime filter support using around filters + blocks. #5949 [Martin Emde, Roman Le Negrate, Stefan Kaes, Jeremy Kemper]
-
-* Update CGI process to allow sessions to contain namespaced models. Closes #4638. [dfelstead@site5.com]
-
-* Fix routing to respect user provided requirements and defaults when assigning default routing options (such as :action => 'index'). Closes #5950. [Nicholas Seckar]
-
-* Rescue Errno::ECONNRESET to handle an unexpectedly closed socket connection. Improves SCGI reliability. #3368, #6226 [sdsykes, fhanshaw@vesaria.com]
-
-* Added that respond_to blocks will automatically set the content type to be the same as is requested [David Heinemeier Hansson]. Examples:
-
- respond_to do |format|
- format.html { render :text => "I'm being sent as text/html" }
- format.rss { render :text => "I'm being sent as application/rss+xml" }
- format.atom { render :text => "I'm being sent as application/xml", :content_type => Mime::XML }
- end
-
-* Added utf-8 as the default charset for all renders. You can change this default using ActionController::Base.default_charset=(encoding) [David Heinemeier Hansson]
-
-* Added proper getters and setters for content type and charset [David Heinemeier Hansson]. Example of what we used to do:
-
- response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
-
- ...now:
-
- response.content_type = Mime::ATOM
- response.charset = "utf-8"
-
-* Declare file extensions exempt from layouts. #6219 [brandon]
- Example: ActionController::Base.exempt_from_layout 'rpdf'
-
-* Add chained replace/update support for assert_select_rjs [Rick Olson]
-
- Given RJS like...
-
- page['test1'].replace "<div id=\"1\">foo</div>"
- page['test2'].replace_html "<div id=\"2\">foo</div>"
-
- Test it with...
-
- assert_select_rjs :chained_replace
- assert_select_rjs :chained_replace, "test1"
-
- assert_select_rjs :chained_replace_html
- assert_select_rjs :chained_replace_html, "test2"
-
-* Load helpers in alphabetical order for consistency. Resolve cyclic javascript_helper dependency. #6132, #6178 [choonkeat@gmail.com]
-
-* Skip params with empty names, such as the &=Save query string from <input type="submit"/>. #2569 [Manfred Stienstra, raphinou@yahoo.com]
-
-* Fix assert_tag so that :content => "foo" does not match substrings, but only exact strings. Use :content => /foo/ to match substrings. #2799 [Eric Hodel]
-
-* Update JavaScriptGenerator#show/hide/toggle/remove to new Prototype syntax for multiple ids, #6068 [petermichaux@gmail.com]
-
-* Update UrlWriter to support :only_path. [Nicholas Seckar, Dave Thomas]
-
-* Fixed JavaScriptHelper#link_to_function and JavaScriptHelper#button_to_function to have the script argument be optional [David Heinemeier Hansson]. So what used to require a nil, like this:
-
- link_to("Hider", nil, :class => "hider_link") { |p| p[:something].hide }
-
- ...can be written like this:
-
- link_to("Hider", :class => "hider_link") { |p| p[:something].hide }
-
-* Added access to nested attributes in RJS #4548 [richcollins@gmail.com]. 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';
-
-* Fixed that AssetTagHelper#image_tag and others using compute_public_path should not modify the incoming source argument (closes #5102) [eule@space.ch]
-
-* Deprecated the auto-appending of .png to AssetTagHelper#image_tag calls that doesn't have an extension [David Heinemeier Hansson]
-
-* Fixed FormOptionsHelper#select to respect :selected value #5813
-
-* Fixed TextHelper#simple_format to deal with multiple single returns within a single paragraph #5835 [moriq@moriq.com]
-
-* Fixed TextHelper#pluralize to handle 1 as a string #5909 [rails@bencurtis.com]
-
-* Improved resolution of DateHelper#distance_of_time_in_words for better precision #5994 [Bob Silva]
-
-* Changed that uncaught exceptions raised any where in the application will cause RAILS_ROOT/public/500.html to be read and shown instead of just the static "Application error (Rails)" [David Heinemeier Hansson]
-
-* Added deprecation language for pagination which will become a plugin by Rails 2.0 [David Heinemeier Hansson]
-
-* Added deprecation language for in_place_editor and auto_complete_field that both pieces will become plugins by Rails 2.0 [David Heinemeier Hansson]
-
-* Deprecated all of ActionController::Dependencies. All dependency loading is now handled from Active Support [David Heinemeier Hansson]
-
-* Added assert_select* for CSS selector-based testing (deprecates assert_tag) #5936 [assaf.arkin@gmail.com]
-
-* radio_button_tag generates unique id attributes. #3353 [Bob Silva, somekool@gmail.com]
-
-* strip_tags passes through blank args such as nil or "". #2229, #6702 [duncan@whomwah.com, dharana]
-
-* Cleanup assert_tag :children counting. #2181 [jamie@bravenet.com]
-
-* button_to accepts :method so you can PUT and DELETE with it. #6005 [Dan Webb]
-
-* Update sanitize text helper to strip plaintext tags, and <img src="javascript:bang">. [Rick Olson]
-
-* Add routing tests to assert that RoutingError is raised when conditions aren't met. Closes #6016 [Nathan Witmer]
-
-* Make auto_link parse a greater subset of valid url formats. [Jamis Buck]
-
-* Integration tests: headers beginning with X aren't excluded from the HTTP_ prefix, so X-Requested-With becomes HTTP_X_REQUESTED_WITH as expected. [Mike Clark]
-
-* Switch to using FormEncodedPairParser for parsing request parameters. [Nicholas Seckar, David Heinemeier Hansson]
-
-* respond_to .html now always renders #{action_name}.rhtml so that registered custom template handlers do not override it in priority. Custom mime types require a block and throw proper error now. [Tobias Lütke]
-
-* Deprecation: test deprecated instance vars in partials. [Jeremy Kemper]
-
-* Add UrlWriter to allow writing urls from Mailers and scripts. [Nicholas Seckar]
-
-* Relax Routing's anchor pattern warning; it was preventing use of [^/] inside restrictions. [Nicholas Seckar]
-
-* Add controller_paths variable to Routing. [Nicholas Seckar]
-
-* Fix assert_redirected_to issue with named routes for module controllers. [Rick Olson]
-
-* Tweak RoutingError message to show option diffs, not just missing named route significant keys. [Rick Olson]
-
-* Invoke method_missing directly on hidden actions. Closes #3030. [Nicholas Seckar]
-
-* Add RoutingError exception when RouteSet fails to generate a path from a Named Route. [Rick Olson]
-
-* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
-
-* Deprecation: check whether instance variables have been monkeyed with before assigning them to deprecation proxies. Raises a RuntimeError if so. [Jeremy Kemper]
-
-* Add support for the param_name parameter to the auto_complete_field helper. #5026 [david.a.williams@gmail.com]
-
-* Deprecation! @params, @session, @flash will be removed after 1.2. Use the corresponding instance methods instead. You'll get printed warnings during tests and logged warnings in dev mode when you access either instance variable directly. [Jeremy Kemper]
-
-* Make Routing noisy when an anchor regexp is assigned to a segment. #5674 [François Beausoleil]
-
-* Added months and years to the resolution of DateHelper#distance_of_time_in_words, such that "60 days ago" becomes "2 months ago" #5611 [pjhyett@gmail.com]
-
-* Make controller_path available as an instance method. #5724 [jmckible@gmail.com]
-
-* Update query parser to support adjacent hashes. [Nicholas Seckar]
-
-* Make action caching aware of different formats for the same action so that, e.g. foo.xml is cached separately from foo.html. Implicitly set content type when reading in cached content with mime revealing extensions so the entire onous isn't on the webserver. [Marcel Molina Jr.]
-
-* Restrict Request Method hacking with ?_method to POST requests. [Rick Olson]
-
-* Fixed the new_#{resource}_url route and added named route tests for Simply Restful. [Rick Olson]
-
-* Added map.resources from the Simply Restful plugin [David Heinemeier Hansson]. Examples (the API has changed to use plurals!):
-
- map.resources :messages
- map.resources :messages, :comments
- map.resources :messages, :new => { :preview => :post }
-
-* Fixed that integration simulation of XHRs should set Accept header as well [Edward Frederick]
-
-* TestRequest#reset_session should restore a TestSession, not a hash [Michael Koziarski]
-
-* Don't search a load-path of '.' for controller files [Jamis Buck]
-
-* Update integration.rb to require test_process explicitly instead of via Dependencies. [Nicholas Seckar]
-
-* Fixed that you can still access the flash after the flash has been reset in reset_session. Closes #5584 [lmarlow]
-
-* Allow form_for and fields_for to work with indexed form inputs. [Jeremy Kemper, Matt Lyon]
-
- <% form_for 'post[]', @post do |f| -%>
- <% end -%>
-
-* Remove leak in development mode by replacing define_method with module_eval. [Nicholas Seckar]
-
-* Provide support for decimal columns to form helpers. Closes #5672. [Dave Thomas]
-
-* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com]
-
-* Reset @html_document between requests so assert_tag works. #4810 [Jarkko Laine, easleydp@gmail.com]
-
-* Integration tests behave well with render_component. #4632 [edward.frederick@revolution.com, dev.rubyonrails@maxdunn.com]
-
-* Added exception handling of missing layouts #5373 [chris@ozmm.org]
-
-* Fixed that real files and symlinks should be treated the same when compiling templates #5438 [zachary@panandscan.com]
-
-* Fixed that the flash should be reset when reset_session is called #5584 [Shugo Maeda]
-
-* Added special case for "1 Byte" in NumberHelper#number_to_human_size #5593 [murpyh@rubychan.de]
-
-* Fixed proper form-encoded parameter parsing for requests with "Content-Type: application/x-www-form-urlencoded; charset=utf-8" (note the presence of a charset directive) [David Heinemeier Hansson]
-
-* Add route_name_path method to generate only the path for a named routes. For example, map.person will add person_path. [Nicholas Seckar]
-
-* Avoid naming collision among compiled view methods. [Jeremy Kemper]
-
-* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [Mislav Marohnić]
-
-* Determine the correct template_root for deeply nested components. #2841 [s.brink@web.de]
-
-* Fix that routes with *path segments in the recall can generate URLs. [Rick Olson]
-
-* Fix strip_links so that it doesn't hang on multiline <acronym> tags [Jamis Buck]
-
-* Remove problematic control chars in rescue template. #5316 [Stefan Kaes]
-
-* Make sure passed routing options are not mutated by routing code. #5314 [Blair Zajac]
-
-* Make sure changing the controller from foo/bar to bing/bang does not change relative to foo. [Jamis Buck]
-
-* Escape the path before routing recognition. #3671
-
-* Make sure :id and friends are unescaped properly. #5275 [me@julik.nl]
-
-* Rewind readable CGI params so others may reread them (such as CGI::Session when passing the session id in a multipart form). #210 [mklame@atxeu.com, matthew@walker.wattle.id.au]
-
-* Added Mime::TEXT (text/plain) and Mime::ICS (text/calendar) as new default types [David Heinemeier Hansson]
-
-* Added Mime::Type.register(string, symbol, synonyms = []) for adding new custom mime types [David Heinemeier Hansson]. Example: Mime::Type.register("image/gif", :gif)
-
-* Added support for Mime objects in render :content_type option [David Heinemeier Hansson]. Example: render :text => some_atom, :content_type => Mime::ATOM
-
-* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 [Manfred Stienstra <m.stienstra@fngtps.com>]
-
-* Routing rewrite. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes. [Nicholas Seckar, Jamis Buck]
-
- map.connect '/foo/:id', :controller => '...', :action => '...'
- map.connect '/foo/:id.:format', :controller => '...', :action => '...'
- map.connect '/foo/:id', ..., :conditions => { :method => :get }
-
-* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper]
-
-* Accept multipart PUT parameters. #5235 [guy.naor@famundo.com]
-
-* Added interrogation of params[:format] to determine Accept type. If :format is specified and matches a declared extension, like "rss" or "xml", that mime type will be put in front of the accept handler. This means you can link to the same action from different extensions and use that fact to determine output [David Heinemeier Hansson]. Example:
-
- class WeblogController < ActionController::Base
- def index
- @posts = Post.find :all
-
- respond_to do |format|
- format.html
- format.xml { render :xml => @posts.to_xml }
- format.rss { render :action => "feed.rxml" }
- end
- end
- end
-
- # returns HTML when requested by a browser, since the browser
- # has the HTML mimetype at the top of its priority list
- Accept: text/html
- GET /weblog
-
- # returns the XML
- Accept: application/xml
- GET /weblog
-
- # returns the HTML
- Accept: application/xml
- GET /weblog.html
-
- # returns the XML
- Accept: text/html
- GET /weblog.xml
-
- All this relies on the fact that you have a route that includes .:format.
-
-* Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post [David Heinemeier Hansson]
-
-* Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete [David Heinemeier Hansson]
-
-* follow_redirect doesn't complain about being redirected to the same controller. #5153 [dymo@mk.ukrtelecom.ua]
-
-* Add layout attribute to response object with the name of the layout that was rendered, or nil if none rendered. [Kevin Clark]
-
-* Fix NoMethodError when parsing params like &&. [Adam Greenfield]
-
-* form.text_area handles the :size option just like the original text_area (:size => '60x10' becomes cols="60" rows="10"). [Jeremy Kemper]
-
-* Excise ingrown code from FormOptionsHelper#options_for_select. #5008 [anonymous]
-
-* Small fix in routing to allow dynamic routes (broken after [4242]) [Rick Olson]
-
- map.connect '*path', :controller => 'files', :action => 'show'
-
-* Use #flush between switching from #write to #syswrite. Closes #4907. [Blair Zajac <blair@orcaware.com>]
-
-* Allow error_messages_for to report errors for multiple objects, as well as support for customizing the name of the object in the error summary header. Closes #4186. [andrew@redlinesoftware.com, Marcel Molina Jr.]
-
- error_messages_for :account, :user, :subscription, :object_name => :account
-
-* Fix assert_redirected_to tests according to real-world usage. Also, don't fail if you add an extra :controller option: [Rick Olson]
-
- redirect_to :action => 'new'
- assert_redirected_to :controller => 'monkeys', :action => 'new'
-
-* Diff compared routing options. Allow #assert_recognizes to take a second arg as a hash to specify optional request method [Rick Olson]
-
- assert_recognizes({:controller => 'users', :action => 'index'}, 'users')
- assert_recognizes({:controller => 'users', :action => 'create'}, {:path => 'users', :method => :post})
-
-* Diff compared options with #assert_redirected_to [Rick Olson]
-
-* Add support in routes for semicolon delimited "subpaths", like /books/:id;:action [Jamis Buck]
-
-* Change link_to_function and button_to_function to (optionally) take an update_page block instead of a JavaScript string. Closes #4804. [zraii@comcast.net, Sam Stephenson]
-
-* Modify routing so that you can say :require => { :method => :post } for a route, and the route will never be selected unless the request method is POST. Only works for route recognition, not for route generation. [Jamis Buck]
-
-* Added :add_headers option to verify which merges a hash of name/value pairs into the response's headers hash if the prerequisites cannot be satisfied. [Sam Stephenson]
- ex. verify :only => :speak, :method => :post,
- :render => { :status => 405, :text => "Must be post" },
- :add_headers => { "Allow" => "POST" }
-
-
-*1.12.5* (August 10th, 2006)
-
-* Updated security fix
-
-
-*1.12.4* (August 8th, 2006)
-
-* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. [Rick Olson]
-
-* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for [David Heinemeier Hansson]
-
-* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled. #1897 [jeremye@bsa.ca.gov]
-
-* Fixed that real files and symlinks should be treated the same when compiling templates. #5438 [zachary@panandscan.com]
-
-* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 [Manfred Stienstra <m.stienstra@fngtps.com>]
-
-* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
-
-* Short documentation to mention use of Mime::Type.register. #5710 [choonkeat@gmail.com]
-
-
-*1.12.3* (June 28th, 2006)
-
-* Fix broken traverse_to_controller. We now:
- Look for a _controller.rb file under RAILS_ROOT to load.
- If we find it, we require_dependency it and return the controller it defined. (If none was defined we stop looking.)
- If we don't find it, we look for a .rb file under RAILS_ROOT to load. If we find it, and it loads a constant we keep looking.
- Otherwise we check to see if a directory of the same name exists, and if it does we create a module for it.
-
-
-*1.12.2* (June 27th, 2006)
-
-* Refinement to avoid exceptions in traverse_to_controller.
-
-* (Hackish) Fix loading of arbitrary files in Ruby's load path by traverse_to_controller. [Nicholas Seckar]
-
-
-*1.12.1* (April 6th, 2006)
-
-* Fixed that template extensions would be cached development mode #4624 [Stefan Kaes]
-
-* Update to Prototype 1.5.0_rc0 [Sam Stephenson]
-
-* Honor skipping filters conditionally for only certain actions even when the parent class sets that filter to conditionally be executed only for the same actions. #4522 [Marcel Molina Jr.]
-
-* Delegate xml_http_request in integration tests to the session instance. [Jamis Buck]
-
-* Update the diagnostics template skip the useless '<controller not set>' text. [Nicholas Seckar]
-
-* CHANGED DEFAULT: Don't parse YAML input by default, but keep it available as an easy option [David Heinemeier Hansson]
-
-* Add additional autocompleter options [aballai, Thomas Fuchs]
-
-* Fixed fragment caching of binary data on Windows #4493 [bellis@deepthought.org]
-
-* Applied Prototype $() performance patches (#4465, #4477) and updated script.aculo.us [Sam Stephenson, Thomas Fuchs]
-
-* Added automated timestamping to AssetTagHelper methods for stylesheets, javascripts, and images when Action Controller is run under Rails [David Heinemeier Hansson]. Example:
-
- image_tag("rails.png") # => '<img alt="Rails" src="/images/rails.png?1143664135" />'
-
- ...to avoid frequent stats (not a problem for most people), you can set RAILS_ASSET_ID in the ENV to avoid stats:
-
- ENV["RAILS_ASSET_ID"] = "2345"
- image_tag("rails.png") # => '<img alt="Rails" src="/images/rails.png?2345" />'
-
- This can be used by deployment managers to set the asset id by application revision
-
-
-*1.12.0* (March 27th, 2006)
-
-* Add documentation for respond_to. [Jamis Buck]
-
-* Fixed require of bluecloth and redcloth when gems haven't been loaded #4446 [murphy@cYcnus.de]
-
-* Update to Prototype 1.5.0_pre1 [Sam Stephenson]
-
-* Change #form_for and #fields_for so that the second argument is not required [Dave Thomas]
-
- <% form_for :post, @post, :url => { :action => 'create' } do |f| -%>
-
- becomes...
-
- <% form_for :post, :url => { :action => 'create' } do |f| -%>
-
-* Update to script.aculo.us 1.6 [Thomas Fuchs]
-
-* Enable application/x-yaml processing by default [Jamis Buck]
-
-* Fix double url escaping of remote_function. Add :escape => false option to ActionView's url_for. [Nicholas Seckar]
-
-* Add :script option to in_place_editor to support evalScripts (closes #4194) [Cody Fauser]
-
-* Fix mixed case enumerable methods in the JavaScript Collection Proxy (closes #4314) [Cody Fauser]
-
-* Undo accidental escaping for mail_to; add regression test. [Nicholas Seckar]
-
-* Added nicer message for assert_redirected_to (closes #4294) [court3nay]
-
- assert_redirected_to :action => 'other_host', :only_path => false
-
- when it was expecting...
-
- redirected_to :action => 'other_host', :only_path => true, :host => 'other.test.host'
-
- gives the error message...
-
- response is not a redirection to all of the options supplied (redirection is <{:only_path=>false, :host=>"other.test.host", :action=>"other_host"}>), difference: <{:only_path=>"true", :host=>"other.test.host"}>
-
-* Change url_for to escape the resulting URLs when called from a view. [Nicholas Seckar, coffee2code]
-
-* Added easy support for testing file uploads with fixture_file_upload #4105 [turnip@turnipspatch.com]. Example:
-
- # Looks in Test::Unit::TestCase.fixture_path + '/files/spongebob.png'
- post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
-
-* Fixed UrlHelper#current_page? to behave even when url-escaped entities are present #3929 [jeremy@planetargon.com]
-
-* Add ability for relative_url_root to be specified via an environment variable RAILS_RELATIVE_URL_ROOT. [isaac@reuben.com, Nicholas Seckar]
-
-* Fixed link_to "somewhere", :post => true to produce valid XHTML by using the parentnode instead of document.body for the instant form #3007 [Bob Silva]
-
-* Added :function option to PrototypeHelper#observe_field/observe_form that allows you to call a function instead of submitting an ajax call as the trigger #4268 [jonathan@daikini.com]
-
-* Make Mime::Type.parse consider q values (if any) [Jamis Buck]
-
-* XML-formatted requests are typecast according to "type" attributes for :xml_simple [Jamis Buck]
-
-* Added protection against proxy setups treating requests as local even when they're not #3898 [Steve Purcell]
-
-* Added TestRequest#raw_post that simulate raw_post from CgiRequest #3042 [François Beausoleil]
-
-* Underscore dasherized keys in formatted requests [Jamis Buck]
-
-* Add MimeResponds::Responder#any for managing multiple types with identical responses [Jamis Buck]
-
-* Make the xml_http_request testing method set the HTTP_ACCEPT header [Jamis Buck]
-
-* Add Verification to scaffolds. Prevent destructive actions using GET [Michael Koziarski]
-
-* Avoid hitting the filesystem when using layouts by using a File.directory? cache. [Stefan Kaes, Nicholas Seckar]
-
-* Simplify ActionController::Base#controller_path [Nicholas Seckar]
-
-* Added simple alert() notifications for RJS exceptions when config.action_view.debug_rjs = true. [Sam Stephenson]
-
-* Added :content_type option to render, so you can change the content type on the fly [David Heinemeier Hansson]. Example: render :action => "atom.rxml", :content_type => "application/atom+xml"
-
-* CHANGED DEFAULT: The default content type for .rxml is now application/xml instead of type/xml, see http://www.xml.com/pub/a/2004/07/21/dive.html for reason [David Heinemeier Hansson]
-
-* Added option to render action/template/file of a specific extension (and here by template type). This means you can have multiple templates with the same name but a different extension [David Heinemeier Hansson]. Example:
-
- class WeblogController < ActionController::Base
- def index
- @posts = Post.find :all
-
- respond_to do |type|
- type.html # using defaults, which will render weblog/index.rhtml
- type.xml { render :action => "index.rxml" }
- type.js { render :action => "index.rjs" }
- end
- end
- end
-
-* Added better support for using the same actions to output for different sources depending on the Accept header [David Heinemeier Hansson]. Example:
-
- class WeblogController < ActionController::Base
- def create
- @post = Post.create(params[:post])
-
- respond_to do |type|
- type.js { render } # renders create.rjs
- type.html { redirect_to :action => "index" }
- type.xml do
- headers["Location"] = url_for(:action => "show", :id => @post)
- render(:nothing, :status => "201 Created")
- end
- end
- end
- end
-
-* Added Base#render(:xml => xml) that works just like Base#render(:text => text), but sets the content-type to text/xml and the charset to UTF-8 [David Heinemeier Hansson]
-
-* Integration test's url_for now runs in the context of the last request (if any) so after post /products/show/1 url_for :action => 'new' will yield /product/new [Tobias Lütke]
-
-* Re-added mixed-in helper methods for the JavascriptGenerator. Moved JavascriptGenerators methods to a module that is mixed in after the helpers are added. Also fixed that variables set in the enumeration methods like #collect are set correctly. Documentation added for the enumeration methods [Rick Olson]. Examples:
-
- page.select('#items li').collect('items') do |element|
- element.hide
- end
- # => var items = $$('#items li').collect(function(value, index) { return value.hide(); });
-
-* Added plugin support for parameter parsers, which allows for better support for REST web services. By default, posts submitted with the application/xml content type is handled by creating a XmlSimple hash with the same name as the root element of the submitted xml. More handlers can easily be registered like this:
-
- # Assign a new param parser to a new content type
- ActionController::Base.param_parsers['application/atom+xml'] = Proc.new do |data|
- node = REXML::Document.new(post)
- { node.root.name => node.root }
- end
-
- # Assign the default XmlSimple to a new content type
- ActionController::Base.param_parsers['application/backpack+xml'] = :xml_simple
-
-Default YAML web services were retired, ActionController::Base.param_parsers carries an example which shows how to get this functionality back. As part of this new plugin support, request.[formatted_post?, xml_post?, yaml_post? and post_format] were all deprecated in favor of request.content_type [Tobias Lütke]
-
-* Fixed Effect.Appear in effects.js to work with floats in Safari #3524, #3813, #3044 [Thomas Fuchs]
-
-* Fixed that default image extension was not appended when using a full URL with AssetTagHelper#image_tag #4032, #3728 [rubyonrails@beautifulpixel.com]
-
-* Added that page caching will only happen if the response code is less than 400 #4033 [g.bucher@teti.ch]
-
-* Add ActionController::IntegrationTest to allow high-level testing of the way the controllers and routes all work together [Jamis Buck]
-
-* Added support to AssetTagHelper#javascript_include_tag for having :defaults appear anywhere in the list, so you can now make one call ala javascript_include_tag(:defaults, "my_scripts") or javascript_include_tag("my_scripts", :defaults) depending on how you want the load order #3506 [Bob Silva]
-
-* Added support for visual effects scoped queues to the visual_effect helper #3530 [Abdur-Rahman Advany]
-
-* Added .rxml (and any non-rhtml template, really) supportfor CaptureHelper#content_for and CaptureHelper#capture #3287 [Brian Takita]
-
-* Added script.aculo.us drag and drop helpers to RJS [Thomas Fuchs]. Examples:
-
- page.draggable 'product-1'
- page.drop_receiving 'wastebasket', :url => { :action => 'delete' }
- page.sortable 'todolist', :url => { action => 'change_order' }
-
-* Fixed that form elements would strip the trailing [] from the first parameter #3545 [ruby@bobsilva.com]
-
-* During controller resolution, update the NameError suppression to check for the expected constant. [Nicholas Seckar]
-
-* Update script.aculo.us to V1.5.3 [Thomas Fuchs]
-
-* Added various InPlaceEditor options, #3746, #3891, #3896, #3906 [Bill Burcham, ruairi, sl33p3r]
-
-* Added :count option to pagination that'll make it possible for the ActiveRecord::Base.count call to using something else than * for the count. Especially important for count queries using DISTINCT #3839 [Stefan Kaes]
-
-* Update script.aculo.us to V1.5.2 [Thomas Fuchs]
-
-* Added element and collection proxies to RJS [David Heinemeier Hansson]. Examples:
-
- page['blank_slate'] # => $('blank_slate');
- page['blank_slate'].show # => $('blank_slate').show();
- page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
-
- 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();
-
-* Add JavaScriptGenerator#replace for replacing an element's "outer HTML". #3246 [tom@craz8.com, Sam Stephenson]
-
-* Remove over-engineered form_for code for a leaner implementation. [Nicholas Seckar]
-
-* Document form_for's :html option. [Nicholas Seckar]
-
-* Major components cleanup and speedup. #3527 [Stefan Kaes]
-
-* Fix problems with pagination and :include. [Kevin Clark]
-
-* Add ActiveRecordTestCase for testing AR integration. [Kevin Clark]
-
-* Add Unit Tests for pagination [Kevin Clark]
-
-* Add :html option for specifying form tag options in form_for. [Sam Stephenson]
-
-* Replace dubious controller parent class in filter docs. #3655, #3722 [info@rhalff.com, eigentone@gmail.com]
-
-* Don't interpret the :value option on text_area as an html attribute. Set the text_area's value. #3752 [gabriel@gironda.org]
-
-* Fix remote_form_for creates a non-ajax form. [Rick Olson]
-
-* Don't let arbitrary classes match as controllers -- a potentially dangerous bug. [Nicholas Seckar]
-
-* Fix Routing tests. Fix routing where failing to match a controller would prevent the rest of routes from being attempted. [Nicholas Seckar]
-
-* Add :builder => option to form_for and friends. [Nicholas Seckar, Rick Olson]
-
-* Fix controller resolution to avoid accidentally inheriting a controller from a parent module. [Nicholas Seckar]
-
-* Set sweeper's @controller to nil after a request so that the controller may be collected between requests. [Nicholas Seckar]
-
-* Subclasses of ActionController::Caching::Sweeper should be Reloadable. [Rick Olson]
-
-* Document the :xhr option for verifications. #3666 [leeo]
-
-* Added :only and :except controls to skip_before/after_filter just like for when you add filters [David Heinemeier Hansson]
-
-* Ensure that the instance variables are copied to the template when performing render :update. [Nicholas Seckar]
-
-* Add the ability to call JavaScriptGenerator methods from helpers called in update blocks. [Sam Stephenson] Example:
- module ApplicationHelper
- def update_time
- page.replace_html 'time', Time.now.to_s(:db)
- page.visual_effect :highlight, 'time'
- end
- end
-
- class UserController < ApplicationController
- def poll
- render :update { |page| page.update_time }
- end
- end
-
-* Add render(:update) to ActionView::Base. [Sam Stephenson]
-
-* Fix render(:update) to not render layouts. [Sam Stephenson]
-
-* Fixed that SSL would not correctly be detected when running lighttpd/fcgi behind lighttpd w/mod_proxy #3548 [Steve Purcell]
-
-* Added the possibility to specify atomatic expiration for the memcachd session container #3571 [Stefan Kaes]
-
-* Change layout discovery to take into account the change in semantics with File.join and nil arguments. [Marcel Molina Jr.]
-
-* Raise a RedirectBackError if redirect_to :back is called when there's no HTTP_REFERER defined #3049 [Kevin Clark]
-
-* Treat timestamps like datetimes for scaffolding purposes #3388 [Maik Schmidt]
-
-* Fix IE bug with link_to "something", :post => true #3443 [Justin Palmer]
-
-* Extract Test::Unit::TestCase test process behavior into an ActionController::TestProcess module. [Sam Stephenson]
-
-* Pass along blocks from render_to_string to render. [Sam Stephenson]
-
-* Add render :update for inline RJS. [Sam Stephenson] Example:
- class UserController < ApplicationController
- def refresh
- render :update do |page|
- page.replace_html 'user_list', :partial => 'user', :collection => @users
- page.visual_effect :highlight, 'user_list'
- end
- end
- end
-
-* allow nil objects for error_messages_for [Michael Koziarski]
-
-* Refactor human_size to exclude decimal place if it is zero. [Marcel Molina Jr.]
-
-* Update to Prototype 1.5.0_pre0 [Sam Stephenson]
-
-* Automatically discover layouts when a controller is namespaced. #2199, #3424 [me@jonnii.com rails@jeffcole.net Marcel Molina Jr.]
-
-* Add support for multiple proxy servers to CgiRequest#host [gaetanot@comcast.net]
-
-* Documentation typo fix. #2367 [Blair Zajac]
-
-* Remove Upload Progress. #2871 [Sean Treadway]
-
-* Fix typo in function name mapping in auto_complete_field. #2929 #3446 [doppler@gmail.com phil.ross@gmail.com]
-
-* Allow auto-discovery of third party template library layouts. [Marcel Molina Jr.]
-
-* Have the form builder output radio button, not check box, when calling the radio button helper. #3331 [LouisStAmour@gmail.com]
-
-* Added assignment of the Autocompleter object created by JavaScriptMacroHelper#auto_complete_field to a local javascript variables [David Heinemeier Hansson]
-
-* Added :on option for PrototypeHelper#observe_field that allows you to specify a different callback hook to have the observer trigger on [David Heinemeier Hansson]
-
-* Added JavaScriptHelper#button_to_function that works just like JavaScriptHelper#link_to_function but uses a button instead of a href [David Heinemeier Hansson]
-
-* Added that JavaScriptHelper#link_to_function will honor existing :onclick definitions when adding the function call [David Heinemeier Hansson]
-
-* Added :disable_with option to FormTagHelper#submit_tag to allow for easily disabled submit buttons with different text [David Heinemeier Hansson]
-
-* Make auto_link handle nil by returning quickly if blank? [Scott Barron]
-
-* Make auto_link match urls with a port number specified. [Marcel Molina Jr.]
-
-* Added support for toggling visual effects to ScriptaculousHelper::visual_effect, #3323. [Thomas Fuchs]
-
-* Update to script.aculo.us to 1.5.0 rev. 3343 [Thomas Fuchs]
-
-* Added :select option for JavaScriptMacroHelper#auto_complete_field that makes it easier to only use part of the auto-complete suggestion as the value for insertion [Thomas Fuchs]
-
-* Added delayed execution of Javascript from within RJS #3264 [devslashnull@gmail.com]. Example:
-
- page.delay(20) do
- page.visual_effect :fade, 'notice'
- end
-
-* Add session ID to default logging, but remove the verbose description of every step [David Heinemeier Hansson]
-
-* Add the following RJS methods: [Sam Stephenson]
-
- * alert - Displays an alert() dialog
- * redirect_to - Changes window.location.href to simulate a browser redirect
- * call - Calls a JavaScript function
- * assign - Assigns to a JavaScript variable
- * << - Inserts an arbitrary JavaScript string
-
-* Fix incorrect documentation for form_for [Nicholas Seckar]
-
-* Don't include a layout when rendering an rjs template using render's :template option. [Marcel Molina Jr.]
-
-*1.1.2* (December 13th, 2005)
-
-* Become part of Rails 1.0
-
-* Update to script.aculo.us 1.5.0 final (equals 1.5.0_rc6) [Thomas Fuchs]
-
-* Update to Prototype 1.4.0 final [Sam Stephenson]
-
-* Added form_remote_for (form_for meets form_remote_tag) [David Heinemeier Hansson]
-
-* Update to script.aculo.us 1.5.0_rc6
-
-* More robust relative url root discovery for SCGI compatibility. This solves the 'SCGI routes problem' -- you no longer need to prefix all your routes with the name of the SCGI mountpoint. #3070 [Dave Ringoen]
-
-* Fix docs for text_area_tag. #3083. [Christopher Cotton]
-
-* Change form_for and fields_for method signatures to take object name and object as separate arguments rather than as a Hash. [David Heinemeier Hansson]
-
-* Introduce :selected option to the select helper. Allows you to specify a selection other than the current value of object.method. Specify :selected => nil to leave all options unselected. #2991 [Jonathan Viney <jonathan@bluewire.net.nz>]
-
-* Initialize @optional in routing code to avoid warnings about uninitialized access to an instance variable. [Nicholas Seckar]
-
-* Make ActionController's render honor the :locals option when rendering a :file. #1665. [Emanuel Borsboom, Marcel Molina Jr.]
-
-* Allow assert_tag(:conditions) to match the empty string when a tag has no children. Closes #2959. [Jamis Buck]
-
-* Update html-scanner to handle CDATA sections better. Closes #2970. [Jamis Buck]
-
-* Don't put flash in session if sessions are disabled. [Jeremy Kemper]
-
-* Strip out trailing &_= for raw post bodies. Closes #2868. [Sam Stephenson]
-
-* Pass multiple arguments to Element.show and Element.hide in JavaScriptGenerator instead of using iterators. [Sam Stephenson]
-
-* Improve expire_fragment documentation. #2966 [court3nay]
-
-* Correct docs for automatic layout assignment. #2610. [Charles M. Gerungan]
-
-* Always create new AR sessions rather than trying too hard to avoid database traffic. #2731 [Jeremy Kemper]
-
-* Update to Prototype 1.4.0_rc4. Closes #2943 (old Array.prototype.reverse behavior can be obtained by passing false as an argument). [Sam Stephenson]
-
-* Use Element.update('id', 'html') instead of $('id').innerHTML = 'html' in JavaScriptGenerator#replace_html so that script tags are evaluated. [Sam Stephenson]
-
-* Make rjs templates always implicitly skip out on layouts. [Marcel Molina Jr.]
-
-* Correct length for the truncate text helper. #2913 [Stefan Kaes]
-
-* Update to Prototype 1.4.0_rc3. Closes #1893, #2505, #2550, #2748, #2783. [Sam Stephenson]
-
-* Add support for new rjs templates which wrap an update_page block. [Marcel Molina Jr.]
-
-* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
-
-* Correct time_zone_options_for_select docs. #2892 [pudeyo@rpi.com]
-
-* Remove the unused, slow response_dump and session_dump variables from error pages. #1222 [lmarlow]
-
-* Performance tweaks: use Set instead of Array to speed up prototype helper include? calls. Avoid logging code if logger is nil. Inline commonly-called template presence checks. #2880, #2881, #2882, #2883 [Stefan Kaes]
-
-* MemCache store may be given multiple addresses. #2869 [Ryan Carver <ryan@fivesevensix.com>]
-
-* Handle cookie parsing irregularity for certain Nokia phones. #2530 [zaitzow@gmail.com]
-
-* Added PrototypeHelper::JavaScriptGenerator and PrototypeHelper#update_page for easily modifying multiple elements in an Ajax response. [Sam Stephenson] Example:
-
- update_page do |page|
- page.insert_html :bottom, 'list', '<li>Last item</li>'
- page.visual_effect :highlight, 'list'
- page.hide 'status-indicator', 'cancel-link'
- end
-
- generates the following JavaScript:
-
- new Insertion.Bottom("list", "<li>Last item</li>");
- new Effect.Highlight("list");
- ["status-indicator", "cancel-link"].each(Element.hide);
-
-* Refactored JavaScriptHelper into PrototypeHelper and ScriptaculousHelper [Sam Stephenson]
-
-* Update to latest script.aculo.us version (as of [3031])
-
-* Updated docs for in_place_editor, fixes a couple bugs and offers extended support for external controls [Justin Palmer]
-
-* Update documentation for render :file. #2858 [Tom Werner]
-
-* Only include builtin filters whose filenames match /^[a-z][a-z_]*_helper.rb$/ to avoid including operating system metadata such as ._foo_helper.rb. #2855 [court3nay]
-
-* Added FormHelper#form_for and FormHelper#fields_for that makes it easier to work with forms for single objects also if they don't reside in instance variables [David Heinemeier Hansson]. Examples:
-
- <% form_for :person, @person, :url => { :action => "update" } do |f| %>
- First name: <%= f.text_field :first_name %>
- Last name : <%= f.text_field :last_name %>
- Biography : <%= f.text_area :biography %>
- Admin? : <%= f.check_box :admin %>
- <% end %>
-
- <% form_for :person, person, :url => { :action => "update" } do |person_form| %>
- First name: <%= person_form.text_field :first_name %>
- Last name : <%= person_form.text_field :last_name %>
-
- <% fields_for :permission => person.permission do |permission_fields| %>
- Admin? : <%= permission_fields.check_box :admin %>
- <% end %>
- <% end %>
-
-* options_for_select allows any objects which respond_to? :first and :last rather than restricting to Array and Range. #2824 [Jacob Robbins <jrobbins@cmj.com>, Jeremy Kemper]
-
-* The auto_link text helper accepts an optional block to format the link text for each url and email address. Example: auto_link(post.body) { |text| truncate(text, 10) } [Jeremy Kemper]
-
-* assert_tag uses exact matches for string conditions, instead of partial matches. Use regex to do partial matches. #2799 [Jamis Buck]
-
-* CGI::Session::ActiveRecordStore.data_column_name = 'foobar' to use a different session data column than the 'data' default. [nbpwie102@sneakemail.com]
-
-* Do not raise an exception when default helper is missing; log a debug message instead. It's nice to delete empty helpers. [Jeremy Kemper]
-
-* Controllers with acronyms in their names (e.g. PDFController) require the correct default helper (PDFHelper in file pdf_helper.rb). #2262 [jeff@opendbms.com]
-
-
-*1.11.0* (November 7th, 2005)
-
-* Added request as instance method to views, so you can do <%= request.env["HTTP_REFERER"] %>, just like you can already access response, session, and the likes [David Heinemeier Hansson]
-
-* Fix conflict with assert_tag and Glue gem #2255 [david.felstead@gmail.com]
-
-* Add documentation to assert_tag indicating that it only works with well-formed XHTML #1937, #2570 [Jamis Buck]
-
-* Added action_pack.rb stub so that ActionPack::Version loads properly [Sam Stephenson]
-
-* Added short-hand to assert_tag so assert_tag :tag => "span" can be written as assert_tag "span" [David Heinemeier Hansson]
-
-* Added skip_before_filter/skip_after_filter for easier control of the filter chain in inheritance hierachies [David Heinemeier Hansson]. Example:
-
- class ApplicationController < ActionController::Base
- before_filter :authenticate
- end
-
- class WeblogController < ApplicationController
- # will run the :authenticate filter
- end
-
- class SignupController < ActionController::Base
- # will not run the :authenticate filter
- skip_before_filter :authenticate
- end
-
-* Added redirect_to :back as a short-hand for redirect_to(request.env["HTTP_REFERER"]) [David Heinemeier Hansson]
-
-* Change javascript_include_tag :defaults to not use script.aculo.us loader, which facilitates the use of plugins for future script.aculo.us and third party javascript extensions, and provide register_javascript_include_default for plugins to specify additional JavaScript files to load. Removed slider.js and builder.js from actionpack. [Thomas Fuchs]
-
-* Fix problem where redirecting components can cause an infinite loop [Rick Olson]
-
-* Added support for the queue option on visual_effect [Thomas Fuchs]
-
-* Update script.aculo.us to V1.5_rc4 [Thomas Fuchs]
-
-* Fix that render :text didn't interpolate instance variables #2629, #2626 [Stefan Kaes]
-
-* Fix line number detection and escape RAILS_ROOT in backtrace Regexp [Nicholas Seckar]
-
-* Fixed document.getElementsByClassName from Prototype to be speedy again [Sam Stephenson]
-
-* Recognize ./#{RAILS_ROOT} as RAILS_ROOT in error traces [Nicholas Seckar]
-
-* Remove ARStore session fingerprinting [Nicholas Seckar]
-
-* Fix obscure bug in ARStore [Nicholas Seckar]
-
-* Added TextHelper#strip_tags for removing HTML tags from a string (using HTMLTokenizer) #2229 [marcin@junkheap.net]
-
-* Added a reader for flash.now, so it's possible to do stuff like flash.now[:alert] ||= 'New if not set' #2422 [Caio Chassot]
-
-
-*1.10.2* (October 26th, 2005)
-
-* Reset template variables after using render_to_string [Stefan Kaes]
-
-* Expose the session model backing CGI::Session
-
-* Abbreviate RAILS_ROOT in traces
-
-
-*1.10.1* (October 19th, 2005)
-
-* Update error trace templates [Nicholas Seckar]
-
-* Stop showing generated routing code in application traces [Nicholas Seckar]
-
-
-*1.10.0* (October 16th, 2005)
-
-* Make string-keys locals assigns optional. Add documentation describing depreciated state [Stefan Kaes]
-
-* Improve line number detection for template errors [Nicholas Seckar]
-
-* Update/clean up documentation (rdoc)
-
-* Upgrade to Prototype 1.4.0_rc0 [Sam Stephenson]
-
-* Added assert_vaild. Reports the proper AR error messages as fail message when the passed record is invalid [Tobias Lütke]
-
-* Add temporary support for passing locals to render using string keys [Nicholas Seckar]
-
-* Clean up error pages by providing better backtraces [Nicholas Seckar]
-
-* Raise an exception if an attempt is made to insert more session data into the ActiveRecordStore data column than the column can hold. #2234. [justin@textdrive.com]
-
-* Removed references to assertions.rb from actionpack assert's backtraces. Makes error reports in functional unit tests much less noisy. [Tobias Lütke]
-
-* Updated and clarified documentation for JavaScriptHelper to be more concise about the various options for including the JavaScript libs. [Thomas Fuchs]
-
-* Hide "Retry with Breakpoint" button on error pages until feature is functional. [David Heinemeier Hansson]
-
-* Fix Request#host_with_port to use the standard port when Rails is behind a proxy. [Nicholas Seckar]
-
-* Escape query strings in the href attribute of URLs created by url_helper. #2333 [Michael Schuerig <michael@schuerig.de>]
-
-* Improved line number reporting for template errors [Nicholas Seckar]
-
-* Added :locals support for render :inline #2463 [mdabney@cavoksolutions.com]
-
-* Unset the X-Requested-With header when using the xhr wrapper in functional tests so that future requests aren't accidentally xhr'ed #2352 [me@julik.nl, Sam Stephenson]
-
-* Unescape paths before writing cache to file system. #1877. [Damien Pollet]
-
-* Wrap javascript_tag contents in a CDATA section and add a cdata_section method to TagHelper #1691 [Michael Schuerig, Sam Stephenson]
-
-* Misc doc fixes (typos/grammar/etc). #2445. [coffee2code]
-
-* Speed improvement for session_options. #2287. [Stefan Kaes]
-
-* Make cacheing binary files friendly with Windows. #1975. [Rich Olson]
-
-* Convert boolean form options form the tag_helper. #809. [Michael Schuerig <michael@schuerig.de>]
-
-* Fixed that an instance variable with the same name as a partial should be implicitly passed as the partial :object #2269 [court3nay]
-
-* Update Prototype to V1.4.0_pre11, script.aculo.us to [2502] [Thomas Fuchs]
-
-* Make assert_tag :children count appropriately. Closes #2181. [jamie@bravenet.com]
-
-* Forced newer versions of RedCloth to use hard breaks [David Heinemeier Hansson]
-
-* Added new scriptaculous options for auto_complete_field #2343 [Manfred Stienstra]
-
-* Don't prepend the asset host if the string is already a fully-qualified URL
-
-* Updated to script.aculo.us V1.5.0_rc2 and Prototype to V1.4.0_pre7 [Thomas Fuchs]
-
-* Undo condition change made in [2345] to prevent normal parameters arriving as StringIO.
-
-* Tolerate consecutive delimiters in query parameters. #2295 [darashi@gmail.com]
-
-* Streamline render process, code cleaning. Closes #2294. [skae]
-
-* Keep flash after components are rendered. #2291 [Rick Olson, Scott]
-
-* Shorten IE file upload path to filename only to match other browsers. #1507 [court3nay]
-
-* Fix open/save dialog in IE not opening files send with send_file/send_data, #2279 [Thomas Fuchs]
-
-* Fixed that auto_discovery_link_tag couldn't take a string as the URL [David Heinemeier Hansson]
-
-* Fixed problem with send_file and WEBrick using stdout #1812 [David Heinemeier Hansson]
-
-* Optimized tag_options to not sort keys, which is no longer necessary when assert_dom_equal and friend is available #1995 [skae]
-
-* Added assert_dom_equal and assert_dom_not_equal to compare tags generated by the helpers in an order-indifferent manner #1995 [skae]
-
-* Fixed that Request#domain caused an exception if the domain header wasn't set in the original http request #1795 [Michael Koziarski]
-
-* Make the truncate() helper multi-byte safe (assuming $KCODE has been set to something other than "NONE") #2103
-
-* Add routing tests from #1945 [ben@groovie.org]
-
-* Add a routing test case covering #2101 [Nicholas Seckar]
-
-* Cache relative_url_root for all webservers, not just Apache #2193 [skae]
-
-* Speed up cookie use by decreasing string copying #2194 [skae]
-
-* Fixed access to "Host" header with requests made by crappy old HTTP/1.0 clients #2124 [Marcel Molina Jr.]
-
-* Added easy assignment of fragment cache store through use of symbols for included stores (old way still works too)
-
- Before:
- ActionController::Base.fragment_cache_store =
- ActionController::Base::Caching::Fragments::FileStore.new("/path/to/cache/directory")
-
- After:
- ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
-
-* Added ActionController::Base.session_store=, session_store, and session_options to make it easier to tweak the session options (instead of going straight to ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS)
-
-* Added TextHelper#cycle to cycle over an array of values on each hit (useful for alternating row colors etc) #2154 [dave-ml@dribin.org]
-
-* Ensure that request.path never returns nil. Closes #1675 [Nicholas Seckar]
-
-* Add ability to specify Route Regexps for controllers. Closes #1917. [Sebastian Kanthak]
-
-* Provide Named Route's hash methods as helper methods. Closes #1744. [Nicholas Seckar, Steve Purcell]
-
-* Added :multipart option to ActiveRecordHelper#form to make it possible to add file input fields #2034 [jstirk@oobleyboo.com]
-
-* Moved auto-completion and in-place editing into the Macros module and their helper counterparts into JavaScriptMacrosHelper
-
-* Added in-place editing support in the spirit of auto complete with ActionController::Base.in_place_edit_for, JavascriptHelper#in_place_editor_field, and Javascript support from script.aculo.us #2038 [Jon Tirsen]
-
-* Added :disabled option to all data selects that'll make the elements inaccessible for change #2167, #253 [eigentone]
-
-* Fixed that TextHelper#auto_link_urls would include punctuation in the links #2166, #1671 [eigentone]
-
-* Fixed that number_to_currency(1000, {:precision => 0})) should return "$1,000", instead of "$1,000." #2122 [sd@notso.net]
-
-* Allow link_to_remote to use any DOM-element as the parent of the form elements to be submitted #2137 [erik@ruby-lang.nl]. Example:
-
- <tr id="row023">
- <td><input name="foo"/></td>
- <td><input name="bar"/></td>
- <td><%= link_to_remote 'Save', :update => "row023",
- :submit => "row023", :url => {:action => 'save_row'} %></td>
- </tr>
-
-* Fixed that render :partial would fail when :object was a Hash (due to backwards compatibility issues) #2148 [Sam Stephenson]
-
-* Fixed JavascriptHelper#auto_complete_for to only include unique items #2153 [Thomas Fuchs]
-
-* Fixed all AssetHelper methods to work with relative paths, such that javascript_include_tag('stdlib/standard') will look in /javascripts/stdlib/standard instead of '/stdlib/standard/' #1963
-
-* Avoid extending view instance with helper modules each request. Closes #1979
-
-* Performance improvements to CGI methods. Closes #1980 [Stefan Kaes]
-
-* Added :post option to UrlHelper#link_to that makes it possible to do POST requests through normal ahref links using Javascript
-
-* Fixed overwrite_params
-
-* Added ActionController::Base.benchmark and ActionController::Base.silence to allow for easy benchmarking and turning off the log
-
-* Updated vendor copy of html-scanner to support better xml parsing
-
-* Added :popup option to UrlHelper#link_to #1996 [gabriel.gironda@gmail.com]. Examples:
-
- link_to "Help", { :action => "help" }, :popup => true
- link_to "Busy loop", { :action => "busy" }, :popup => ['new_window', 'height=300,width=600']
-
-* Drop trailing \000 if present on RAW_POST_DATA (works around bug in Safari Ajax implementation) #918
-
-* Fix observe_field to fall back to event-based observation if frequency <= 0 #1916 [Michael Schubert]
-
-* Allow use of the :with option for submit_to_remote #1936 [jon@instance-design.co.uk]
-
-* AbstractRequest#domain returns nil when host is an ip address #2012 [Kevin Clark]
-
-* ActionController documentation update #2051 [François Beausoleil]
-
-* Yield @content_for_ variables to templates #2058 [Sam Stephenson]
-
-* Make rendering an empty partial collection behave like :nothing => true #2080 [Sam Stephenson]
-
-* Add option to specify the singular name used by pagination.
-
-* Use string key to obtain action value. Allows indifferent hashes to be disabled.
-
-* Added ActionView::Base.cache_template_loading back.
-
-* Rewrote compiled templates to decrease code complexity. Removed template load caching in favour of compiled caching. Fixed template error messages. [Nicholas Seckar]
-
-* Fix Routing to handle :some_param => nil better. [Nicholas Seckar, Luminas]
-
-* Add support for :include with pagination (subject to existing constraints for :include with :limit and :offset) #1478 [Michael Schubert]
-
-* Prevent the benchmark module from blowing up if a non-HTTP/1.1 request is processed
-
-* Added :use_short_month option to select_month helper to show month names as abbreviations
-
-* Make link_to escape the javascript in the confirm option #1964 [nicolas.pouillard@gmail.com]
-
-* Make assert_redirected_to properly check URL's passed as strings #1910 [Scott Barron]
-
-* Make sure :layout => false is always used when rendering inside a layout
-
-* Use raise instead of assert_not_nil in Test::Unit::TestCase#process to ensure that the test variables (controller, request, response) have been set
-
-* Make sure assigns are built for every request when testing #1866
-
-* Allow remote_addr to be queried on TestRequest #1668
-
-* Fixed bug when a partial render was passing a local with the same name as the partial
-
-* Improved performance of test app req/sec with ~10% refactoring the render method #1823 [Stefan Kaes]
-
-* Improved performance of test app req/sec with 5-30% through a series of Action Pack optimizations #1811 [Stefan Kaes]
-
-* Changed caching/expiration/hit to report using the DEBUG log level and errors to use the ERROR log level instead of both using INFO
-
-* Added support for per-action session management #1763
-
-* Improved rendering speed on complicated templates by up to 100% (the more complex the templates, the higher the speedup) #1234 [Stefan Kaes]. This did necessasitate a change to the internals of ActionView#render_template that now has four parameters. Developers of custom view handlers (like Amrita) need to update for that.
-
-* Added options hash as third argument to FormHelper#input, so you can do input('person', 'zip', :size=>10) #1719 [jeremye@bsa.ca.gov]
-
-* Added Base#expires_in(seconds)/Base#expires_now to control HTTP content cache headers #1755 [Thomas Fuchs]
-
-* Fixed line number reporting for Builder template errors #1753 [piotr]
-
-* Fixed assert_routing so that testing controllers in modules works as expected [Nicholas Seckar, Rick Olson]
-
-* Fixed bug with :success/:failure callbacks for the JavaScriptHelper methods #1730 [court3nay/Thomas Fuchs]
-
-* Added named_route method to RouteSet instances so that RouteSet instance methods do not prevent certain names from being used. [Nicholas Seckar]
-
-* Fixed routes so that routes which do not specify :action in the path or in the requirements have a default of :action => 'index', In addition, fixed url generation so that :action => 'index' does not need to be provided for such urls. [Nicholas Seckar, Markjuh]
-
-* Worked around a Safari bug where it wouldn't pass headers through if the response was zero length by having render :nothing return ' ' instead of ''
-
-* Fixed Request#subdomains to handle "foo.foo.com" correctly
-
-
-*1.9.1* (11 July, 2005)
-
-* Fixed that auto_complete_for didn't force the input string to lower case even as the db comparison was
-
-* Fixed that Action View should always use the included Builder, never attempt to require the gem, to ensure compatibility
-
-* Added that nil options are not included in tags, so tag("p", :ignore => nil) now returns <p /> not <p ignore="" /> but that tag("p", :ignore => "") still includes it #1465 [Michael Schuerig]
-
-* Fixed that UrlHelper#link_to_unless/link_to_if used html_escape on the name if no link was to be applied. This is unnecessary and breaks its use with images #1649 [joergd@pobox.com]
-
-* Improved error message for DoubleRenderError
-
-* Fixed routing to allow for testing of *path components #1650 [Nicholas Seckar]
-
-* Added :handle as an option to sortable_element to restrict the drag handle to a given class #1642 [thejohnny]
-
-* Added a bunch of script.aculo.us features #1644, #1677, #1695 [Thomas Fuchs]
- * Effect.ScrollTo, to smoothly scroll the page to an element
- * Better Firefox flickering handling on SlideUp/SlideDown
- * Removed a possible memory leak in IE with draggables
- * Added support for cancelling dragging my hitting ESC
- * Added capability to remove draggables/droppables and redeclare sortables in dragdrop.js (this makes it possible to call sortable_element on the same element more than once, e.g. in AJAX returns that modify the sortable element. all current sortable 'stuff' on the element will be discarded and the sortable will be rebuilt)
- * Always reset background color on Effect.Highlight; this make change backwards-compatibility, to be sure include style="background-color:(target-color)" on your elements or else elements will fall back to their CSS rules (which is a good thing in most circumstances)
- * Removed circular references from element to prevent memory leaks (still not completely gone in IE)
- * Changes to class extension in effects.js
- * Make Effect.Highlight restore any previously set background color when finishing (makes effect work with CSS classes that set a background color)
- * Fixed myriads of memory leaks in IE and Gecko-based browsers [David Zülke]
- * Added incremental and local autocompleting and loads of documentation to controls.js [Ivan Krstic]
- * Extended the auto_complete_field helper to accept tokens option
- * Changed object extension mechanism to favor Object.extend to make script.aculo.us easily adaptable to support 3rd party libs like IE7.js [David Zülke]
-
-* Fixed that named routes didn't use the default values for action and possible other parameters #1534 [Nicholas Seckar]
-
-* Fixed JavascriptHelper#visual_effect to use camelize such that :blind_up will work #1639 [pelletierm@eastmedia.net]
-
-* Fixed that a SessionRestoreError was thrown if a model object was placed in the session that wasn't available to all controllers. This means that it's no longer necessary to use the 'model :post' work-around in ApplicationController to have a Post model in your session.
-
-
-*1.9.0* (6 July, 2005)
-
-* Added logging of the request URI in the benchmark statement (makes it easy to grep for slow actions)
-
-* Added javascript_include_tag :defaults shortcut that'll include all the default javascripts included with Action Pack (prototype, effects, controls, dragdrop)
-
-* Cache several controller variables that are expensive to calculate #1229 [Stefan Kaes]
-
-* The session class backing CGI::Session::ActiveRecordStore may be replaced with any class that duck-types with a subset of Active Record. See docs for details #1238 [Stefan Kaes]
-
-* Fixed that hashes was not working properly when passed by GET to lighttpd #849 [Nicholas Seckar]
-
-* Fixed assert_template nil will be true when no template was rendered #1565 [maceywj@telus.net]
-
-* Added :prompt option to FormOptions#select (and the users of it, like FormOptions#select_country etc) to create "Please select" style descriptors #1181 [Michael Schuerig]
-
-* Added JavascriptHelper#update_element_function, which returns a Javascript function (or expression) that'll update a DOM element according to the options passed #933 [mortonda@dgrmm.net]. Examples:
-
- <%= update_element_function("products", :action => :insert, :position => :bottom, :content => "<p>New product!</p>") %>
-
- <% update_element_function("products", :action => :replace, :binding => binding) do %>
- <p>Product 1</p>
- <p>Product 2</p>
- <% end %>
-
-* Added :field_name option to DateHelper#select_(year|month|day) to deviate from the year/month/day defaults #1266 [Marcel Molina Jr.]
-
-* Added JavascriptHelper#draggable_element and JavascriptHelper#drop_receiving_element to facilitate easy dragging and dropping through the script.aculo.us libraries #1578 [Thomas Fuchs]
-
-* Added that UrlHelper#mail_to will now also encode the default link title #749 [f.svehla@gmail.com]
-
-* Removed the default option of wrap=virtual on FormHelper#text_area to ensure XHTML compatibility #1300 [thomas@columbus.rr.com]
-
-* Adds the ability to include XML CDATA tags using Builder #1563 [Josh Knowles]. Example:
-
- xml.cdata! "some text" # => <![CDATA[some text]]>
-
-* Added evaluation of <SCRIPT> blocks in content returned to Ajax calls #1577 [Thomas Fuchs/court3nay/Sean Treadway]
-
-* Directly generate paths with a leading slash instead of tacking it on later. #1543 [Nicholas Seckar]
-
-* Fixed errant parameter modification in functional tests. #1542 [Nicholas Seckar]
-
-* Routes fail with leading slash #1540 [Nicholas Seckar]
-
-* Added support for graceful error handling of Ajax calls #1217 [Jamis Buck/Thomas Fuchs]. Example:
-
- link_to_remote(
- "test",
- :url => { :action => "faulty" },
- :update => { :success => "good", :failure => "bad" },
- 403 => "alert('Forbidden- got ya!')",
- 404 => "alert('Nothing there...?')",
- :failure => "alert('Unkown error ' + request.status)")
-
-* Attempt to explicitly flush the output at the end of CgiProcess#out
-
-* Fixed assert_redirected_to to handle absolute controller paths properly #1472 [Rick Olson/Nicholas Seckar]
-
-* Added event-based observations when frequency is not set on observe_field/form #1474 [flash@vanklinkenbergsoftware.nl]
-
-* Added script.aculo.us Javascripts (controls.js, dragdrop.js, effects.js) (NEEDS MORE DESCRIPTION) #1509 [Thomas Fuchs]
-
-* Fixed prototype to consider all fields it doesn't know as text (such as Safari's search) just like the browser in its serialization #1497 [Sean Treadway]
-
-* Improved performance of Routes generation by a factor of 5 #1434 [Nicholas Seckar]
-
-* Added named routes (NEEDS BETTER DESCRIPTION) #1434 [Nicholas Seckar]
-
-* Improved AbstractRequest documentation #1483 [court3nay]
-
-* Added ActionController::Base.allow_concurrency to control whether the application is thread-safe, so multi-threaded servers like WEBrick knows whether to apply a mutex around the performance of each action. Turned off by default. EXPERIMENTAL FEATURE.
-
-* Added TextHelper#word_wrap(text, line_length = 80) #1449 [tuxie@dekadance.se]
-
-* Added a fall-through action for form_remote_tag that'll be used in case Javascript is unavailable #1459 [Scott Barron]. Example:
-
- form_remote_tag :html => { :action => url_for(:controller => "some", :action => "place") }
-
-* Added :xhr => true/false option to verify so you can ensure that a request is coming from an Ajax call or not #1464 [Thomas Fuchs]
-
-* Added tag_options as a third parameter to AssetHelper#auto_discovery_link_tag to control options like the title of the link #1430 [Kevin Clark]
-
-* Added option to pass in parameters to CaptureHelper#capture, so you can create more advanced view helper methods #1466 [duane.johnson@gmail.com]. Example:
-
- <% show_calendar(:year => 2005, :month => 6) do |day, options| %>
- <% options[:bgcolor] = '#dfd' if 10..15.include? day %>
- [<%= day %>]
- <% end %>
-
-* Changed the default name of the input tag generated by FormTagHelper#submit_tag from "submit" to "commit" so it doesn't clash with form.submit() calls in Javascript #1271
-
-* Fixed relative urls support for lighttpd #1048 [Nicholas Seckar/maznawak@nerim.net]
-
-* Correct distance_of_time_in_words for integer arguments and make the second arg optional, treating the first arg as a duration in seconds. #1458 [madrobby <thomas@fesch.at>]
-
-* Fixed query parser to deal gracefully with equal signs inside keys and values #1345 [gorou].
- Example: /?sig=abcdef=:foobar=&x=y will pass now.
-
-* Added Cuba to country list #1351 [todd]
-
-* Fixed radio_button to work with numeric values #1352 [demetrius]
-
-* Added :extension option to NumberHelper#number_to_phone #1361 [delynnb]
-
-* Added button_to as a form-based solution to deal with harmful actions that should be hidden behind POSTs. This makes it just as easy as link_to to create a safe trigger for actions like destroy, although it's limited by being a block element, the fixed look, and a no-no inside other forms. #1371 [tom@moertel.com]
-
-* Fixed image_tag so an exception is not thrown just because the image is missing and alt value can't be generated #1395 [Marcel Molina Jr.]
-
-* Added a third parameter to TextHelper#auto_link called href_options for specifying additional tag options on the links generated #1401 [tyler.kovacs@gmail.com]. Example: auto_link(text, :all, { :target => "_blank" }) to have all the generated links open in a new window.
-
-* Fixed TextHelper#highlight to return the text, not nil, if the phrase is blank #1409 [Patrick Lenz]
-
-* Fixed TagHelper such that :name and 'name' keys in the options doesn't result in two attributes #1455 [take_tk]
-
-* Ensure that helpers are only available to the controllers where they are defined and their subclasses. #1394 [kdole@tamu.edu]
-
-* render("foo/bar") works with a layout again
-
-* Fixed double-singularization on scaffolded pagination call (Address would be turned into Addres) #1216, #1404 [nilsga]
-
-* Removed the require hack used by functional testing to work around an earlier bug in rake.
-
-* Allow distance_of_time_in_words to work with any value that responds to #to_time (like dates) #969
-
-* Support :render option for :verify #1440 [Tobias Lütke]
-
-* Updated vendor copy of html-scanner lib to 0.5.2, for bug fixes and optimizations. The :content option may be used as expected--to find a tag whose textual content is a particular value--in assert_tag, now.
-
-* Changed test requests to come from 0.0.0.0 instead of 127.0.0.1 such that they don't trigger debugging screens on exceptions, but instead call rescue_action_in_public
-
-* Modernize scaffolding to match the generator: use the new render method and change style from the warty @params["id"] to the sleek params[:id]. #1367
-
-* Include :id in the action generated by the form helper method. Then, for example, the controller can do Model.find(params[:id]) for both edit and update actions. Updated scaffolding to take advantage. #1367
-
-* Add assertions with friendly messages to TestCase#process to ensure that @controller, @request, and @response are set. #1367
-
-* Arrays, hashes sent via multipart posts are converted to strings #1032 [dj@omelia.org, me@julik.nl]
-
-* render(:layout => true) is a synonym for render(:layout => nil)
-
-* Make sure the benchmarking render method always returns the output of the render.
-
-* render(:action), render(:template) and render() are the only three calls that default to using a layout. All other render calls assume :layout => false. This also fixes send_file, which was applying a layout if one existed for the current action.
-
-* verify with :redirect_to won't redirect if a redirect or render has already been performed #1350
-
-* render(:partial => true) is identical to the behavior of the deprecated render_partial()
-
-* Fixed render(:partial => "...") to use an empty Hash for the local assigns #1365
-
-* Fixed Caching::Fragments::FileStore.delete to not raise an exception if the delete fails.
-
-* Deprecated all render_* methods in favor of consolidating all rendering behavior in Base#render(options). This enables more natural use of combining options, such as layouts. Examples:
-
- +---------------------------------------------------------------+-------------------------------------------------------+
- | BEFORE | AFTER |
- +---------------------------------------------------------------+-------------------------------------------------------+
- | render_with_layout "weblog/show", "200 OK", "layouts/dialog" | render :action => "show", :layout => "dialog" |
- | render_without_layout "weblog/show" | render :action => "show", :layout => false |
- | render_action "error", "404 Not Found" | render :action => "error", :status => "404 Not Found" |
- | render_template "xml.div('stuff')", "200 OK", :rxml | render :inline => "xml.div('stuff')", :type => :rxml |
- | render_text "hello world!" | render :text => "hello world!" |
- | render_partial_collection "person", @people, nil, :a => 1 | render :partial => "person", :collection => @people, |
- | | :locals => { :a => 1 } |
- +---------------------------------------------------------------+-------------------------------------------------------+
-
-* Deprecated redirect_to_path and redirect_to_url in favor of letting redirect_to do the right thing when passed either a path or url.
-
-* Fixed use of an integer as return code for renders, so render_text "hello world", 404 now works #1327
-
-* Fixed assert_redirect_to to work with redirect_to_path #869 [Nicholas Seckar]
-
-* Fixed escaping of :method option in remote_form_tag #1218 [Rick Olson]
-
-* Added Serbia and Montenegro to the country_select #1239 [todd@robotcoop.com]
-
-* Fixed Request#remote_ip in testing #1251 [Jeremy Kemper]
-
-* Fixed that compute_public_path should recognize external URLs, so image_tag("http://www.example.com/images/icon.gif") is not prefixed with the relative url path #1254 [victor-ronr-trac@carotena.net]
-
-* Added support for descending year values in DateHelper#select_year, like select_year(Date.today, :start_year => 2005, :end_year => 1900), which would count down from 2005 to 1900 instead of the other way #1274 [nwoods@mail.com]
-
-* Fixed that FormHelper#checkbox should return a checked checkbox if the value is the same as checked_value #1286 [Florian Weber]
-
-* Fixed Form.disable in Prototype #1317 [Wintermute]
-
-* Added accessors to logger, params, response, session, flash, and headers from the view, so you can write <% logger.info "stuff" %> instead of <% @logger.info "others" %> -- more consistent with the preferred way of accessing these attributes and collections from the controller
-
-* Added support for POST data in form of YAML or XML, which is controller through the Content-Type header. Example request:
-
- Content-Type: application/xml
- <request><item><content>HelloWorld</content></item></request>
-
- ...is the same as:
-
- Content-Type: application/x-yaml
- ---
- item:
- content: HelloWorld
-
- ...is the same as:
-
- item[content]=HelloWorld
-
- Which in the end turns into { "item" => { "content" => "HelloWorld" } }. This makes it a lot easier to publish REST web services on top of your regular actions (as they won't care).
-
- Example Curl call:
-
- curl -H 'Content-Type: application/xml' -d '<request><item><content>KillMeMore</content></item></request>' http://www.example.com/service
-
- You can query to find out whether a given request came through as one of these types with:
- - request.post_format? (:url_encoded, :xml or :yaml)
- - request.formatted_post? (for either xml or yaml)
- - request.xml_post?
- - request.yaml_post?
-
-* Added bundling of XmlSimple by Maik Schmidt
-
-* Fixed that render_partial_collection should always return a string (and not sometimes an array, despite <%= %> not caring)
-
-* Added TextHelper#sanitize that can will remove any Javascript handlers, blocks, and forms from an input of HTML. This allows for use of HTML on public sites, but still be free of XSS issues. #1277 [Jamis Buck]
-
-* Fixed the HTML scanner used by assert_tag where a infinite loop could be caused by a stray less-than sign in the input #1270 [Jamis Buck]
-
-* Added functionality to assert_tag, so you can now do tests on the siblings of a node, to assert that some element comes before or after the element in question, or just to assert that some element exists as a sibling #1226 [Jamis Buck]
-
-* Added better error handling for regexp caching expiration
-
-* Fixed handling of requests coming from unknown HTTP methods not to kill the server
-
-* Added that both AssetHelper#stylesheet_link_tag and AssetHelper#javascript_include_tag now accept an option hash as the last parameter, so you can do stuff like: stylesheet_link_tag "style", :media => "all"
-
-* Added FormTagHelper#image_submit_tag for making submit buttons that uses images
-
-* Added ActionController::Base.asset_host that will then be used by all the asset helpers. This enables you to easily offload static content like javascripts and images to a separate server tuned just for that.
-
-* Fixed action/fragment caching using the filestore when a directory and a file wanted to use the same name. Now there's a .cache prefix that sidesteps the conflict #1188 [imbcmdth@hotmail.com]
-
-* Fixed missing id uniqueness in FormTag#radio_button #1207 [Jarkko Laine]
-
-* Fixed assert_redirected_to to work with :only_path => false #1204 [Alisdair McDiarmid]
-
-* Fixed render_partial_collection to output an empty string instead of nil when handed an empty array #1202 [Ryan Carver]
-
-* Improved the speed of regular expression expirations for caching by a factor of 10 #1221 [Jamis Buck]
-
-* Removed dumping of template assigns on the rescue page as it would very easily include a ton of data making page loads take seconds (and the information was rarely helpful) #1222
-
-* Added BenchmarkHelper that can measure the execution time of a block in a template and reports the result to the log. Example:
-
- <% benchmark "Notes section" do %>
- <%= expensive_notes_operation %>
- <% end %>
-
- Will add something like "Notes section (0.345234)" to the log.
-
-* Added ActionController::Caching::Sweeper as an improved an easier to use sweeper. The new sweepers work on a single-step approach instead of two-steps like the old ones. Before
-
- def after_save(record)
- @list = record.is_a?(List) ? record : record.list
- end
-
- def filter(controller)
- controller.expire_page(:controller => "lists", :action => %w( show public feed ), :id => @list.id)
- controller.expire_action(:controller => "lists", :action => "all")
- @list.shares.each { |share| controller.expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
- end
-
- ..after:
-
- def after_save(record)
- list = record.is_a?(List) ? record : record.list
- expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
- expire_action(:controller => "lists", :action => "all")
- list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
- end
-
- The new sweepers can also observe on the actions themselves by implementing methods according to (before|after)_$controller_$action. Example of a callback that'll be called after PagesController#update_title has been performed:
-
- def after_pages_update_title
- expire_fragment(%r{pages/#{controller.assigns["page"].id}/.*})
- end
-
- Note that missing_method is delegated to the controller instance, which is assigned in a before filter. This means that you can call expire_fragment instead of @controller.expire_fragment.
-
-* Added that Fragments#expire_fragment now accepts as a regular expression as the name thereby deprecating expire_matched_fragments
-
-* Fixed that fragments shouldn't use the current host and the path as part of the key like pages does
-
-* Added conditions to around_filters just like before_filter and after_filter
-
-
-*1.8.1* (20th April, 2005)
-
-* Added xml_http_request/xhr method for simulating XMLHttpRequest in functional tests #1151 [Sam Stephenson]. Example:
-
- xhr :post, :index
-
-* Fixed that Ajax.Base.options.asynchronous wasn't being respected in Ajax.Request (thanks Jon Casey)
-
-* Fixed that :get, :post, and the others should take a flash array as the third argument just like process #1144 [rails@cogentdude.com]
-
-* Fixed a problem with Flash.now
-
-* Fixed stringification on all assigned hashes. The sacrifice is that assigns[:person] won't work in testing. Instead assigns["person"] or assigns(:person) must be used. In other words, the keys of assigns stay strings but we've added a method-based accessor to appease the need for symbols.
-
-* Fixed that rendering a template would require a connection to the database #1146
-
-
-*1.8.0* (19th April, 2005)
-
-* Added assert_tag and assert_no_tag as a much improved alternative to the deprecated assert_template_xpath_match #1126 [Jamis Buck]
-
-* Deprecated the majority of all the testing assertions and replaced them with a much smaller core and access to all the collections the old assertions relied on. That way the regular test/unit assertions can be used against these. Added documentation about how to use it all.
-
-* Added a wide range of new Javascript effects:
- * Effect.Puff zooms the element out and makes it smoothly transparent at the same time, giving a "puff" illusion #996 [thomas@fesch.at]
- After the animation is completed, the display property will be set to none.
- This effect will work on relative and absolute positioned elements.
-
- * Effect.Appear as the opposite of Effect.Fade #990 [thomas@fesch.at]
- You should return elements with style="display:none;" or a like class for this to work best and have no chance of flicker.
-
- * Effect.Squish for scaling down an element and making it disappear at the end #972 [thomas@fesch.at]
-
- * Effect.Scale for smoothly scaling images or text up and down #972 [thomas@fesch.at]
-
- * Effect.Fade which smoothly turns opacity from 100 to 0 and then hides the element #960 [thomas@fesch.at]
-
-* Added Request#xml_http_request? (and an alias xhr?) to that'll return true when the request came from one of the Javascript helper methods (Ajax). This can be used to give one behavior for modern browsers supporting Ajax, another to old browsers #1127 [Sam Stephenson]
-
-* Changed render_partial to take local assigns as the second parameter instead of an explicit object and then the assigns. So the API changes from:
-
- <%= render_partial "account", person, "rules" => regulations.rules %>
-
- ...to:
-
- <%= render_partial "account", :account => person, :rules => regulations.rules %>
-
- The old API will still work, though, and render_partial "account" will still assume :account => @account.
-
-* Added support for web servers that use PATH_INFO instead of REQUEST_URI like IIS #1014 [BradG/Nicholas Seckar]
-
-* Added graceful handling of PUT, DELETE, and OPTIONS requests for a complete coverage of REST functionality #1136 [Josh Knowles]
-
-* Fixed that you can now pass an alternative :href option to link_to_function/remote in order to point to somewhere other than # if the javascript fails or is turned off. You can do the same with form_remote_tag by passing in :action. #1113 [Sam Stephenson]
-
-* Fixed DateHelper to return values on the option tags such that they'll work properly in IE with form_remote_tag #1024 [Scott Raymond]
-
-* Fixed FormTagHelper#check_box to respect checked #1049 [DelynnB]
-
-* Added that render_partial called from a controller will use the action name as default #828 [Dan Peterson]
-
-* Added Element.toggle, Element.show, and Element.hide to the prototype javascript library. Toggle.display has been deprecated, but will still work #992 [Lucas Carlson]
-
-* Added that deleting a cookie should not just set it to an empty string but also instantly expire it #1118 [todd@robotcoop.com]
-
-* Added AssetTagHelper#image_path, AssetTagHelper#javascript_path, and AssetTagHelper#stylesheet_path #1110 [Larry Halff]
-
-* Fixed url_for(nil) in functional tests #1116 [Alisdair McDiarmid]
-
-* Fixed error handling of broken layouts #1115 [Michael Schubert]
-
-* Added submit_to_remote that allows you to trigger an Ajax form submition at the click of the submission button, which allows for multiple targets in a single form through the use of multiple submit buttons #930 [yrashk@gmail.com]
-
-* Fixed pagination to work with joins #1034 [scott@sigkill.org]
-
-* Fixed that *rest parameter in map.connect couldn't accept an empty list #1037 [Dee Zsombor]
-
-* Added :confirm option to link_to_remote just like link_to has #1082 [yrashk@fp.org.ua]
-
-* Added minute_step as an option to select_minute (and the helpers that use it) to jump in larger increments than just 1 minute. At 15, it would return 0, 15, 30, 45 options #1085 [ordwaye@evergreen.edu]
-
-* Fixed that an exception would be thrown when an empty form was submitted #1090 [jan@ulbrich-boerwang.de]
-
-* Moved TextHelper#human_size to NumberHelper#number_to_human_size, but kept an deprecated alias to the old method name
-
-* Fixed that the content-type for some browsers could include an additional \r which made wonky things happen #1067 [Thomas Fuchs]
-
-* Fixed that radio buttons shouldn't have a default size attribute #1074 [hendrik@mans.de]
-
-* Added ActionView::Helpers::InstanceTag::DEFAULT_RADIO_OPTIONS that contains a hash of default options for radio buttons #1074 [hendrik@mans.de]
-
-* Fixed that in some circumstances controllers outside of modules may have hidden ones inside modules. For example, admin/content might have been hidden by /content. #1075 [Nicholas Seckar]
-
-* Added JavascriptHelper#periodically_call_remote in order to create areas of a page that update automatically at a set interval #945 [Jon Tirsen]
-
-* Fixed Cache#expire_matched_fragments that couldn't recognize the difference between string and url_for options #1030 [Stefan Kaes]
-
-* Added simulation of @request.request_uri in functional tests #1038 [Jamis Buck]
-
-* Fixed autolinking to work better in more cases #1013 [Jamis Buck]
-
-* Added the possible of using symbols in form helpers that relate to instance variables like text_field :account, :name in addition to text_field "account", "name"'
-
-* Fixed javascript_include_tag to output type instead of language and conform to XHTML #1018 [Rick Olson]
-
-* Added NumberHelper for common string representations like phone number, currency, and percentage #1015 [DeLynn]
-
-* Added pagination for scaffolding (10 items per page) #964 [mortonda@dgrmm.net]
-
-* Added assert_no_cookie and fixed assert_cookie_equal to deal with non-existing cookies #979 [Jeremy Kemper]
-
-* Fixed :overwrite_param so it doesn't delete but reject elements from @request.parameters #982 [raphinou@yahoo.com]
-
-* Added :method option to verify for ensuring that either GET, POST, etc is allowed #984 [Jamis Buck]
-
-* Added options to set cc, bcc, subject, and body for UrlHelper#mail_to #966 [DeLynn]
-
-* Fixed include_blank for select_hour/minute/second #527 [edward@debian.org]
-
-* Improved the message display on the exception handler pages #963 [Johan Sorensen]
-
-* Fixed that on very rare occasions, webrick would raise a NoMethodError: private method 'split' called for nil #1001 [Flurin Egger]
-
-* Fixed problem with page caching #958 [Rick Olson]
-
-
-*1.7.0* (27th March, 2005)
-
-* Added ActionController::Base.page_cache_extension for setting the page cache file extension (the default is .html) #903 [Andreas]
-
-* Fixed "bad environment variable value" exception caused by Safari, Apache, and Ajax calls #918
-
-* Fixed that pagination_helper would ignore :params #947 [Sebastian Kanthak]
-
-* Added :owerwrite_params back to url_for and friends -- it was AWL since the introduction of Routes #921 [raphinou]
-
-* Added :position option to link_to_remote/form_remote_tag that can be either :before, :top, :bottom, or :after and specifies where the return from the method should be inserted #952 [Matthew McCray/Sam Stephenson]
-
-* Added Effect.Highlight to prototype.js to do Yellow Fade Technique (of 37signals' fame) on any container #952 [Sam Stephenson/court3nay]
-
-* Added include_seconds option as the third parameter to distance_of_time_in_words which will render "less than a minute" in higher resolution ("less than 10 seconds" etc) #944 [thomas@fesch.at]
-
-* Added fourth option to process in test cases to specify the content of the flash #949 [Jamis Buck]
-
-* Added Verifications that allows you to specify preconditions to actions in form of statements like <tt>verify :only => :update_post, :params => "admin_privileges", :redirect_to => { :action => "settings" }</tt>, which ensure that the update_post action is only called if admin_privileges is available as a parameter -- otherwise the user is redirected to settings. #897 [Jamis Buck]
-
-* Fixed Form.Serialize for the JavascriptHelper to also seriliaze password fields #934 [dweitzman@gmail.com]
-
-* Added JavascriptHelper#escape_javascript as a public method (was private) and made it escape both single and double quotes and new lines #940 [mortonda@dgrmm.net]
-
-* Added trailing_slash option to url_for, so you can generate urls ending in a slash. Note that is currently not recommended unless you need it for special reasons since it breaks caching #937 [stian@grytoyr.net]
-
-* Added expire_matched_fragments(regular_expression) to clear out a lot of fragment caches at once #927 [Rick Olson]
-
-* Fixed the problems with : and ? in file names for fragment caches on Windows #927 [Rick Olson]
-
-* Added TextHelper#human_size for formatting file sizes, like human_size(1234567) => 1.2 MB #943 [thomas@fesch.at]
-
-* Fixed link_to :confirm #936 [Nicholas Seckar]
-
-* Improved error reporting especially around never shallowing exceptions. Debugging helpers should be much easier now #980 [Nicholas Seckar]
-
-* Fixed Toggle.display in prototype.js #902 [Lucas Carlson]
-
-
-*1.6.0* (22th March, 2005)
-
-* Added a JavascriptHelper and accompanying prototype.js library that opens the world of Ajax to Action Pack with a large array of options for dynamically interacting with an application without reloading the page #884 [Sam Stephenson/David]
-
-* Added pagination support through both a controller and helper add-on #817 [Sam Stephenson]
-
-* Fixed routing and helpers to make Rails work on non-vhost setups #826 [Nicholas Seckar/Tobias Lütke]
-
-* Added a much improved Flash module that allows for finer-grained control on expiration and allows you to flash the current action #839 [Caio Chassot]. Example of flash.now:
-
- class SomethingController < ApplicationController
- def save
- ...
- if @something.save
- # will redirect, use flash
- flash[:message] = 'Save successful'
- redirect_to :action => 'list'
- else
- # no redirect, message is for current action, use flash.now
- flash.now[:message] = 'Save failed, review'
- render_action 'edit'
- end
- end
- end
-
-* Added to_param call for parameters when composing an url using url_for from something else than strings #812 [Sam Stephenson]. Example:
-
- class Page
-   def initialize(number)
-     @number = number
-   end
-   # ...
-   def to_param
-     @number.to_s
-   end
- end
-
- You can now use instances of Page with url_for:
-
- class BarController < ApplicationController
-   def baz
-     page = Page.new(4)
-     url = url_for :page => page # => "http://foo/bar/baz?page=4"
-   end
- end
-
-* Fixed form helpers to query Model#id_before_type_cast instead of Model#id as a temporary workaround for Ruby 1.8.2 warnings #818 [DeLynn B]
-
-* Fixed TextHelper#markdown to use blank? instead of empty? so it can deal with nil strings passed #814 [Johan Sörensen]
-
-* Added TextHelper#simple_format as a non-dependency text presentation helper #814 [Johan Sörensen]
-
-* Added that the html options disabled, readonly, and multiple can all be treated as booleans. So specifying <tt>disabled => :true</tt> will give <tt>disabled="disabled"</tt>. #809 [mindel]
-
-* Added path collection syntax for Routes that will gobble up the rest of the url and pass it on to the controller #830 [rayners]. Example:
-
- map.connect 'categories/*path_info', :controller => 'categories', :action => 'show'
-
- A request for /categories/top-level-cat, would give @params[:path_info] with "top-level-cat".
- A request for /categories/top-level-cat/level-1-cat, would give @params[:path_info] with "top-level-cat/level-1-cat" and so forth.
-
- The @params[:path_info] return is really an array, but where to_s has been overwritten to do join("/").
-
-* Fixed options_for_select on selected line issue #624 [Florian Weber]
-
-* Added CaptureHelper with CaptureHelper#capture and CaptureHelper#content_for. See documentation in helper #837 [Tobias Lütke]
-
-* Fixed :anchor use in url_for #821 [Nicholas Seckar]
-
-* Removed the reliance on PATH_INFO as it was causing problems for caching and inhibited the new non-vhost support #822 [Nicholas Seckar]
-
-* Added assigns shortcut for @response.template.assigns to controller test cases [Jeremy Kemper]. Example:
-
- Before:
-
- def test_list
- assert_equal 5, @response.template.assigns['recipes'].size
- assert_equal 8, @response.template.assigns['categories'].size
- end
-
- After:
-
- def test_list
- assert_equal 5, assigns(:recipes).size
- assert_equal 8, assigns(:categories).size
- end
-
-* Added TagHelper#image_tag and deprecated UrlHelper#link_image_to (recommended approach is to combine image_tag and link_to instead)
-
-* Fixed textilize to be resilient to getting nil parsed (by using Object#blank? instead of String#empty?)
-
-* Fixed that the :multipart option in FormTagHelper#form_tag would be ignored [Yonatan Feldman]
-
-
-*1.5.1* (7th March, 2005)
-
-* Fixed that the routes.rb file wouldn't be found on symlinked setups due to File.expand_path #793 [piotr@t-p-l.com]
-
-* Changed ActiveRecordStore to use Marshal instead of YAML as the latter proved troublesome in persisting circular dependencies. Updating existing applications MUST clear their existing session table from data to start using this updated store #739 [Jamis Buck]
-
-* Added shortcut :id assignment to render_component and friends (before you had to go through :params) #784 [Lucas Carlson]
-
-* Fixed that map.connect should convert arguments to strings #780 [Nicholas Seckar]
-
-* Added UrlHelper#link_to_if/link_to_unless to enable other conditions that just link_to_unless_current #757 [mindel]
-
-* Fixed that single quote was not escaped in a UrlHelper#link_to javascript confirm #549 [Scott Barron]
-
-* Removed the default border on link_image_to (it broke xhtml strict) -- can be specified with :border => 0 #517 [?/caleb]
-
-* Fixed that form helpers would treat string and symbol keys differently in html_options (and possibly create duplicate entries) #112 [Jeremy Kemper]
-
-* Fixed that broken pipe errors (clients disconnecting in mid-request) could bring down a fcgi process
-
-* Added the original exception message to session recall errors (so you can see which class wasnt required)
-
-* Fixed that RAILS_ROOT might not be defined when AP was loaded, so do a late initialization of the ROUTE_FILE #761 [Scott Barron]
-
-* Fix request.path_info and clear up LoadingModule behavior #754 [Nicholas Seckar]
-
-* Fixed caching to be aware of extensions (so you can cache files like api.wsdl or logo.png) #734 [Nicholas Seckar]
-
-* Fixed that Routes would raise NameErrors if a controller component contains characters that are not valid constant names #733 [Nicholas Seckar]
-
-* Added PATH_INFO access from the request that allows urls like the following to be interpreted by rails: http://www.example.com/dispatcher.cgi/controller/action -- that makes it possible to use rails as a CGI under lighttpd and would also allow (for example) Rublog to be ported to rails without breaking existing links to Rublog-powered blogs. #728 [Jamis Buck]
-
-* Fixed that caching the root would result in .html not index.html #731, #734 [alisdair/Nicholas Seckar]
-
-
-*1.5.0* (24th February, 2005)
-
-* Added Routing as a replacement for mod_rewrite pretty urls [Nicholas Seckar]. Read more in ActionController::Base.url_for and on http://manuals.rubyonrails.com/read/book/9
-
-* Added components that allows you to call other actions for their rendered response while execution another action. You can either delegate the entire response rendering or you can mix a partial response in with your other content. Read more on http://manuals.rubyonrails.com/read/book/14
-
-* Fixed that proxy IPs do not follow all RFC1918 nets #251 [caleb@aei-tech.com]
-
-* Added Base#render_to_string to parse a template and get the result back as a string #479
-
-* Fixed that send_file/data can work even if render* has been called before in action processing to render the content of a file to be send for example #601
-
-* Added FormOptionsHelper#time_zone_select and FormOptionsHelper#time_zone_options_for_select to work with the new value object TimeZone in Active Support #688 [Jamis Buck]
-
-* Added FormHelper#file_field and FormTagHelper#file_field_tag for creating file upload fields
-
-* Added :order option for date_select that allows control over the order in which the date dropdowns is used and which of them should be used #619 [Tim Bates]. Examples:
-
- date_select("post", "written_on", :order => [:day, :month, :year])
- date_select("user", "birthday", :order => [:month, :day])
-
-* Added ActionView::Base.register_template_handler for easy integration of an alternative template language to ERb and Builder. See test/controller/custom_handler_test.rb for a usage example #656 [Jamis Buck]
-
-* Added AssetTagHelper that provides methods for linking a HTML page together with other assets, such as javascripts, stylesheets, and feeds.
-
-* Added FormTagHelper that provides a number of methods for creating form tags that doesn't rely on conventions with an object assigned to the template like FormHelper does. With the FormTagHelper, you provide the names and values yourself.
-
-* Added Afghanistan, Iran, and Iraq to the countries list used by FormOptions#country_select and FormOptions#country_options_for_select
-
-* Renamed link_to_image to link_image_to (since thats what it actually does) -- kept alias for the old method name
-
-* Fixed textilize for RedCloth3 to keep doing hardbreaks
-
-* Fixed that assert_template_xpath_matches did not indicate when a path was not found #658 [Eric Hodel]
-
-* Added TextHelper#auto_link to turn email addresses and urls into ahrefs
-
-* Fixed that on validation errors, scaffold couldn't find template #654 [mindel]
-
-* Added Base#hide_action(*names) to hide public methods from a controller that would otherwise have been callable through the URL. For the majority of cases, its preferred just to make the methods you don't want to expose protected or private (so they'll automatically be hidden) -- but if you must have a public method, this is a way to make it uncallable. Base#hidden_actions retrieve the list of all hidden actions for the controller #644 [Nicholas Seckar]
-
-* Fixed that a bunch of methods from ActionController::Base was accessible as actions (callable through a URL) when they shouldn't have been #644 [Nicholas Seckar]
-
-* Added UrlHelper#current_page?(options) method to check if the url_for options passed corresponds to the current page
-
-* Fixed https handling on other ports than 443 [Alan Gano]
-
-* Added follow_redirect method for functional tests that'll get-request the redirect that was made. Example:
-
- def test_create_post
- post :create, "post" => { "title" => "Exciting!" }
- assert_redirected_to :action => "show"
-
- follow_redirect
- assert_rendered_file "post/show"
- end
-
- Limitation: Only works for redirects to other actions within the same controller.
-
-* Fixed double requiring of models with the same name as the controller
-
-* Fixed that query params could be forced to nil on a POST due to the raw post fix #562 [moriq@moriq.com]
-
-* Fixed that cookies shouldn't be frozen in TestRequest #571 [Eric Hodel]
-
-
-*1.4.0* (January 25th, 2005)
-
-* Fixed problems with ActiveRecordStore under the development environment in Rails
-
-* Fixed the ordering of attributes in the xml-decleration of Builder #540 [woeye]
-
-* Added @request.raw_post as a convenience access to @request.env['RAW_POST_DATA'] #534 [Tobias Lütke]
-
-* Added support for automatic id-based indexing for lists of items #532 [dblack]. Example:
-
- <% @students.each do |@student| %>
- <%= text_field "student[]", "first_name", :size => "20" %>
- <%= text_field "student[]", "last_name" %>
- <%= text_field "student[]", "grade", :size => "5" %>
- <% end %>
-
- ...would produce, for say David Black with id 123 and a grace of C+:
-
- <input id="student_123_first_name" name="student[123][first_name]" size="20" size="30" type="text" value="David" />
- <input id="student_123_last_name" name="student[123][last_name]" size="30" type="text" value="Black" />
- <input id="student_123_grade" name="student[123][grade]" size="5" type="text" value="C+" />
-
-* Added :application_prefix to url_for and friends that makes it easier to setup Rails in non-vhost environments #516 [Jamis Buck]
-
-* Added :encode option to mail_to that'll allow you to masquarede the email address behind javascript or hex encoding #494 [Lucas Carlson]
-
-* Fixed that the content-header was being set to application/octet_stream instead of application/octet-stream on send_date/file [Alexey]
-
-* Removed the need for passing the binding when using CacheHelper#cache
-
-* Added TestResponse#binary_content that'll return as a string the data sent through send_data/send_file for testing #500 [Alexey]
-
-* Added @request.env['RAW_POST_DATA'] for people who need access to the data before Ruby's CGI has parsed it #505 [Jeremy Kemper]
-
-* Fixed that a default fragment store wan't being set to MemoryStore as intended.
-
-* Fixed that all redirect and render calls now return true, so you can use the pattern of "do and return". Example:
-
- def show
- redirect_to(:action => "login") and return unless @person.authenticated?
- render_text "I won't happen unless the person is authenticated"
- end
-
-* Added that renders and redirects called in before_filters will have the same effect as returning false: stopping the chain. Example:
-
- class WeblogController
- before_filter { |c| c.send(:redirect_to_url("http://www.farfaraway.com")}) }
-
- def hello
- render_text "I will never be called"
- end
- end
-
-
-* Added that only one render or redirect can happen per action. The first call wins and subsequent calls are ignored. Example:
-
- def do_something
- redirect_to :action => "elsewhere"
- render_action "overthere"
- end
-
- Only the redirect happens. The rendering call is simply ignored.
-
-
-*1.3.1* (January 18th, 2005)
-
-* Fixed a bug where cookies wouldn't be set if a symbol was used instead of a string as the key
-
-* Added assert_cookie_equal to assert the contents of a named cookie
-
-* Fixed bug in page caching that prevented it from working at all
-
-
-*1.3.0* (January 17th, 2005)
-
-* Added an extensive caching module that offers three levels of granularity (page, action, fragment) and a variety of stores.
- Read more in ActionController::Caching.
-
-* Added the option of passing a block to ActiveRecordHelper#form in order to add more to the auto-generated form #469 [dom@sisna.com]
-
- form("entry", :action => "sign") do |form|
- form << content_tag("b", "Department")
- form << collection_select("department", "id", @departments, "id", "name")
- end
-
-* Added arrays as a value option for params in url_for and friends #467 [Eric Anderson]. Example:
-
- url_for(:controller => 'user', :action => 'delete', :params => { 'username' => %( paul john steve ) } )
- # => /user/delete?username[]=paul&username[]=john&username[]=steve
-
-* Fixed that controller tests can now assert on the use of cookies #466 [Alexey]
-
-* Fixed that send_file would "remember" all the files sent by adding to the headers again and again #458 [Jeremy Kemper]
-
-* Fixed url rewriter confusion when the controller or action name was a substring of the controller_prefix or action_prefix
-
-* Added conditional layouts like <tt>layout "weblog_standard", :except => :rss</tt> #452 [Marcel Molina Jr.]
-
-* Fixed that MemCacheStore wasn't included by default and added default MemCache object pointing to localhost #447 [Lucas Carlson]
-
-* Added fourth argument to render_collection_of_partials that allows you to specify local_assigns -- just like render_partial #432 [Ryan Davis]
-
-* Fixed that host would choke when cgi.host returned nil #432 [Tobias Lütke]
-
-* Added that form helpers now take an index option #448 [Tim Bates]
-
- Example:
- text_field "person", "name", "index" => 3
-
- Becomes:
- <input type="text" name="person[3][name]" id="person_3_name" value="<%= @person.name %>" />
-
-* Fixed three issues with retrying breakpoints #417 [Florian Gross]
-
- 1. Don't screw up pages that use multiple values for the same parameter (?foo=bar&foo=qux was converted to ?foo=barqux)
- 2. Don't screw up all forms when you click the "Retry with Breakpoint" link multiple times instead of reloading
- (This caused the parameters to be added multiple times for GET forms leading to trouble.)
- 3. Don't add ?BP-RETRY=1 multiple times
-
-* Added that all renders and redirects now return false, so they can be used as the last line in before_filters to stop execution.
-
- Before:
- def authenticate
- unless @session[:authenticated]
- redirect_to :controller => "account", :action => "login"
- return false
- end
- end
-
- After:
- def authenticate
- redirect_to(:controller => "account", :action => "login") unless @session[:authenticated]
- end
-
-* Added conditional filters #431 [Marcel Molina Jr.]. Example:
-
- class JournalController < ActionController::Base
- # only require authentication if the current action is edit or delete
- before_filter :authorize, :only_on => [ :edit, :delete ]
-
- private
- def authorize
- # redirect to login unless authenticated
- end
- end
-
-* Added Base#render_nothing as a cleaner way of doing render_text "" when you're not interested in returning anything but an empty response.
-
-* Added the possibility of passing nil to UrlHelper#link_to to use the link itself as the name
-
-
-*1.2.0* (January 4th, 2005)
-
-* Added MemCacheStore for storing session data in Danga's MemCache system [Bob Cottrell]
- Depends on: MemCached server (http://www.danga.com/memcached/), MemCache client (http://raa.ruby-lang.org/project/memcache/)
-
-* Added thread-safety to the DRbStore #66, #389 [Ben Stiglitz]
-
-* Added DateHelper#select_time and DateHelper#select_second #373 [Scott Baron]
-
-* Added :host and :protocol options to url_for and friends to redirect to another host and protocol than the current.
-
-* Added class declaration for the MissingFile exception #388 [Kent Sibilev]
-
-* Added "short hypertext note with a hyperlink to the new URI(s)" to redirects to fulfill compliance with RFC 2616 (HTTP/1.1) section 10.3.3 #397 [Tim Bates]
-
-* Added second boolean parameter to Base.redirect_to_url and Response#redirect to control whether the redirect is permanent or not (301 vs 302) #375 [Hodel]
-
-* Fixed redirects when the controller and action is named the same. Still haven't fixed same controller, module, and action, though #201 [Josh Peek]
-
-* Fixed problems with running multiple functional tests in Rails under 1.8.2 by including hack for test/unit weirdness
-
-* Fixed that @request.remote_ip didn't work in the test environment #369 [Bruno Mattarollo]
-
-
-*1.1.0*
-
-* Added search through session to clear out association caches at the end of each request. This makes it possible to place Active Record objects
- in the session without worrying about stale data in the associations (the main object is still subject to caching, naturally) #347 [Tobias Lütke]
-
-* Added more informative exception when using helper :some_helper and the helper requires another file that fails, you'll get an
- error message tells you what file actually failed to load, rather than falling back on assuming it was the helper file itself #346 [dblack]
-
-* Added use of *_before_type_cast for all input and text fields. This is helpful for getting "100,000" back on a integer-based
- validation where the value would normally be "100".
-
-* Added Request#port_string to get something like ":8080" back on 8080 and "" on 80 (or 443 with https).
-
-* Added Request#domain (returns string) and Request#subdomains (returns array).
-
-* Added POST support for the breakpoint retries, so form processing that raises an exception can be retried with the original request [Florian Gross]
-
-* Fixed regression with Base#reset_session that wouldn't use the DEFAULT_SESSION_OPTIONS [adam@the-kramers.net]
-
-* Fixed error rendering of rxml documents to not just swallow the exception and return 0 (still not guessing the right line, but hey)
-
-* Fixed that textilize and markdown would instantiate their engines even on empty strings. This also fixes #333 [Ulysses]
-
-* Fixed UrlHelper#link_to_unless so it doesn't care if the id is a string or fixnum [Ryan Davis]
-
-
-*1.0.1*
-
-* Fixed a bug that would cause an ApplicationController to require itself three times and hence cause filters to be run three times [evl]
-
-
-*1.0*
-
-* Added that controllers will now attempt to require a model dependency with their name and in a singular attempt for their name.
- So both PostController and PostsController will automatically have the post.rb model required. If no model is found, no error is raised,
- as it is then expected that no match is available and the programmer will have included his own models.
-
-* Fixed DateHelper#date_select so that you can pass include_blank as an option even if you don't use start_year and end_year #59 [what-a-day]
-
-* Added that controllers will now search for a layout in $template_root/layouts/$controller_name.r(html|xml), so PostsController will look
- for layouts/posts.rhtml or layouts/posts.rxml and automatically configure this layout if found #307 [Marcel Molina Jr.]
-
-* Added FormHelper#radio_button to work with radio buttons like its already possible with check boxes [Michael Koziarski]
-
-* Added TemplateError#backtrace that makes it much easier to debug template errors from unit and functional tests
-
-* Added display of error messages with scaffolded form pages
-
-* Added option to ERB templates to swallow newlines by using <% if something -%> instead of just <% if something %>. Example:
-
- class SomeController < ApplicationController
- <% if options[:scaffold] %>
- scaffold :<%= singular_name %>
- <% end %>
- helper :post
-
- ...produces this on post as singular_name:
-
- class SomeController < ApplicationController
-
- scaffold :post
-
- helper :post
-
- ...where as:
-
- class SomeController < ApplicationController
- <% if options[:scaffold] -%>
- scaffold :<%= singular_name %>
- <% end -%>
- helper :post
-
- ...produces:
-
- class SomeController < ApplicationController
- scaffold :post
- helper :post
-
- [This undocumented gem for ERb was uncovered by bitsweat]
-
-* Fixed CgiRequest so that it'll now accept session options with Symbols as keys (as the documentation points out) [Suggested by Andreas]
-
-* Added that render_partial will always by default include a counter with value 1 unless there is a counter passed in via the
- local_assigns hash that overrides it. As a result, render_collection_of_partials can still be written in terms of render_partial
- and partials that make use of a counter can be called without problems from both render_collection_of_partials as well as
- render_partial #295 [Marcel Molina Jr.]
-
-* Fixed CgiRequest#out to fall back to #write if $stdout doesn't have #syswrite [Jeremy Kemper]
-
-* Fixed all helpers so that they use XHTML compliant double quotes for values instead of single quotes [htonl/Jeremy Kemper]
-
-* Added link_to_image(src, options = {}, html_options = {}). Documentation:
-
- Creates a link tag to the image residing at the +src+ using an URL created by the set of +options+. See the valid options in
- link:classes/ActionController/Base.html#M000021. It's also possible to pass a string instead of an options hash to
- get a link tag that just points without consideration. The <tt>html_options</tt> works jointly for the image and ahref tag by
- letting the following special values enter the options on the image and the rest goes to the ahref:
-
- ::alt: If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension)
- ::size: Supplied as "XxY", so "30x45" becomes width="30" and height="45"
- ::align: Sets the alignment, no special features
-
- The +src+ can be supplied as a...
- * full path, like "/my_images/image.gif"
- * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
- * file name without extension, like "logo", that gets expanded to "/images/logo.png"
-
-* Fixed to_input_field_tag so it no longer explicitly uses InstanceTag.value if value was specified in the options hash [evl]
-
-* Added the possibility of having validate be protected for assert_(in)valid_column #263 [Tobias Lütke]
-
-* Added that ActiveRecordHelper#form now calls url_for on the :action option.
-
-* Added all the HTTP methods as alternatives to the generic "process" for functional testing #276 [Tobias Lütke]. Examples:
-
- # Calls Controller#miletone with a GET request
- process :milestone
-
- # Calls Controller#miletone with a POST request that has parameters
- post :milestone, { "name" => "David" }
-
- # Calls Controller#milestone with a HEAD request that has both parameters and session data
- head :milestone, { "id" => 1 }, { "user_id" => 23 }
-
- This is especially useful for testing idiomatic REST web services.
-
-* Added proper handling of HEAD requests, so that content isn't returned (Request#head? added as well) #277 [Eric Hodel]
-
-* Added indifference to whether @headers["Content-Type"], @headers["Content-type"], or @headers["content-type"] is used.
-
-* Added TestSession#session_id that returns an empty string to make it easier to functional test applications that doesn't use
- cookie-based sessions #275 [jcf]
-
-* Fixed that cached template loading would still check the file system to see if the file existed #258 [Andreas Schwarz]
-
-* Added options to tailor header tag, div id, and div class on ActiveRecordHelper#error_messages_for [Josh Peek]
-
-* Added graceful handling of non-alphanumeric names and misplaced brackets in input parameters [Jeremy Kemper]
-
-* Added a new container for cookies that makes them more intuative to use. The old methods of cookie and @cookies have been deprecated.
-
- Examples for writing:
-
- cookies["user_name"] = "david" # => Will set a simple session cookie
- cookies["login"] = { "value" => "XJ-122", "expires" => Time.now + 360} # => Will set a cookie that expires in 1 hour
-
- Examples for reading:
-
- cookies["user_name"] # => "david"
- cookies.size # => 2
-
- Read more in ActionController::Cookies
-
- NOTE: If you were using the old accessor (cookies instead of @cookies), this could potentially break your code -- if you expect a full cookie object!
-
-* Added the opportunity to defined method_missing on a controller which will handle all requests for actions not otherwise defined #223 [timb]
-
-* Fixed AbstractRequest#remote_ip for users going through proxies - Patch #228 [Eric Hodel]
-
-* Added Request#ssl? which is shorthand for @request.protocol == "https://"
-
-* Added the choice to call form_tag with no arguments (resulting in a form posting to current action) [Jeremy Kemper]
-
-* Upgraded to Builder 1.2.1
-
-* Added :module as an alias for :controller_prefix to url_for and friends, so you can do redirect_to(:module => "shop", :controller => "purchases")
- and go to /shop/purchases/
-
-* Added support for controllers in modules through @params["module"].
-
-* Added reloading for dependencies under cached environments like FastCGI and mod_ruby. This makes it possible to use those environments for development.
- This is turned on by default, but can be turned off with ActionController::Base.reload_dependencies = false in production environments.
-
- NOTE: This will only have an effect if you use the new model, service, and observer class methods to mark dependencies. All libraries loaded through
- require will be "forever" cached. You can, however, use ActionController::Base.load_or_require("library") to get this behavior outside of the new
- dependency style.
-
-* Added that controllers will automatically require their own helper if possible. So instead of doing:
-
- class MsgController < ApplicationController
- helper :msg
- end
-
- ...you can just do:
-
- class MsgController < ApplicationController
- end
-
-* Added dependencies_on(layer) to query the dependencies of a controller. Examples:
-
- MsgController.dependencies_on(:model) # => [ :post, :comment, :attachment ]
- MsgController.dependencies_on(:service) # => [ :notification_service ]
- MsgController.dependencies_on(:observer) # => [ :comment_observer ]
-
-* Added a new dependency model with the class methods model, service, and observer. Example:
-
- class MsgController < ApplicationController
- model :post, :comment, :attachment
- service :notification_service
- observer :comment_observer
- end
-
- These new "keywords" remove the need for explicitly calling 'require' in most cases. The observer method even instantiates the
- observer as well as requiring it.
-
-* Fixed that link_to would escape & in the url again after url_for already had done so
-
-
-*0.9.5* (28)
-
-* Added helper_method to designate that a given private or protected method you should available as a helper in the view. [Jeremy Kemper]
-
-* Fixed assert_rendered_file so it actually verifies if that was the rendered file [htonl]
-
-* Added the option for sharing partial spacer templates just like partials themselves [radsaq]
-
-* Fixed that Russia was named twice in country_select [alexey]
-
-* Fixed request_origin to use remote_ip instead of remote_addr [Jeremy Kemper]
-
-* Fixed link_to breakage when nil was passed for html_options [alexey]
-
-* Fixed redirect_to on a virtual server setup with apache with a port other than the default where it would forget the port number [seanohalpin]
-
-* Fixed that auto-loading webrick on Windows would cause file uploads to fail [Jeremy Kemper]
-
-* Fixed issues with sending files on WEBrick by setting the proper binmode [Jeremy Kemper]
-
-* Added send_data as an alternative to send_file when the stream is not read off the filesystem but from a database or generated live [Jeremy Kemper]
-
-* Added a new way to include helpers that doesn't require the include hack and can go without the explicit require. [Jeremy Kemper]
-
- Before:
-
- module WeblogHelper
- def self.included(controller) #:nodoc:
- controller.ancestors.include?(ActionController::Base) ? controller.add_template_helper(self) : super
- end
- end
-
- require 'weblog_helper'
- class WeblogController < ActionController::Base
- include WeblogHelper
- end
-
- After:
-
- module WeblogHelper
- end
-
- class WeblogController < ActionController::Base
- helper :weblog
- end
-
-* Added a default content-type of "text/xml" to .rxml renders [Ryan Platte]
-
-* Fixed that when /controller/index was requested by the browser, url_for would generates wrong URLs [Ryan Platte]
-
-* Fixed a bug that would share cookies between users when using FastCGI and mod_ruby [The Robot Co-op]
-
-* Added an optional third hash parameter to the process method in functional tests that takes the session data to be used [alexey]
-
-* Added UrlHelper#mail_to to make it easier to create mailto: style ahrefs
-
-* Added better error messages for layouts declared with the .rhtml extension (which they shouldn't) [geech]
-
-* Added another case to DateHelper#distance_in_minutes to return "less than a minute" instead of "0 minutes" and "1 minute" instead of "1 minutes"
-
-* Added a hidden field to checkboxes generated with FormHelper#check_box that will make sure that the unchecked value (usually 0)
- is sent even if the checkbox is not checked. This relieves the controller from doing custom checking if the checkbox wasn't
- checked. BEWARE: This might conflict with your run-on-the-mill work-around code. [Tobias Lütke]
-
-* Fixed error_message_on to just use the first if more than one error had been added [Marcel Molina Jr.]
-
-* Fixed that URL rewriting with /controller/ was working but /controller was not and that you couldn't use :id on index [geech]
-
-* Fixed a bug with link_to where the :confirm option wouldn't be picked up if the link was a straight url instead of an option hash
-
-* Changed scaffolding of forms to use <label> tags instead of <b> to please W3C [evl]
-
-* Added DateHelper#distance_of_time_in_words_to_now(from_time) that works like distance_of_time_in_words,
- but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
-
-* Added assert_flash_equal(expected, key, message), assert_session_equal(expected, key, message),
- assert_assigned_equal(expected, key, message) to test the contents of flash, session, and template assigns.
-
-* Improved the failure report on assert_success when the action triggered a redirection [alexey].
-
-* Added "markdown" to accompany "textilize" as a TextHelper method for converting text to HTML using the Markdown syntax.
- BlueCloth must be installed in order for this method to become available.
-
-* Made sure that an active session exists before we attempt to delete it [Samuel]
-
-* Changed link_to with Javascript confirmation to use onclick instead of onClick for XHTML validity [Scott Barron]
-
-
-*0.9.0 (43)*
-
-* Added support for Builder-based templates for files with the .rxml extension. These new templates are an alternative to ERb that
- are especially useful for generating XML content, such as this RSS example from Basecamp:
-
- xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
- xml.channel do
- xml.title(@feed_title)
- xml.link(@url)
- xml.description "Basecamp: Recent items"
- xml.language "en-us"
- xml.ttl "40"
-
- for item in @recent_items
- xml.item do
- xml.title(item_title(item))
- xml.description(item_description(item)) if item_description(item)
- xml.pubDate(item_pubDate(item))
- xml.guid(@person.firm.account.url + @recent_items.url(item))
- xml.link(@person.firm.account.url + @recent_items.url(item))
-
- xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
- end
- end
- end
- end
-
- ...which will generate something like:
-
- <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
- <channel>
- <title>Web Site Redesign</title>
- <link>http://www.basecamphq.com/clients/travelcenter/1/</link>
- <description>Basecamp: Recent items</description>
- <language>en-us</language>
- <ttl>40</ttl>
- <item>
- <title>Post: don't you know</title>
- <description>&amp;lt;p&amp;gt;deeper and down&amp;lt;/p&amp;gt;</description>
- <pubDate>Fri, 20 Aug 2004 21:13:50 CEST</pubDate>
- <guid>http://www.basecamphq.com/clients/travelcenter/1/msg/assets/96976/comments</guid>
- <link>http://www.basecamphq.com/clients/travelcenter/1/msg/assets/96976/comments</link>
- <dc:creator>David H. Heinemeier</dc:creator>
- </item>
- <item>
- <title>Milestone completed: Design Comp 2</title>
- <pubDate>Mon, 9 Aug 2004 14:42:06 CEST</pubDate>
- <guid>http://www.basecamphq.com/clients/travelcenter/1/milestones/#49</guid>
- <link>http://www.basecamphq.com/clients/travelcenter/1/milestones/#49</link>
- </item>
- </channel>
- </rss>
-
- The "xml" local variable is automatically available in .rxml templates. You construct the template by calling a method with the name
- of the tag you want. Options for the tag can be specified as a hash parameter to that method.
-
- Builder-based templates can be mixed and matched with the regular ERb ones. The only thing that differentiates them is the extension.
- No new methods have been added to the public interface to handle them.
-
- Action Pack ships with a version of Builder, but it will use the RubyGems version if you have one installed.
-
- Read more about Builder on: http://onestepback.org/index.cgi/Tech/Ruby/StayingSimple.rdoc
-
- [Builder is created by Jim Weirich]
-
-* Added much improved support for functional testing [what-a-day].
-
- # Old style
- def test_failing_authenticate
- @request.request_uri = "/login/authenticate"
- @request.action = "authenticate"
- @request.request_parameters["user_name"] = "nop"
- @request.request_parameters["password"] = ""
-
- response = LoginController.process_test(@request)
-
- assert_equal "The username and/or password you entered is invalid.", response.session["flash"]["alert"]
- assert_equal "http://37signals.basecamp.com/login/", response.headers["location"]
- end
-
- # New style
- def test_failing_authenticate
- process :authenticate, "user_name" => "nop", "password" => ""
- assert_flash_has 'alert'
- assert_redirected_to :action => "index"
- end
-
- See a full example on http://codepaste.org/view/paste/334
-
-* Increased performance by up to 100% with a revised cookie class that fixes the performance problems with the
- default one that ships with 1.8.1 and below. It replaces the inheritance on SimpleDelegator with DelegateClass(Array)
- following the suggestion from Matz on:
- http://groups.google.com/groups?th=e3a4e68ba042f842&seekm=c3sioe%241qvm%241%40news.cybercity.dk#link14
-
-* Added caching for compiled ERb templates. On Basecamp, it gave between 8.5% and 71% increase in performance [Andreas Schwarz].
-
-* Added implicit counter variable to render_collection_of_partials [Marcel Molina Jr.]. From the docs:
-
- <%= render_collection_of_partials "ad", @advertisements %>
-
- This will render "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display. An iteration counter
- will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the
- example above, the template would be fed +ad_counter+.
-
-* Fixed problems with two sessions being maintained on reset_session that would particularly screw up ActiveRecordStore.
-
-* Fixed reset_session to start an entirely new session instead of merely deleting the old. So you can now safely access @session
- after calling reset_ression and expect it to work.
-
-* Added @request.get?, @request.post?, @request.put?, @request.delete? as convenience query methods for @request.method [geech]
-
-* Added @request.method that'll return a symbol representing the HTTP method, such as :get, :post, :put, :delete [geech]
-
-* Changed @request.remote_ip and @request.host to work properly even when a proxy is in front of the application [geech]
-
-* Added JavaScript confirm feature to link_to. Documentation:
-
- The html_options have a special feature for creating javascript confirm alerts where if you pass
- :confirm => 'Are you sure?', the link will be guarded with a JS popup asking that question.
- If the user accepts, the link is processed, otherwise not.
-
-* Added link_to_unless_current as a UrlHelper method [Sam Stephenson]. Documentation:
-
- Creates a link tag of the given +name+ using an URL created by the set of +options+, unless the current
- controller, action, and id are the same as the link's, in which case only the name is returned (or the
- given block is yielded, if one exists). This is useful for creating link bars where you don't want to link
- to the page currently being viewed.
-
-* Fixed that UrlRewriter (the driver for url_for, link_to, etc) would blow up when the anchor was an integer [alexey]
-
-* Added that layouts defined with no directory defaults to layouts. So layout "weblog/standard" will use
- weblog/standard (as always), but layout "standard" will use layouts/standard.
-
-* Fixed that partials (or any template starting with an underscore) was publically viewable [Marten]
-
-* Added HTML escaping to text_area helper.
-
-* Added :overwrite_params to url_for and friends to keep the parameters as they were passed to the current action and only overwrite a subset.
- The regular :params will clear the slate so you need to manually add in existing parameters if you want to reuse them. [raphinou]
-
-* Fixed scaffolding problem with composite named objects [Moo Jester]
-
-* Added the possibility for shared partials. Example:
-
- <%= render_partial "advertisement/ad", ad %>
-
- This will render the partial "advertisement/_ad.rhtml" regardless of which controller this is being called from.
-
- [Jacob Fugal]
-
-* Fixed crash when encountering forms that have empty-named fields [James Prudente]
-
-* Added check_box form helper method now accepts true/false as well as 1/0 [what-a-day]
-
-* Fixed the lacking creation of all directories with install.rb [Dave Steinberg]
-
-* Fixed that date_select returns valid XHTML selected options [Andreas Schwarz]
-
-* Fixed referencing an action with the same name as a controller in url_for [what-a-day]
-
-* Fixed the destructive nature of Base#attributes= on the argument [Kevin Watt]
-
-* Changed ActionControllerError to decent from StandardError instead of Exception. It can now be caught by a generic rescue.
-
-* Added SessionRestoreError that is raised when a session being restored holds objects where there is no class available.
-
-* Added block as option for inline filters. So what used to be written as:
-
- before_filter Proc { |controller| return false if controller.params["stop_action"] }
-
- ...can now be as:
-
- before_filter { |controller| return false if controller.params["stop_action"] }
-
- [Jeremy Kemper]
-
-* Made the following methods public (was protected): url_for, controller_class_name, controller_name, action_name
- This makes it easier to write filters without cheating around the encapsulation with send.
-
-* ActionController::Base#reset_session now sticks even if you access @session afterwards [Kent Sibilev]
-
-* Improved the exception logging so the log file gets almost as much as in-browser debugging.
-
-* Changed base class setup from AbstractTemplate/ERbTemplate to ActionView::Base. This change should be harmless unless you were
- accessing Action View directly in which case you now need to reference the Base class.\
-
-* Added that render_collection_of_partials returns nil if the collection is empty. This makes showing a “no items” message easier.
- For example: <%= render_collection_of_partials("message", @messages) || "No messages found." %> [Sam Stephenson]
-
-* Added :month_before_year as an option to date_select to get the month select before the year. Especially useful for credit card forms.
-
-* Added :add_month_numbers to select_month to get options like "3 - March".
-
-* Removed Base.has_active_layout? as it couldn't answer the question without the instance. Use Base#active_layout instead.
-
-* Removed redundant call to update on ActionController::Base#close_session [Andreas Schwarz]
-
-* Fixed that DRb Store accidently started its own server (instead of just client) [Andreas]
-
-* Fixed strip_links so it now works across multiple lines [Chad Fowler]
-
-* Fixed the TemplateError exception to show the proper trace on to_s (useful for unit test debugging)
-
-* Implemented class inheritable attributes without eval [Caio Chassot]
-
-* Made TextHelper#concat accept binding as it would otherwise not work
-
-* The FormOptionsHelper will now call to_s on the keys and values used to generate options
-
-
-*0.8.5*
-
-* Introduced passing of locally scoped variables between templates:
-
- You can pass local variables to sub templates by using a hash of with the variable
- names as keys and the objects as values:
-
- <%= render "shared/header", { "headline" => "Welcome", "person" => person } %>
-
- These can now be accessed in shared/header with:
-
- Headline: <%= headline %>
- First name: <%= person.first_name %>
-
-* Introduced the concept of partials as a certain type of sub templates:
-
- There's also a convenience method for rendering sub templates within the current
- controller that depends on a single object (we call this kind of sub templates for
- partials). It relies on the fact that partials should follow the naming convention
- of being prefixed with an underscore -- as to separate them from regular templates
- that could be rendered on their own. In the template for Advertiser#buy, we could have:
-
- <% for ad in @advertisements %>
- <%= render_partial "ad", ad %>
- <% end %>
-
- This would render "advertiser/_ad.rhtml" and pass the local variable +ad+
- for the template to display.
-
- == Rendering a collection of partials
-
- The example of partial use describes a familar pattern where a template needs
- to iterate over a array and render a sub template for each of the elements.
- This pattern has been implemented as a single method that accepts an array and
- renders a partial by the same name of as the elements contained within. So the
- three-lined example in "Using partials" can be rewritten with a single line:
-
- <%= render_collection_of_partials "ad", @advertisements %>
-
- So this will render "advertiser/_ad.rhtml" and pass the local variable +ad+ for
- the template to display.
-
-* Improved send_file by allowing a wide range of options to be applied [Jeremy Kemper]:
-
- Sends the file by streaming it 4096 bytes at a time. This way the
- whole file doesn't need to be read into memory at once. This makes
- it feasible to send even large files.
-
- Be careful to sanitize the path parameter if it coming from a web
- page. send_file(@params['path'] allows a malicious user to
- download any file on your server.
-
- Options:
- * <tt>:filename</tt> - specifies the filename the browser will see.
- Defaults to File.basename(path).
- * <tt>:type</tt> - specifies an HTTP content type.
- Defaults to 'application/octet-stream'.
- * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
- Valid values are 'inline' and 'attachment' (default).
- * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream
- the file. Defaults to 4096.
-
- The default Content-Type and Content-Disposition headers are
- set to download arbitrary binary files in as many browsers as
- possible. IE versions 4, 5, 5.5, and 6 are all known to have
- a variety of quirks (especially when downloading over SSL).
-
- Simple download:
- send_file '/path/to.zip'
-
- Show a JPEG in browser:
- send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
-
- Read about the other Content-* HTTP headers if you'd like to
- provide the user with more information (such as Content-Description).
- http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
-
- Also be aware that the document may be cached by proxies and browsers.
- The Pragma and Cache-Control headers declare how the file may be cached
- by intermediaries. They default to require clients to validate with
- the server before releasing cached responses. See
- http://www.mnot.net/cache_docs/ for an overview of web caching and
- http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
- for the Cache-Control header spec.
-
-* Added pluralize method to the TextHelper that makes it easy to get strings like "1 message", "3 messages"
-
-* Added proper escaping for the rescues [Andreas Schwarz]
-
-* Added proper escaping for the option and collection tags [Andreas Schwarz]
-
-* Fixed NaN errors on benchmarking [Jim Weirich]
-
-* Fixed query string parsing for URLs that use the escaped versions of & or ; as part of a key or value
-
-* Fixed bug with custom Content-Type headers being in addition to rather than instead of the default header.
- (This bug didn't matter with neither CGI or mod_ruby, but FCGI exploded on it) [With help from Ara T. Howard]
-
-
-*0.8.0*
-
-* Added select, collection_select, and country_select to make it easier for Active Records to set attributes through
- drop-down lists of options. Example:
-
- <%= select "person", "gender", %w( Male Female ) %>
-
- ...would give the following:
-
- <select name="person[gender]" id="person_gender"><option>Male</option><option>Female</option></select>
-
-* Added an option for getting multiple values on a single form name into an array instead of having the last one overwrite.
- This is especially useful for groups of checkboxes, which can now be written as:
-
- <input type="checkbox" name="rights[]" value="CREATE" />
- <input type="checkbox" name="rights[]" value="UPDATE" />
- <input type="checkbox" name="rights[]" value="DELETE" />
-
- ...and retrieved in the controller action with:
-
- @params["rights"] # => [ "CREATE", "UPDATE", "DELETE" ]
-
- The old behavior (where the last one wins, "DELETE" in the example) is still available. Just don't add "[]" to the
- end of the name. [Scott Baron]
-
-* Added send_file which uses the new render_text block acceptance to make it feasible to send large files.
- The files is sent with a bunch of voodoo HTTP headers required to get arbitrary files to download as
- expected in as many browsers as possible (eg, IE hacks). Example:
-
- def play_movie
- send_file "/movies/that_movie.avi"
- end
-
- [Jeremy Kemper]
-
-* render_text now accepts a block for deferred rendering. Useful for streaming large files, displaying
- a “please wait” message during a complex search, etc. Streaming example:
-
- render_text do |response|
- File.open(path, 'rb') do |file|
- while buf = file.read(1024)
- print buf
- end
- end
- end
-
- [Jeremy Kemper]
-
-* Added a new Tag Helper that can generate generic tags programmatically insted of through HTML. Example:
-
- tag("br", "clear" => "all") => <br clear="all" />
-
- ...that's usually not terribly interesting (unless you have a lot of options already in a hash), but it
- gives way for more specific tags, like the new form tag:
-
- form_tag({ :controller => "weblog", :action => "update" }, { :multipart => "true", "style" => "width: 200px"}) =>
- <form action="/weblog/update" enctype="multipart/formdata" style="width: 200px">
-
- There's even a "pretty" version for people who don't like to open tags in code and close them in HTML:
-
- <%= start_form_tag :action => "update" %>
- # all the input fields
- <%= end_form_tag %>
-
- (end_form_tag just returns "</form>")
-
-* The selected parameter in options_for_select may now also an array of values to be selected when
- using a multiple select. Example:
-
- options_for_select([ "VISA", "Mastercard", "Discover" ], ["VISA", "Discover"]) =>
- <option selected>VISA</option>\n<option>Mastercard</option>\n<option selected>Discover</option>
-
- [Scott Baron]
-
-* Changed the URL rewriter so controller_prefix and action_prefix can be used in isolation. You can now do:
-
- url_for(:controller_prefix => "clients")
-
- ...or:
-
- url_for(:action_prefix => "category/messages")
-
- Neither would have worked in isolation before (:controller_prefix required a :controller and :action_prefix required an :action)
-
-* Started process of a cleaner separation between Action Controller and ERb-based Action Views by introducing an
- abstract base class for views. And Amita adapter could be fitted in more easily now.
-
-* The date helper methods date_select and datetime_select now also use the field error wrapping
- (div with class fieldWithErrors by default).
-
-* The date helper methods date_select and datetime_select can now discard selects
-
-* Added option on AbstractTemplate to specify a different field error wrapping. Example:
-
- ActionView::AbstractTemplate.field_error_proc = Proc.new do |html, instance|
- "<p>#{instance.method_name + instance.error_message}</p><div style='background-color: red'>#{html}</div>"
- end
-
- ...would give the following on a Post#title (text field) error:
-
- <p>Title can't be empty</p>
- <div style='background-color: red'>
- <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
- </div>
-
-* The UrlHelper methods url_for and link_to will now by default only return paths, not complete URIs.
- That should make it easier to fit a Rails application behind a proxy or load-balancer.
- You can overwrite this by passing :only_path => false as part of the options. [Suggested by U235]
-
-* Fixed bug with having your own layout for use with scaffolding [Kevin Radloff]
-
-* Fixed bug where redirect_to_path didn't append the port on non-standard ports [dhawkins]
-
-* Scaffolding plays nicely with single-table inheritance (LoadErrors are caught) [Jeremy Kemper]
-
-* Scaffolding plays nice with plural models like Category/categories [Jeremy Kemper]
-
-* Fixed missing suffix appending in scaffolding [Kevin Radloff]
-
-
-*0.7.9*
-
-* The "form" method now present boolean fields from PostgreSQL as drop-down menu. [Scott]
-
-* Scaffolding now automatically attempts to require the class that's being scaffolded.
-
-* Scaffolding will use the current active layout, instead of its own, if one has been specified. Example:
-
- class WeblogController < ActionController::Base
- layout "layouts/weblog"
- scaffold :post
- end
-
- [Suggested by Scott]
-
-* Changed url_for (and all the that drives, like redirect_to, link_to, link_for) so you can pass it a symbol instead of a hash.
- This symbol is a method reference which is then called to calculate the url. Example:
-
- class WeblogController < ActionController::Base
- def update
- # do some update
- redirect_to :dashboard_url
- end
-
- protected
- def dashboard_url
- if @project.active?
- url_for :controller => "project", :action => "dashboard"
- else
- url_for :controller => "account", :action => "dashboard"
- end
- end
- end
-
-* Added default_url_options to specialize behavior for all url_for (and friends) calls:
-
- Overwrite to implement a number of default options that all url_for-based methods will use.
- The default options should come in form of a hash, just like the one you would use for
- url_for directly. Example:
-
- def default_url_options(options)
- { :controller_prefix => @project.active? ? "projects/" : "accounts/" }
- end
-
- As you can infer from the example, this is mostly useful for situations where you want to
- centralize dynamic dissions about the urls as they stem from the business domain. Please note
- that any individual url_for call can always override the defaults set by this method.
-
-
-* Changed url_for so that an "id" passed in the :params is not treated special. You need to use the dedicated :id to get
- the special auto path-params treatment. Considering the url http://localhost:81/friends/list
-
- url_for(:action => "show", :params => { "id" => 5 })
- ...used to give http://localhost:81/friends/show/5
- ......now gives http://localhost:81/friends/show?id=5
-
- If you want the automated id behavior, do:
-
- url_for(:action => "show", :id => 5 )
- ....which gives http://localhost:81/friends/show/5
-
-
-* Fixed problem with anchor being inserted before path parameters with url_for (and friends)
-
-
-*0.7.8*
-
-* Fixed session bug where you couldn't store any objects that didn't exist in the standard library
- (such as Active Record objects).
-
-* Added reset_session method for Action Controller objects to clear out all objects in the session.
-
-* Fixed that exceptions raised during filters are now also caught by the default rescues
-
-* Added new around_filter for doing before and after filtering with a single object [Florian Weber]:
-
- class WeblogController < ActionController::Base
- around_filter BenchmarkingFilter.new
-
- # Before this action is performed, BenchmarkingFilter#before(controller) is executed
- def index
- end
- # After this action has been performed, BenchmarkingFilter#after(controller) is executed
- end
-
- class BenchmarkingFilter
- def initialize
- @runtime
- end
-
- def before
- start_timer
- end
-
- def after
- stop_timer
- report_result
- end
- end
-
-* Added the options for specifying a different name and id for the form helper methods than what is guessed [Florian Weber]:
-
- text_field "post", "title"
- ...just gives: <input id="post_title" name="post[title]" size="30" type="text" value="" />
-
- text_field "post", "title", "id" => "title_for_post", "name" => "first_post_title"
- ...can now give: <input id="title_for_post" name="first_post_title" size="30" type="text" value="" />
-
-* Added DebugHelper with a single "debug" method for doing pretty dumps of objects in the view
- (now used in the default rescues to better present the contents of session and template variables)
-
-* Added note to log about the templates rendered within layouts (before just the layout was shown)
-
-* Fixed redirects on https setups [Andreas]
-
-* Fixed scaffolding problem on the edit action when using :suffix => true [Scott]
-
-* Fixed scaffolding problem where implementing list.rhtml wouldn't work for the index action
-
-* URLs generated now uses &amp; instead of just & so pages using it can validate with W3C [Spotted by Andreas]
-
-
-*0.7.7*
-
-* Fixed bug in CGI extension that prevented multipart forms from working
-
-
-*0.7.6*
-
-* Included ERB::Util so all templates can easily escape HTML content with <%=h @person.content %>
-
-* All requests are now considered local by default, so everyone will be exposed to detailed debugging screens on errors.
- When the application is ready to go public, set ActionController::Base.consider_all_requests_local to false,
- and implement the protected method local_request? in the controller to determine when debugging screens should be shown.
-
-* Fixed three bugs with the url_for/redirect_to/link_to handling. Considering the url http://localhost:81/friends/show/1
-
- url_for(:action => "list")
- ...used to give http://localhost:81/friends/list/1
- ......now gives http://localhost:81/friends/list
-
- url_for(:controller => "friends", :action => "destroy", :id => 5)
- ...used to give http://localhost:81/friends/destroy
- ......now gives http://localhost:81/friends/destroy/5
-
- Considering the url http://localhost:81/teachers/show/t
-
- url_for(:action => "list", :id => 5)
- ...used to give http://localhost:81/5eachers/list/t
- ......now gives http://localhost:81/teachers/list/5
-
- [Reported by David Morton & Radsaq]
-
-* Logs exception to logfile in addition to showing them for local requests
-
-* Protects the eruby load behind a begin/rescue block. eRuby is not required to run ActionController.
-
-* Fixed install.rb to also install clean_logger and the templates
-
-* Added ActiveRecordStore as a session option. Read more in lib/action_controller/session/active_record_store.rb [Tim Bates]
-
-* Change license to MIT License (and included license file in package)
-
-* Application error page now returns status code 500 instead of 200
-
-* Fixed using Procs as layout handlers [Florian Weber]
-
-* Fixed bug with using redirects ports other than 80
-
-* Added index method that calls list on scaffolding
-
-
-*0.7.5*
-
-* First public release
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
new file mode 100644
index 0000000000..e625bbe7af
--- /dev/null
+++ b/actionpack/CHANGELOG.md
@@ -0,0 +1,6164 @@
+## Rails 4.0.0 (unreleased) ##
+
+* Add `divider` option to `grouped_options_for_select` to generate a separator
+ `optgroup` automatically, and deprecate `prompt` as third argument, in favor
+ of using an options hash. *Nicholas Greenfield*
+
+* Add `time_field` and `time_field_tag` helpers which render an `input[type="time"]` tag. *Alex Soulim*
+
+* Removed old text_helper apis for highlight, excerpt and word_wrap *Jeremy Walker*
+
+* Templates without a handler extension now raises a deprecation warning but still
+ defaults to ERb. In future releases, it will simply return the template contents. *Steve Klabnik*
+
+* Remove `:disable_with` in favor of `'data-disable-with'` option from `submit_tag`, `button_tag` and `button_to` helpers.
+
+ *Carlos Galdino + Rafael Mendonça França*
+
+* Remove `:mouseover` option from `image_tag` helper. *Rafael Mendonça França*
+
+* The `select` method (select tag) forces :include_blank if `required` is true and
+ `display size` is one and `multiple` is not true. *Angelo Capilleri*
+
+* Copy literal route constraints to defaults so that url generation know about them.
+ The copied constraints are `:protocol`, `:subdomain`, `:domain`, `:host` and `:port`.
+
+ *Andrew White*
+
+* `respond_to` and `respond_with` now raise ActionController::UnknownFormat instead
+ of directly returning head 406. The exception is rescued and converted to 406
+ in the exception handling middleware. *Steven Soroka*
+
+* Allows `assert_redirected_to` to match against a regular expression. *Andy Lindeman*
+
+* Add backtrace to development routing error page. *Richard Schneeman*
+
+* Replace `include_seconds` boolean argument with `:include_seconds => true` option
+ in `distance_of_time_in_words` and `time_ago_in_words` signature. *Dmitriy Kiriyenko*
+
+* Remove `button_to_function` and `link_to_function` helpers. *Rafael Mendonça França*
+
+* Make current object and counter (when it applies) variables accessible when
+ rendering templates with :object / :collection. *Carlos Antonio da Silva*
+
+* JSONP now uses mimetype application/javascript instead of application/json. *omjokine*
+
+* Allow to lazy load `default_form_builder` by passing a `String` instead of a constant. *Piotr Sarnacki*
+
+* Session arguments passed to `process` calls in functional tests are now merged into
+ the existing session, whereas previously they would replace the existing session.
+ This change may break some existing tests if they are asserting the exact contents of
+ the session but should not break existing tests that only assert individual keys.
+
+ *Andrew White*
+
+* Add `index` method to FormBuilder class. *Jorge Bejar*
+
+* Remove the leading \n added by textarea on assert_select. *Santiago Pastorino*
+
+* Changed default value for `config.action_view.embed_authenticity_token_in_remote_forms`
+ to `false`. This change breaks remote forms that need to work also without javascript,
+ so if you need such behavior, you can either set it to `true` or explicitly pass
+ `:authenticity_token => true` in form options
+
+* Added ActionDispatch::SSL middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França*
+
+* Add `include_hidden` option to select tag. With `:include_hidden => false` select with `multiple` attribute doesn't generate hidden input with blank value. *Vasiliy Ermolovich*
+
+* Removed default `size` option from the `text_field`, `search_field`, `telephone_field`, `url_field`, `email_field` helpers. *Philip Arndt*
+
+* Removed default `cols` and `rows` options from the `text_area` helper. *Philip Arndt*
+
+* Adds support for layouts when rendering a partial with a given collection. *serabe*
+
+* Allows the route helper `root` to take a string argument. For example, `root 'pages#main'`. *bcardarella*
+
+* Forms of persisted records use always PATCH (via the `_method` hack). *fxn*
+
+* For resources, both PATCH and PUT are routed to the `update` action. *fxn*
+
+* Don't ignore `force_ssl` in development. This is a change of behavior - use a `:if` condition to recreate the old behavior.
+
+ class AccountsController < ApplicationController
+ force_ssl :if => :ssl_configured?
+
+ def ssl_configured?
+ !Rails.env.development?
+ end
+ end
+
+ *Pat Allan*
+
+* Adds support for the PATCH verb:
+ * Request objects respond to `patch?`.
+ * Routes have a new `patch` method, and understand `:patch` in the
+ existing places where a verb is configured, like `:via`.
+ * New method `patch` available in functional tests.
+ * If `:patch` is the default verb for updates, edits are
+ tunneled as PATCH rather than as PUT, and routing acts accordingly.
+ * New method `patch_via_redirect` available in integration tests.
+
+ *dlee*
+
+* Integration tests support the `OPTIONS` method. *Jeremy Kemper*
+
+* `expires_in` accepts a `must_revalidate` flag. If true, "must-revalidate"
+ is added to the Cache-Control header. *fxn*
+
+* Add `date_field` and `date_field_tag` helpers which render an `input[type="date"]` tag *Olek Janiszewski*
+
+* Adds `image_url`, `javascript_url`, `stylesheet_url`, `audio_url`, `video_url`, and `font_url`
+ to assets tag helper. These URL helpers will return the full path to your assets. This is useful
+ when you are going to reference this asset from external host. *Prem Sichanugrist*
+
+* Default responder will now always use your overridden block in `respond_with` to render your response. *Prem Sichanugrist*
+
+* Allow `value_method` and `text_method` arguments from `collection_select` and
+ `options_from_collection_for_select` to receive an object that responds to `:call`,
+ such as a `proc`, to evaluate the option in the current element context. This works
+ the same way with `collection_radio_buttons` and `collection_check_boxes`.
+
+ *Carlos Antonio da Silva + Rafael Mendonça França*
+
+* Add `collection_check_boxes` form helper, similar to `collection_select`:
+ Example:
+
+ collection_check_boxes :post, :author_ids, Author.all, :id, :name
+ # Outputs something like:
+ <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" />
+ <label for="post_author_ids_1">D. Heinemeier Hansson</label>
+ <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
+ <label for="post_author_ids_2">D. Thomas</label>
+ <input name="post[author_ids][]" type="hidden" value="" />
+
+ The label/check_box pairs can be customized with a block.
+
+ *Carlos Antonio da Silva + Rafael Mendonça França*
+
+* Add `collection_radio_buttons` form helper, similar to `collection_select`:
+ Example:
+
+ collection_radio_buttons :post, :author_id, Author.all, :id, :name
+ # Outputs something like:
+ <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" />
+ <label for="post_author_id_1">D. Heinemeier Hansson</label>
+ <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
+ <label for="post_author_id_2">D. Thomas</label>
+
+ The label/radio_button pairs can be customized with a block.
+
+ *Carlos Antonio da Silva + Rafael Mendonça França*
+
+* check_box with `:form` html5 attribute will now replicate the `:form`
+ attribute to the hidden field as well. *Carlos Antonio da Silva*
+
+* Turn off verbose mode of rack-cache, we still have X-Rack-Cache to
+ check that info. Closes #5245. *Santiago Pastorino*
+
+* `label` form helper accepts :for => nil to not generate the attribute. *Carlos Antonio da Silva*
+
+* Add `:format` option to number_to_percentage *Rodrigo Flores*
+
+* Add `config.action_view.logger` to configure logger for ActionView. *Rafael Mendonça França*
+
+* Deprecated ActionController::Integration in favour of ActionDispatch::Integration
+
+* Deprecated ActionController::IntegrationTest in favour of ActionDispatch::IntegrationTest
+
+* Deprecated ActionController::PerformanceTest in favour of ActionDispatch::PerformanceTest
+
+* Deprecated ActionController::AbstractRequest in favour of ActionDispatch::Request
+
+* Deprecated ActionController::Request in favour of ActionDispatch::Request
+
+* Deprecated ActionController::AbstractResponse in favour of ActionDispatch::Response
+
+* Deprecated ActionController::Response in favour of ActionDispatch::Response
+
+* Deprecated ActionController::Routing in favour of ActionDispatch::Routing
+
+* check_box helper with :disabled => true will generate a disabled hidden field to conform with the HTML convention where disabled fields are not submitted with the form.
+ This is a behavior change, previously the hidden tag had a value of the disabled checkbox.
+ *Tadas Tamosauskas*
+
+* `favicon_link_tag` helper will now use the favicon in app/assets by default. *Lucas Caton*
+
+* `ActionView::Helpers::TextHelper#highlight` now defaults to the
+ HTML5 `mark` element. *Brian Cardarella*
+
+
+## Rails 3.2.3 (March 30, 2012) ##
+
+* Add `config.action_view.embed_authenticity_token_in_remote_forms` (defaults to true) which allows to set if authenticity token will be included by default in remote forms. If you change it to false, you can still force authenticity token by passing `:authenticity_token => true` in form options *Piotr Sarnacki*
+
+* Do not include the authenticity token in forms where remote: true as ajax forms use the meta-tag value *DHH*
+
+* Upgrade rack-cache to 1.2. *José Valim*
+
+* ActionController::SessionManagement is removed. *Santiago Pastorino*
+
+* Since the router holds references to many parts of the system like engines, controllers and the application itself, inspecting the route set can actually be really slow, therefore we default alias inspect to to_s. *José Valim*
+
+* Add a new line after the textarea opening tag. Closes #393 *Rafael Mendonça França*
+
+* Always pass a respond block from to responder. We should let the responder decide what to do with the given overridden response block, and not short circuit it. *Prem Sichanugrist*
+
+* Fixes layout rendering regression from 3.2.2. *José Valim*
+
+
+## Rails 3.2.2 (March 1, 2012) ##
+
+* Format lookup for partials is derived from the format in which the template is being rendered. Closes #5025 part 2 *Santiago Pastorino*
+
+* Use the right format when a partial is missing. Closes #5025. *Santiago Pastorino*
+
+* Default responder will now always use your overridden block in `respond_with` to render your response. *Prem Sichanugrist*
+
+* check_box helper with :disabled => true will generate a disabled hidden field to conform with the HTML convention where disabled fields are not submitted with the form.
+ This is a behavior change, previously the hidden tag had a value of the disabled checkbox.
+ *Tadas Tamosauskas*
+
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* Documentation improvements.
+
+* Allow `form.select` to accept ranges (regression). *Jeremy Walker*
+
+* `datetime_select` works with -/+ infinity dates. *Joe Van Dyk*
+
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* Add `config.action_dispatch.default_charset` to configure default charset for ActionDispatch::Response. *Carlos Antonio da Silva*
+
+* Deprecate setting default charset at controller level, use the new `config.action_dispatch.default_charset` instead. *Carlos Antonio da Silva*
+
+* Deprecate ActionController::UnknownAction in favour of AbstractController::ActionNotFound. *Carlos Antonio da Silva*
+
+* Deprecate ActionController::DoubleRenderError in favour of AbstractController::DoubleRenderError. *Carlos Antonio da Silva*
+
+* Deprecate method_missing handling for not found actions, use action_missing instead. *Carlos Antonio da Silva*
+
+* Deprecate ActionController#rescue_action, ActionController#initialize_template_class, and ActionController#assign_shortcuts.
+ These methods were not being used internally anymore and are going to be removed in Rails 4. *Carlos Antonio da Silva*
+
+* Use a BodyProxy instead of including a Module that responds to
+ close. Closes #4441 if Active Record is disabled assets are delivered
+ correctly *Santiago Pastorino*
+
+* Rails initialization with initialize_on_precompile = false should set assets_dir *Santiago Pastorino*
+
+* Add font_path helper method *Santiago Pastorino*
+
+* Depends on rack ~> 1.4.0 *Santiago Pastorino*
+
+* Add :gzip option to `caches_page`. The default option can be configured globally using `page_cache_compression` *Andrey Sitnik*
+
+* The ShowExceptions middleware now accepts a exceptions application that is responsible to render an exception when the application fails. The application is invoked with a copy of the exception in `env["action_dispatch.exception"]` and with the PATH_INFO rewritten to the status code. *José Valim*
+
+* Add `button_tag` support to ActionView::Helpers::FormBuilder.
+
+ This support mimics the default behavior of `submit_tag`.
+
+ Example:
+
+ <%= form_for @post do |f| %>
+ <%= f.button %>
+ <% end %>
+
+* Date helpers accept a new option, `:use_two_digit_numbers = true`, that renders select boxes for months and days with a leading zero without changing the respective values.
+ For example, this is useful for displaying ISO8601-style dates such as '2011-08-01'. *Lennart Fridén and Kim Persson*
+
+* Make ActiveSupport::Benchmarkable a default module for ActionController::Base, so the #benchmark method is once again available in the controller context like it used to be *DHH*
+
+* Deprecated implied layout lookup in controllers whose parent had a explicit layout set:
+
+ class ApplicationController
+ layout "application"
+ end
+
+ class PostsController < ApplicationController
+ end
+
+ In the example above, Posts controller will no longer automatically look up for a posts layout.
+
+ If you need this functionality you could either remove `layout "application"` from ApplicationController or explicitly set it to nil in PostsController. *José Valim*
+
+* Rails will now use your default layout (such as "layouts/application") when you specify a layout with `:only` and `:except` condition, and those conditions fail. *Prem Sichanugrist*
+
+ For example, consider this snippet:
+
+ class CarsController
+ layout 'single_car', :only => :show
+ end
+
+ Rails will use 'layouts/single_car' when a request comes in `:show` action, and use 'layouts/application' (or 'layouts/cars', if exists) when a request comes in for any other actions.
+
+* form_for with +:as+ option uses "#{action}_#{as}" as css class and id:
+
+ Before:
+
+ form_for(@user, :as => 'client') # => "<form class="client_new">..."
+
+ Now:
+
+ form_for(@user, :as => 'client') # => "<form class="new_client">..."
+
+ *Vasiliy Ermolovich*
+
+* Allow rescue responses to be configured through a railtie as in `config.action_dispatch.rescue_responses`. Please look at ActiveRecord::Railtie for an example *José Valim*
+
+* Allow fresh_when/stale? to take a record instead of an options hash *DHH*
+
+* Assets should use the request protocol by default or default to relative if no request is available *Jonathan del Strother*
+
+* Log "Filter chain halted as CALLBACKNAME rendered or redirected" every time a before callback halts *José Valim*
+
+* You can provide a namespace for your form to ensure uniqueness of id attributes on form elements.
+ The namespace attribute will be prefixed with underscore on the generate HTML id. *Vasiliy Ermolovich*
+
+ Example:
+
+ <%= form_for(@offer, :namespace => 'namespace') do |f| %>
+ <%= f.label :version, 'Version' %>:
+ <%= f.text_field :version %>
+ <% end %>
+
+* Refactor ActionDispatch::ShowExceptions. The controller is responsible for choosing to show exceptions when `consider_all_requests_local` is false.
+
+ It's possible to override `show_detailed_exceptions?` in controllers to specify which requests should provide debugging information on errors. The default value is now false, meaning local requests in production will no longer show the detailed exceptions page unless `show_detailed_exceptions?` is overridden and set to `request.local?`.
+
+* Responders now return 204 No Content for API requests without a response body (as in the new scaffold) *José Valim*
+
+* Added ActionDispatch::RequestId middleware that'll make a unique X-Request-Id header available to the response and enables the ActionDispatch::Request#uuid method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog *DHH*
+
+* Limit the number of options for select_year to 1000.
+
+ Pass the :max_years_allowed option to set your own limit.
+
+ *Libo Cannici*
+
+* Passing formats or handlers to render :template and friends is deprecated. For example: *Nick Sutterer & José Valim*
+
+ render :template => "foo.html.erb"
+
+ Instead, you can provide :handlers and :formats directly as option:
+ render :template => "foo", :formats => [:html, :js], :handlers => :erb
+
+* Changed log level of warning for missing CSRF token from :debug to :warn. *Mike Dillon*
+
+* content_tag_for and div_for can now take the collection of records. It will also yield the record as the first argument if you set a receiving argument in your block *Prem Sichanugrist*
+
+ So instead of having to do this:
+
+ @items.each do |item|
+ content_tag_for(:li, item) do
+ Title: <%= item.title %>
+ end
+ end
+
+ You can now do this:
+
+ content_tag_for(:li, @items) do |item|
+ Title: <%= item.title %>
+ end
+
+* send_file now guess the mime type *Esad Hajdarevic*
+
+* Mime type entries for PDF, ZIP and other formats were added *Esad Hajdarevic*
+
+* Generate hidden input before select with :multiple option set to true.
+ This is useful when you rely on the fact that when no options is set,
+ the state of select will be sent to rails application. Without hidden field
+ nothing is sent according to HTML spec *Bogdan Gusiev*
+
+* Refactor ActionController::TestCase cookies *Andrew White*
+
+ Assigning cookies for test cases should now use cookies[], e.g:
+
+ cookies[:email] = 'user@example.com'
+ get :index
+ assert_equal 'user@example.com', cookies[:email]
+
+ To clear the cookies, use clear, e.g:
+
+ cookies.clear
+ get :index
+ assert_nil cookies[:email]
+
+ We now no longer write out HTTP_COOKIE and the cookie jar is
+ persistent between requests so if you need to manipulate the environment
+ for your test you need to do it before the cookie jar is created.
+
+* ActionController::ParamsWrapper on ActiveRecord models now only wrap
+ attr_accessible attributes if they were set, if not, only the attributes
+ returned by the class method attribute_names will be wrapped. This fixes
+ the wrapping of nested attributes by adding them to attr_accessible.
+
+
+## Rails 3.1.4 (March 1, 2012) ##
+
+* Skip assets group in Gemfile and all assets configurations options
+ when the application is generated with --skip-sprockets option.
+
+ *Guillermo Iguaran*
+
+* Use ProcessedAsset#pathname in Sprockets helpers when debugging is on. Closes #3333 #3348 #3361.
+
+ *Guillermo Iguaran*
+
+* Allow to use asset_path on named_routes aliasing RailsHelper's
+ asset_path to path_to_asset *Adrian Pike*
+
+* Assets should use the request protocol by default or default to relative if no request is available *Jonathan del Strother*
+
+
+## Rails 3.1.3 (November 20, 2011) ##
+
+* Downgrade sprockets to ~> 2.0.3. Using 2.1.0 caused regressions.
+
+* Fix using `translate` helper with a html translation which uses the `:count` option for
+ pluralization.
+
+ *Jon Leighton*
+
+
+## Rails 3.1.2 (November 18, 2011) ##
+
+* Fix XSS security vulnerability in the `translate` helper method. When using interpolation
+ in combination with HTML-safe translations, the interpolated input would not get HTML
+ escaped. *GH 3664*
+
+ Before:
+
+ translate('foo_html', :something => '<script>') # => "...<script>..."
+
+ After:
+
+ translate('foo_html', :something => '<script>') # => "...&lt;script&gt;..."
+
+ *Sergey Nartimov*
+
+* Upgrade sprockets dependency to ~> 2.1.0
+
+* Ensure that the format isn't applied twice to the cache key, else it becomes impossible
+ to target with expire_action.
+
+ *Christopher Meiklejohn*
+
+* Swallow error when can't unmarshall object from session.
+
+ *Bruno Zanchet*
+
+* Implement a workaround for a bug in ruby-1.9.3p0 where an error would be raised
+ while attempting to convert a template from one encoding to another.
+
+ Please see http://redmine.ruby-lang.org/issues/5564 for details of the bug.
+
+ The workaround is to load all conversions into memory ahead of time, and will
+ only happen if the ruby version is *exactly* 1.9.3p0. The hope is obviously that
+ the underlying problem will be resolved in the next patchlevel release of
+ 1.9.3.
+
+ *Jon Leighton*
+
+* Ensure users upgrading from 3.0.x to 3.1.x will properly upgrade their flash object in session (issues #3298 and #2509)
+
+
+## Rails 3.1.1 (October 07, 2011) ##
+
+* javascript_path and stylesheet_path now refer to /assets if asset pipelining
+ is on. *Santiago Pastorino*
+
+* button_to support form option. Now you're able to pass for example
+ 'data-type' => 'json'. *ihower*
+
+* image_path and image_tag should use /assets if asset pipelining is turned
+ on. Closes #3126 *Santiago Pastorino and christos*
+
+* Avoid use of existing precompiled assets during rake assets:precompile run.
+ Closes #3119 *Guillermo Iguaran*
+
+* Copy assets to nondigested filenames too *Santiago Pastorino*
+
+* Give precedence to `config.digest = false` over the existence of
+ manifest.yml asset digests *christos*
+
+* escape options for the stylesheet_link_tag method *Alexey Vakhov*
+
+* Re-launch assets:precompile task using (Rake.)ruby instead of Kernel.exec so
+ it works on Windows *cablegram*
+
+* env var passed to process shouldn't be modified in process method. *Santiago
+ Pastorino*
+
+* `rake assets:precompile` loads the application but does not initialize
+ it.
+ To the app developer, this means configuration add in
+ config/initializers/* will not be executed.
+ Plugins developers need to special case their initializers that are
+ meant to be run in the assets group by adding :group => :assets. *José Valim*
+
+* Sprockets uses config.assets.prefix for asset_path *asee*
+
+* FileStore key_file_path properly limit filenames to 255 characters. *phuibonhoa*
+
+* Fix Hash#to_query edge case with html_safe strings. *brainopia*
+
+* Allow asset tag helper methods to accept :digest => false option in order to completely avoid the digest generation.
+ Useful for linking assets from static html files or from emails when the user could probably look at an older html email with an older asset. *Santiago Pastorino*
+
+* Don't mount Sprockets server at config.assets.prefix if config.assets.compile is false. *Mark J. Titorenko*
+
+* Set relative url root in assets when controller isn't available for Sprockets (eg. Sass files using asset_path). Fixes #2435 *Guillermo Iguaran*
+
+* Fix basic auth credential generation to not make newlines. GH #2882
+
+* Fixed the behavior of asset pipeline when config.assets.digest and config.assets.compile are false and requested asset isn't precompiled.
+ Before the requested asset were compiled anyway ignoring that the config.assets.compile flag is false. *Guillermo Iguaran*
+
+* CookieJar is now Enumerable. Fixes #2795
+
+* Fixed AssetNotPrecompiled error raised when rake assets:precompile is compiling certain .erb files. See GH #2763 #2765 #2805 *Guillermo Iguaran*
+
+* Manifest is correctly placed in assets path when default assets prefix is changed. Fixes #2776 *Guillermo Iguaran*
+
+* Fixed stylesheet_link_tag and javascript_include_tag to respect additional options passed by the users when debug is on. *Guillermo Iguaran*
+
+
+## Rails 3.1.0 (August 30, 2011) ##
+
+* Param values are `paramified` in controller tests. *David Chelimsky*
+
+* x_sendfile_header now defaults to nil and config/environments/production.rb doesn't set a particular value for it. This allows servers to set it through X-Sendfile-Type. *Santiago Pastorino*
+
+* The submit form helper does not generate an id "object_name_id" anymore. *fbrusatti*
+
+* Make sure respond_with with :js tries to render a template in all cases *José Valim*
+
+* json_escape will now return a SafeBuffer string if it receives SafeBuffer string *tenderlove*
+
+* Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string *Prem Sichanugrist*
+
+* Fix escape_js to work correctly with the new SafeBuffer restriction *Paul Gallagher*
+
+* Brought back alternative convention for namespaced models in i18n *thoefer*
+
+ Now the key can be either "namespace.model" or "namespace/model" until further deprecation.
+
+* It is prohibited to perform a in-place SafeBuffer mutation *tenderlove*
+
+ The old behavior of SafeBuffer allowed you to mutate string in place via
+ method like `sub!`. These methods can add unsafe strings to a safe buffer,
+ and the safe buffer will continue to be marked as safe.
+
+ An example problem would be something like this:
+
+ <%= link_to('hello world', @user).sub!(/hello/, params[:xss]) %>
+
+ In the above example, an untrusted string (`params[:xss]`) is added to the
+ safe buffer returned by `link_to`, and the untrusted content is successfully
+ sent to the client without being escaped. To prevent this from happening
+ `sub!` and other similar methods will now raise an exception when they are called on a safe buffer.
+
+ In addition to the in-place versions, some of the versions of these methods which return a copy of the string will incorrectly mark strings as safe. For example:
+
+ <%= link_to('hello world', @user).sub(/hello/, params[:xss]) %>
+
+ The new versions will now ensure that *all* strings returned by these methods on safe buffers are marked unsafe.
+
+ You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb
+
+* Warn if we cannot verify CSRF token authenticity *José Valim*
+
+* Allow AM/PM format in datetime selectors *Aditya Sanghi*
+
+* Only show dump of regular env methods on exception screen (not all the rack crap) *DHH*
+
+* auto_link has been removed with no replacement. If you still use auto_link
+ please install the rails_autolink gem:
+ http://github.com/tenderlove/rails_autolink
+
+ *tenderlove*
+
+* Added streaming support, you can enable it with: *José Valim*
+
+ class PostsController < ActionController::Base
+ stream :only => :index
+ end
+
+ Please read the docs at `ActionController::Streaming` for more information.
+
+* Added `ActionDispatch::Request.ignore_accept_header` to ignore accept headers and only consider the format given as parameter *José Valim*
+
+* Created `ActionView::Renderer` and specified an API for `ActionView::Context`, check those objects for more information *José Valim*
+
+* Added `ActionController::ParamsWrapper` to wrap parameters into a nested hash, and will be turned on for JSON request in new applications by default *Prem Sichanugrist*
+
+ This can be customized by setting `ActionController::Base.wrap_parameters` in `config/initializer/wrap_parameters.rb`
+
+* RJS has been extracted out to a gem. *fxn*
+
+* Implicit actions named not_implemented can be rendered. *Santiago Pastorino*
+
+* Wildcard route will always match the optional format segment by default. *Prem Sichanugrist*
+
+ For example if you have this route:
+
+ match '*pages' => 'pages#show'
+
+ by requesting '/foo/bar.json', your `params[:pages]` will be equals to "foo/bar" with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `:format => false` like this:
+
+ match '*pages' => 'pages#show', :format => false
+
+* Added Base.http_basic_authenticate_with to do simple http basic authentication with a single class method call *DHH*
+
+ class PostsController < ApplicationController
+ USER_NAME, PASSWORD = "dhh", "secret"
+
+ before_filter :authenticate, :except => [ :index ]
+
+ def index
+ render :text => "Everyone can see me!"
+ end
+
+ def edit
+ render :text => "I'm only accessible if you know the password"
+ end
+
+ private
+ def authenticate
+ authenticate_or_request_with_http_basic do |user_name, password|
+ user_name == USER_NAME && password == PASSWORD
+ end
+ end
+ end
+
+ ..can now be written as
+
+ class PostsController < ApplicationController
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
+
+ def index
+ render :text => "Everyone can see me!"
+ end
+
+ def edit
+ render :text => "I'm only accessible if you know the password"
+ end
+ end
+
+* Allow you to add `force_ssl` into controller to force browser to transfer data via HTTPS protocol on that particular controller. You can also specify `:only` or `:except` to specific it to particular action. *DHH and Prem Sichanugrist*
+
+* Allow FormHelper#form_for to specify the :method as a direct option instead of through the :html hash *DHH*
+
+ form_for(@post, remote: true, method: :delete) instead of form_for(@post, remote: true, html: { method: :delete })
+
+* Make JavaScriptHelper#j() an alias for JavaScriptHelper#escape_javascript() -- note this then supersedes the Object#j() method that the JSON gem adds within templates using the JavaScriptHelper *DHH*
+
+* Sensitive query string parameters (specified in config.filter_parameters) will now be filtered out from the request paths in the log file. *Prem Sichanugrist, fxn*
+
+* URL parameters which return false for to_param now appear in the query string (previously they were removed) *Andrew White*
+
+* URL parameters which return nil for to_param are now removed from the query string *Andrew White*
+
+* ActionDispatch::MiddlewareStack now uses composition over inheritance. It is
+ no longer an array which means there may be methods missing that were not tested.
+
+* Add an :authenticity_token option to form_tag for custom handling or to omit the token (pass :authenticity_token => false). *Jakub Kuźma, Igor Wiedler*
+
+* HTML5 button_tag helper. *Rizwan Reza*
+
+* Template lookup now searches further up in the inheritance chain. *Artemave*
+
+* Brought back config.action_view.cache_template_loading, which allows to decide whether templates should be cached or not. *Piotr Sarnacki*
+
+* url_for and named url helpers now accept :subdomain and :domain as options, *Josh Kalderimis*
+
+* The redirect route method now also accepts a hash of options which will only change the parts of the url in question, or an object which responds to call, allowing for redirects to be reused (check the documentation for examples). *Josh Kalderimis*
+
+* Added config.action_controller.include_all_helpers. By default 'helper :all' is done in ActionController::Base, which includes all the helpers by default. Setting include_all_helpers to false will result in including only application_helper and helper corresponding to controller (like foo_helper for foo_controller). *Piotr Sarnacki*
+
+* Added a convenience idiom to generate HTML5 data-* attributes in tag helpers from a :data hash of options:
+
+ tag("div", :data => {:name => 'Stephen', :city_state => %w(Chicago IL)})
+ # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
+
+ Keys are dasherized. Values are JSON-encoded, except for strings and symbols. *Stephen Celis*
+
+* Deprecate old template handler API. The new API simply requires a template handler to respond to call. *José Valim*
+
+* :rhtml and :rxml were finally removed as template handlers. *José Valim*
+
+* Moved etag responsibility from ActionDispatch::Response to the middleware stack. *José Valim*
+
+* Rely on Rack::Session stores API for more compatibility across the Ruby world. This is backwards incompatible since Rack::Session expects #get_session to accept 4 arguments and requires #destroy_session instead of simply #destroy. *José Valim*
+
+* file_field automatically adds :multipart => true to the enclosing form. *Santiago Pastorino*
+
+* Renames csrf_meta_tag -> csrf_meta_tags, and aliases csrf_meta_tag for backwards compatibility. *fxn*
+
+* Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. *Yehuda Katz, Carl Lerche*
+
+
+## Rails 3.0.12 (March 1, 2012) ##
+
+* Fix using `tranlate` helper with a html translation which uses the `:count` option for
+ pluralization.
+
+ *Jon Leighton*
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* Fix XSS security vulnerability in the `translate` helper method. When using interpolation
+ in combination with HTML-safe translations, the interpolated input would not get HTML
+ escaped. *GH 3664*
+
+ Before:
+
+ translate('foo_html', :something => '<script>') # => "...<script>..."
+
+ After:
+
+ translate('foo_html', :something => '<script>') # => "...&lt;script&gt;..."
+
+ *Sergey Nartimov*
+
+* Implement a workaround for a bug in ruby-1.9.3p0 where an error would be
+ raised while attempting to convert a template from one encoding to another.
+
+ Please see http://redmine.ruby-lang.org/issues/5564 for details of the bug.
+
+ The workaround is to load all conversions into memory ahead of time, and will
+ only happen if the ruby version is exactly 1.9.3p0. The hope is obviously
+ that the underlying problem will be resolved in the next patchlevel release
+ of 1.9.3.
+
+* Fix assert_select_email to work on multipart and non-multipart emails as the method stopped working correctly in Rails 3.x due to changes in the new mail gem.
+
+* Fix url_for when passed a hash to prevent additional options (eg. :host, :protocol) from being added to the hash after calling it.
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* Fixes an issue where cache sweepers with only after filters would have no
+ controller object, it would raise undefined method controller_name for nil [jeroenj]
+
+* Ensure status codes are logged when exceptions are raised.
+
+* Subclasses of OutputBuffer are respected.
+
+* Fixed ActionView::FormOptionsHelper#select with :multiple => false
+
+* Avoid extra call to Cache#read in case of a fragment cache hit
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove]
+
+* Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string [Prem Sichanugrist]
+
+* Fix text helpers to work correctly with the new SafeBuffer restriction [Paul Gallagher, Arun Agrawal, Prem Sichanugrist]
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* It is prohibited to perform a in-place SafeBuffer mutation [tenderlove]
+
+ The old behavior of SafeBuffer allowed you to mutate string in place via
+ method like `sub!`. These methods can add unsafe strings to a safe buffer,
+ and the safe buffer will continue to be marked as safe.
+
+ An example problem would be something like this:
+
+ <%= link_to('hello world', @user).sub!(/hello/, params[:xss]) %>
+
+ In the above example, an untrusted string (`params[:xss]`) is added to the
+ safe buffer returned by `link_to`, and the untrusted content is successfully
+ sent to the client without being escaped. To prevent this from happening
+ `sub!` and other similar methods will now raise an exception when they are called on a safe buffer.
+
+ In addition to the in-place versions, some of the versions of these methods which return a copy of the string will incorrectly mark strings as safe. For example:
+
+ <%= link_to('hello world', @user).sub(/hello/, params[:xss]) %>
+
+ The new versions will now ensure that *all* strings returned by these methods on safe buffers are marked unsafe.
+
+ You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb
+
+* Fixed github issue #342 with asset paths and relative roots.
+
+
+## Rails 3.0.7 (April 18, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.6 (April 5, 2011) ##
+
+* Fixed XSS vulnerability in `auto_link`. `auto_link` no longer marks input as
+ html safe. Please make sure that calls to auto_link() are wrapped in a
+ sanitize(), or a raw() depending on the type of input passed to auto_link().
+ For example:
+
+ <%= sanitize(auto_link(some_user_input)) %>
+
+ Thanks to Torben Schulz for reporting this. The fix can be found here:
+ 61ee3449674c591747db95f9b3472c5c3bd9e84d
+
+* Fixes the output of `rake routes` to be correctly match to the behavior of the application, as the regular expression used to match the path is greedy and won't capture the format part by default *Prem Sichanugrist*
+
+* Fixes an issue with number_to_human when converting values which are less than 1 but greater than -1 *Josh Kalderimis*
+
+* Sensitive query string parameters (specified in config.filter_parameters) will now be filtered out from the request paths in the log file. *Prem Sichanugrist, fxn*
+
+* URL parameters which return nil for to_param are now removed from the query string *Andrew White*
+
+* Don't allow i18n to change the minor version, version now set to ~> 0.5.0 *Santiago Pastorino*
+
+* Make TranslationHelper#translate use the :rescue_format option in I18n 0.5.0 *Sven Fuchs*
+
+* Fix regression: javascript_include_tag shouldn't raise if you register an expansion key with nil or [] value *Santiago Pastorino*
+
+* Fix Action caching bug where an action that has a non-cacheable response always renders a nil response body. It now correctly renders the response body. *Cheah Chu Yeow*
+
+
+## Rails 3.0.5 (February 26, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.4 (February 8, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.3 (November 16, 2010) ##
+
+* When ActiveRecord::Base objects are sent to predicate methods, the id of the object should be sent to ARel, not the ActiveRecord::Base object.
+
+* :constraints routing should only do sanity checks against regular expressions. String arguments are OK.
+
+
+## Rails 3.0.2 (November 15, 2010) ##
+
+* The helper number_to_currency accepts a new :negative_format option to be able to configure how to render negative amounts. *Don Wilson*
+
+
+## Rails 3.0.1 (October 15, 2010) ##
+
+* No Changes, just a version bump.
+
+
+## Rails 3.0.0 (August 29, 2010) ##
+
+* password_field renders with nil value by default making the use of passwords secure by default, if you want to render you should do for instance f.password_field(:password, :value => @user.password) *Santiago Pastorino*
+
+* Symbols and strings in routes should yield the same behavior. Note this may break existing apps that were using symbols with the new routes API. *José Valim*
+
+* Add clear_helpers as a way to clean up all helpers added to this controller, maintaining just the helper with the same name as the controller. *José Valim*
+
+* Support routing constraints in functional tests. *Andrew White*
+
+* Add a header that tells Internet Explorer (all versions) to use the best available standards support. *Yehuda Katz*
+
+* Allow stylesheet/javascript extensions to be changed through railties. *Josh Kalderimis*
+
+* link_to, button_to, and tag/tag_options now rely on html_escape instead of escape_once. *fxn*
+
+* url_for returns always unescaped strings, and the :escape option is gone. *fxn*
+
+* Added accept-charset parameter and _snowman hidden field to force the contents
+ of Rails POSTed forms to be in UTF-8 *Yehuda Katz*
+
+* Upgrade to Rack 1.2.1 *Jeremy Kemper*
+
+* Allow :path to be given to match/get/post/put/delete instead of :path_names in the new router *Carlos Antônio da Silva*
+
+* Added resources_path_names to the new router DSL *José Valim*
+
+* Allow options to be given to the namespace method in the new router *Carlos Antônio da Silva*
+
+* Deprecate :name_prefix in the new router DSL *José Valim*
+
+* Add shallow routes back to the new router *Diego Carrion, Andrew White*
+
+ resources :posts do
+ shallow do
+ resources :comments
+ end
+ end
+
+ You can now use comment_path for /comments/1 instead of post_comment_path for /posts/1/comments/1.
+
+* Add support for multi-subdomain session by setting cookie host in session cookie so you can share session between www.example.com, example.com and user.example.com. #4818 *Guillermo Álvarez*
+
+* Removed textilize, textilize_without_paragraph and markdown helpers. *Santiago Pastorino*
+
+* Remove middleware laziness *José Valim*
+
+* Make session stores rely on request.cookie_jar and change set_session semantics to return the cookie value instead of a boolean. *José Valim*
+
+* OAuth 2: HTTP Token Authorization support to complement Basic and Digest Authorization. *Rick Olson*
+
+* Fixed inconsistencies in form builder and view helpers #4432 *Neeraj Singh*
+
+* Both :xml and :json renderers now forwards the given options to the model, allowing you to invoke them as render :xml => @projects, :include => :tasks *José Valim, Yehuda Katz*
+
+* Renamed the field error CSS class from fieldWithErrors to field_with_errors for consistency. *Jeremy Kemper*
+
+* Add support for shorthand routes like /projects/status(.:format) #4423 *Diego Carrion*
+
+* Changed translate helper so that it doesn’t mark every translation as safe HTML. Only keys with a "_html" suffix and keys named "html" are considered to be safe HTML. All other translations are left untouched. *Craig Davey*
+
+* New option :as added to form_for allows to change the object name. The old <% form_for :client, @post %> becomes <% form_for @post, :as => :client %> *spastorino*
+
+* Removed verify method in controllers. *JV*
+ It's now available as a plugin at http://github.com/rails/verification
+
+* Removed input, form, error_messages_for and error_message_on from views. *JV*
+ It's now available as a plugin at http://github.com/rails/dynamic_form
+
+* Routes can be scoped by controller module. *Jeremy Kemper*
+
+ # /session => Auth::SessionsController
+ scope :module => 'auth' do
+ resource :session
+ end
+
+* Added #favicon_link_tag, it uses #image_path so in particular the favicon gets an asset ID *fxn*
+
+* Fixed that default locale templates should be used if the current locale template is missing *DHH*
+
+* Added all the new HTML5 form types as individual form tag methods (search, url, number, etc) #3646 *Stephen Celis*
+
+* Changed the object used in routing constraints to be an instance of
+ ActionDispatch::Request rather than Rack::Request *YK*
+
+* Changed ActionDispatch::Request#method to return a String, to be compatible
+ with Rack::Request. Added ActionDispatch::Request#method_symbol to
+ return a symbol form of the request method. *YK*
+
+* Changed ActionDispatch::Request#method to return the original
+ method and #request_method to return the overridden method in the
+ case of methodoverride being used (this means that #method returns
+ "HEAD" and #request_method returns "GET" in HEAD requests). This
+ is for compatibility with Rack::Request *YK*
+
+* #concat is now deprecated in favor of using <%= %> helpers *YK*
+
+* Block helpers now return Strings, so you can use <%= form_for @foo do |f| %>.
+ <% form_for do |f| %> still works with deprecation notices *YK*
+
+* Add a new #mount method on the router that does not anchor the PATH_INFO
+ at the end *YK & CL*
+
+* Create a new LookupContext object that is responsible for performantly
+ finding a template for a given pattern *JV*
+
+* Removed relative_url_for in favor of respecting SCRIPT_NAME *YK & CL*
+
+* Changed file streaming to use Rack::Sendfile middleware *YK*
+
+* ActionDispatch::Request#content_type returns a String to be compatible with
+ Rack::Request. Use #content_mime_type for the Mime::Type instance *YK*
+
+* Updated Prototype to 1.6.1 and Scriptaculous to 1.8.3 *ML*
+
+* Change the preferred way that URL helpers are included into a class*YK & CL*
+
+ # for all helpers including named routes
+ include Rails.application.router.url_helpers
+
+ # for just url_for
+ include Rails.application.router.url_for
+
+* Fixed that PrototypeHelper#update_page should return html_safe *DHH*
+
+* Fixed that much of DateHelper wouldn't return html_safe? strings *DHH*
+
+* Fixed that fragment caching should return a cache hit as html_safe (or it would all just get escaped) *DHH*
+
+* Added that ActionController::Base now does helper :all instead of relying on the default ApplicationController in Rails to do it *DHH*
+
+* Added ActionDispatch::Request#authorization to access the http authentication header regardless of its proxy hiding *DHH*
+
+* Added :alert, :notice, and :flash as options to ActionController::Base#redirect_to that'll automatically set the proper flash before the redirection *DHH*. Examples:
+
+ flash[:notice] = 'Post was created'
+ redirect_to(@post)
+
+ ...becomes:
+
+ redirect_to(@post, :notice => 'Post was created')
+
+* Added ActionController::Base#notice/= and ActionController::Base#alert/= as a convenience accessors in both the controller and the view for flash[:notice]/= and flash[:alert]/= *DHH*
+
+* Introduce grouped_collection_select helper. #1249 *Dan Codeape, Erik Ostrom*
+
+* Make sure javascript_include_tag/stylesheet_link_tag does not append ".js" or ".css" onto external urls. #1664 *Matthew Rudy Jacobs*
+
+* Ruby 1.9: fix Content-Length for multibyte send_data streaming. #2661 *Sava Chankov*
+
+* Ruby 1.9: ERB template encoding using a magic comment at the top of the file. *Jeremy Kemper*
+ <%# encoding: utf-8 %>
+
+* Change integration test helpers to accept Rack environment instead of just HTTP Headers *Pratik Naik*
+
+ Before : get '/path', {}, 'Accept' => 'text/javascript'
+ After : get '/path', {}, 'HTTP_ACCEPT' => 'text/javascript'
+
+* Instead of checking Rails.env.test? in Failsafe middleware, check env["rails.raise_exceptions"] *Bryan Helmkamp*
+
+* Fixed that TestResponse.cookies was returning cookies unescaped #1867 *Doug McInnes*
+
+
+## 2.3.2 Final (March 15, 2009) ##
+
+* Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #<Post:0x23150b8>") *DHH*
+
+* Don't check authenticity tokens for any AJAX requests *Ross Kaffenberger/Bryan Helmkamp*
+
+* Added ability to pass in :public => true to fresh_when, stale?, and expires_in to make the request proxy cachable #2095 *Gregg Pollack*
+
+* Fixed that passing a custom form builder would be forwarded to nested fields_for calls #2023 *Eloy Duran/Nate Wiger*
+
+* Form option helpers now support disabled option tags and the use of lambdas for selecting/disabling option tags from collections #837 *Tekin*
+
+* Added partial scoping to TranslationHelper#translate, so if you call translate(".foo") from the people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo") *DHH*
+
+* Fix a syntax error in current_page?() that was prevent matches against URL's with multiple query parameters #1385, #1868 *chris finne/Andrew White*
+
+* Added localized rescue template when I18n.locale is set (ex: public/404.da.html) #1835 *José Valim*
+
+* Make the form_for and fields_for helpers support the new Active Record nested update options. #1202 *Eloy Duran*
+
+ <% form_for @person do |person_form| %>
+ ...
+ <% person_form.fields_for :projects do |project_fields| %>
+ <% if project_fields.object.active? %>
+ Name: <%= project_fields.text_field :name %>
+ <% end %>
+ <% end %>
+ <% end %>
+
+
+* Added grouped_options_for_select helper method for wrapping option tags in optgroups. #977 *Jon Crawford*
+
+* Implement HTTP Digest authentication. #1230 *Gregg Kellogg, Pratik Naik* Example :
+
+ class DummyDigestController < ActionController::Base
+ USERS = { "lifo" => 'world' }
+
+ before_filter :authenticate
+
+ def index
+ render :text => "Hello Secret"
+ end
+
+ private
+
+ def authenticate
+ authenticate_or_request_with_http_digest("Super Secret") do |username|
+ # Return the user's password
+ USERS[username]
+ end
+ end
+ end
+
+* Improved i18n support for the number_to_human_size helper. Changes the storage_units translation data; update your translations accordingly. #1634 *Yaroslav Markin*
+ storage_units:
+ # %u is the storage unit, %n is the number (default: 2 MB)
+ format: "%n %u"
+ units:
+ byte:
+ one: "Byte"
+ other: "Bytes"
+ kb: "KB"
+ mb: "MB"
+ gb: "GB"
+ tb: "TB"
+
+* Added :silence option to BenchmarkHelper#benchmark and turned log_level into a hash parameter and deprecated the old use *DHH*
+
+* Fixed the AssetTagHelper cache to use the computed asset host as part of the cache key instead of just assuming the its a string #1299 *DHH*
+
+* Make ActionController#render(string) work as a shortcut for render :file/:template/:action => string. #1435 *Pratik Naik* Examples:
+
+ \# Instead of render(:action => 'other_action')
+ render('other_action') # argument has no '/'
+ render(:other_action)
+
+ \# Instead of render(:template => 'controller/action')
+ render('controller/action') # argument must not begin with a '/', but contain a '/'
+
+ \# Instead of render(:file => '/Users/lifo/home.html.erb')
+ render('/Users/lifo/home.html.erb') # argument must begin with a '/'
+
+* Add :prompt option to date/time select helpers. #561 *Sam Oliver*
+
+* Fixed that send_file shouldn't set an etag #1578 *Hongli Lai*
+
+* Allow users to opt out of the spoofing checks in Request#remote_ip. Useful for sites whose traffic regularly triggers false positives. *Darren Boyd*
+
+* Deprecated formatted_polymorphic_url. *Jeremy Kemper*
+
+* Added the option to declare an asset_host as an object that responds to call (see http://github.com/dhh/asset-hosting-with-minimum-ssl for an example) *David Heinemeier Hansson*
+
+* Added support for multiple routes.rb files (useful for plugin engines). This also means that draw will no longer clear the route set, you have to do that by hand (shouldn't make a difference to you unless you're doing some funky stuff) *David Heinemeier Hansson*
+
+* Dropped formatted_* routes in favor of just passing in :format as an option. This cuts resource routes generation in half #1359 *aaronbatalion*
+
+* Remove support for old double-encoded cookies from the cookie store. These values haven't been generated since before 2.1.0, and any users who have visited the app in the intervening 6 months will have had their cookie upgraded. *Michael Koziarski*
+
+* Allow helpers directory to be overridden via ActionController::Base.helpers_dir #1424 *Sam Pohlenz*
+
+* Remove deprecated ActionController::Base#assign_default_content_type_and_charset
+
+* Changed the default of ActionView#render to assume partials instead of files when not given an options hash *DHH*. Examples:
+
+ # Instead of <%= render :partial => "account" %>
+ <%= render "account" %>
+
+ # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
+ <%= render "account", :account => @buyer %>
+
+ # @account is an Account instance, so it uses the RecordIdentifier to replace
+ # <%= render :partial => "accounts/account", :locals => { :account => @account } %>
+ <%= render(@account) %>
+
+ # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
+ # <%= render :partial => "posts/post", :collection => @posts %>
+ <%= render(@posts) %>
+
+* Remove deprecated render_component. Please use the plugin from http://github.com/rails/render_component/tree/master *Pratik Naik*
+
+* Fixed RedCloth and BlueCloth shouldn't preload. Instead just assume that they're available if you want to use textilize and markdown and let autoload require them *David Heinemeier Hansson*
+
+
+## 2.2.2 (November 21st, 2008) ##
+
+* I18n: translate number_to_human_size. Add storage_units: [Bytes, KB, MB, GB, TB] to your translations. #1448 *Yaroslav Markin*
+
+* Restore backwards compatible functionality for setting relative_url_root. Include deprecation
+
+* Switched the CSRF module to use the request content type to decide if the request is forgeable. #1145 *Jeff Cohen*
+
+* Added :only and :except to map.resources to let people cut down on the number of redundant routes in an application. Typically only useful for huge routesets. #1215 *Tom Stuart*
+
+ map.resources :products, :only => :show do |product|
+ product.resources :images, :except => :destroy
+ end
+
+* Added render :js for people who want to render inline JavaScript replies without using RJS *David Heinemeier Hansson*
+
+* Fixed that polymorphic_url should compact given array #1317 *hiroshi*
+
+* Fixed the sanitize helper to avoid double escaping already properly escaped entities #683 *antonmos/Ryan McGeary*
+
+* Fixed that FormTagHelper generated illegal html if name contained square brackets #1238 *Vladimir Dobriakov*
+
+* Fix regression bug that made date_select and datetime_select raise a Null Pointer Exception when a nil date/datetime was passed and only month and year were displayed #1289 *Bernardo Padua/Tor Erik*
+
+* Simplified the logging format for parameters (don't include controller, action, and format as duplicates) *David Heinemeier Hansson*
+
+* Remove the logging of the Session ID when the session store is CookieStore *David Heinemeier Hansson*
+
+* Fixed regex in redirect_to to fully support URI schemes #1247 *Seth Fitzsimmons*
+
+* Fixed bug with asset timestamping when using relative_url_root #1265 *Joe Goldwasser*
+
+
+## 2.2.0 RC1 (October 24th, 2008) ##
+
+* Fix incorrect closing CDATA delimiter and that HTML::Node.parse would blow up on unclosed CDATA sections *packagethief*
+
+* Added stale? and fresh_when methods to provide a layer of abstraction above request.fresh? and friends *DHH*. Example:
+
+ class ArticlesController < ApplicationController
+ def show_with_respond_to_block
+ @article = Article.find(params[:id])
+
+
+ # If the request sends headers that differs from the options provided to stale?, then
+ # the request is indeed stale and the respond_to block is triggered (and the options
+ # to the stale? call is set on the response).
+ #
+ # If the request headers match, then the request is fresh and the respond_to block is
+ # not triggered. Instead the default render will occur, which will check the last-modified
+ # and etag headers and conclude that it only needs to send a "304 Not Modified" instead
+ # of rendering the template.
+ if stale?(:last_modified => @article.published_at.utc, :etag => @article)
+ respond_to do |wants|
+ # normal response processing
+ end
+ end
+ end
+
+ def show_with_implied_render
+ @article = Article.find(params[:id])
+
+ # Sets the response headers and checks them against the request, if the request is stale
+ # (i.e. no match of either etag or last-modified), then the default render of the template happens.
+ # If the request is fresh, then the default render will return a "304 Not Modified"
+ # instead of rendering the template.
+ fresh_when(:last_modified => @article.published_at.utc, :etag => @article)
+ end
+ end
+
+
+* Added inline builder yield to atom_feed_helper tags where appropriate *Sam Ruby*. Example:
+
+ entry.summary :type => 'xhtml' do |xhtml|
+ xhtml.p pluralize(order.line_items.count, "line item")
+ xhtml.p "Shipped to #{order.address}"
+ xhtml.p "Paid by #{order.pay_type}"
+ end
+
+* Make PrototypeHelper#submit_to_remote a wrapper around PrototypeHelper#button_to_remote. *Tarmo Tänav*
+
+* Set HttpOnly for the cookie session store's cookie. #1046
+
+* Added FormTagHelper#image_submit_tag confirm option #784 *Alastair Brunton*
+
+* Fixed FormTagHelper#submit_tag with :disable_with option wouldn't submit the button's value when was clicked #633 *Jose Fernandez*
+
+* Stopped logging template compiles as it only clogs up the log *David Heinemeier Hansson*
+
+* Changed the X-Runtime header to report in milliseconds *David Heinemeier Hansson*
+
+* Changed BenchmarkHelper#benchmark to report in milliseconds *David Heinemeier Hansson*
+
+* Changed logging format to be millisecond based and skip misleading stats *DHH*. Went from:
+
+ Completed in 0.10000 (4 reqs/sec) | Rendering: 0.04000 (40%) | DB: 0.00400 (4%) | 200 OK [http://example.com]
+
+ ...to:
+
+ Completed in 100ms (View: 40, DB: 4) | 200 OK [http://example.com]
+
+* Add support for shallow nesting of routes. #838 *S. Brent Faulkner*
+
+ Example :
+
+ map.resources :users, :shallow => true do |user|
+ user.resources :posts
+ end
+
+ - GET /users/1/posts (maps to PostsController#index action as usual)
+ named route "user_posts" is added as usual.
+
+ - GET /posts/2 (maps to PostsController#show action as if it were not nested)
+ Additionally, named route "post" is added too.
+
+* Added button_to_remote helper. #3641 *Donald Piret, Tarmo Tänav*
+
+* Deprecate render_component. Please use render_component plugin from http://github.com/rails/render_component/tree/master *Pratik Naik*
+
+* Routes may be restricted to lists of HTTP methods instead of a single method or :any. #407 *Brennan Dunn, Gaius Centus Novus*
+ map.resource :posts, :collection => { :search => [:get, :post] }
+ map.session 'session', :requirements => { :method => [:get, :post, :delete] }
+
+* Deprecated implicit local assignments when rendering partials *Josh Peek*
+
+* Introduce current_cycle helper method to return the current value without bumping the cycle. #417 *Ken Collins*
+
+* Allow polymorphic_url helper to take url options. #880 *Tarmo Tänav*
+
+* Switched integration test runner to use Rack processor instead of CGI *Josh Peek*
+
+* Made AbstractRequest.if_modified_sense return nil if the header could not be parsed *Jamis Buck*
+
+* Added back ActionController::Base.allow_concurrency flag *Josh Peek*
+
+* AbstractRequest.relative_url_root is no longer automatically configured by a HTTP header. It can now be set in your configuration environment with config.action_controller.relative_url_root *Josh Peek*
+
+* Update Prototype to 1.6.0.2 #599 *Patrick Joyce*
+
+* Conditional GET utility methods. *Jeremy Kemper*
+ response.last_modified = @post.updated_at
+ response.etag = [:admin, @post, current_user]
+
+ if request.fresh?(response)
+ head :not_modified
+ else
+ # render ...
+ end
+
+* All 2xx requests are considered successful *Josh Peek*
+
+* Fixed that AssetTagHelper#compute_public_path shouldn't cache the asset_host along with the source or per-request proc's won't run *David Heinemeier Hansson*
+
+* Removed config.action_view.cache_template_loading, use config.cache_classes instead *Josh Peek*
+
+* Get buffer for fragment cache from template's @output_buffer *Josh Peek*
+
+* Set config.action_view.warn_cache_misses = true to receive a warning if you perform an action that results in an expensive disk operation that could be cached *Josh Peek*
+
+* Refactor template preloading. New abstractions include Renderable mixins and a refactored Template class *Josh Peek*
+
+* Changed ActionView::TemplateHandler#render API method signature to render(template, local_assigns = {}) *Josh Peek*
+
+* Changed PrototypeHelper#submit_to_remote to PrototypeHelper#button_to_remote to stay consistent with link_to_remote (submit_to_remote still works as an alias) #8994 *clemens*
+
+* Add :recursive option to javascript_include_tag and stylesheet_link_tag to be used along with :all. #480 *Damian Janowski*
+
+* Allow users to disable the use of the Accept header *Michael Koziarski*
+
+ The accept header is poorly implemented by browsers and causes strange
+ errors when used on public sites where crawlers make requests too. You can use formatted urls (e.g. /people/1.xml) to support API clients in a much simpler way.
+ To disable the header you need to set:
+ config.action_controller.use_accept_header = false
+* Do not stat template files in production mode before rendering. You will no longer be able to modify templates in production mode without restarting the server *Josh Peek*
+
+* Deprecated TemplateHandler line offset *Josh Peek*
+
+* Allow caches_action to accept cache store options. #416. *José Valim*. Example:
+
+ caches_action :index, :redirected, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
+
+* Remove define_javascript_functions, javascript_include_tag and friends are far superior. *Michael Koziarski*
+
+* Deprecate :use_full_path render option. The supplying the option no longer has an effect *Josh Peek*
+
+* Add :as option to render a collection of partials with a custom local variable name. #509 *Simon Jefford, Pratik Naik*
+
+ render :partial => 'other_people', :collection => @people, :as => :person
+
+ This will let you access objects of @people as 'person' local variable inside 'other_people' partial template.
+
+* time_zone_select: support for regexp matching of priority zones. Resolves #195 *Ernie Miller*
+
+* Made ActionView::Base#render_file private *Josh Peek*
+
+* Refactor and simplify the implementation of assert_redirected_to. Arguments are now normalised relative to the controller being tested, not the root of the application. *Michael Koziarski*
+
+ This could cause some erroneous test failures if you were redirecting between controllers
+ in different namespaces and wrote your assertions relative to the root of the application.
+
+* Remove follow_redirect from controller functional tests.
+
+ If you want to follow redirects you can use integration tests. The functional test version was only useful if you were using redirect_to :id=>...
+* Fix polymorphic_url with singleton resources. #461 *Tammer Saleh*
+
+* Replaced TemplateFinder abstraction with ViewLoadPaths *Josh Peek*
+
+* Added block-call style to link_to *Sam Stephenson/David Heinemeier Hansson*. Example:
+
+ <% link_to(@profile) do %>
+ <strong><%= @profile.name %></strong> -- <span>Check it out!!</span>
+ <% end %>
+
+* Performance: integration test benchmarking and profiling. *Jeremy Kemper*
+
+* Make caching more aware of mime types. Ensure request format is not considered while expiring cache. *Jonathan del Strother*
+
+* Drop ActionController::Base.allow_concurrency flag *Josh Peek*
+
+* More efficient concat and capture helpers. Remove ActionView::Base.erb_variable. *Jeremy Kemper*
+
+* Added page.reload functionality. Resolves #277. *Sean Huber*
+
+* Fixed Request#remote_ip to only raise hell if the HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR doesn't match (not just if they're both present) *Mark Imbriaco, Bradford Folkens*
+
+* Allow caches_action to accept a layout option *José Valim*
+
+* Added Rack processor *Ezra Zygmuntowicz, Josh Peek*
+
+
+## 2.1.0 (May 31st, 2008) ##
+
+* InstanceTag#default_time_from_options overflows to DateTime *Geoff Buesing*
+
+* Fixed that forgery protection can be used without session tracking (Peter Jones) *#139*
+
+* Added session(:on) to turn session management back on in a controller subclass if the superclass turned it off (Peter Jones) *#136*
+
+* Change the request forgery protection to go by Content-Type instead of request.format so that you can't bypass it by POSTing to "#{request.uri}.xml" *Rick Olson*
+* InstanceTag#default_time_from_options with hash args uses Time.current as default; respects hash settings when time falls in system local spring DST gap *Geoff Buesing*
+
+* select_date defaults to Time.zone.today when config.time_zone is set *Geoff Buesing*
+
+* Fixed that TextHelper#text_field would corrypt when raw HTML was used as the value (mchenryc, Kevin Glowacz) *#80*
+
+* Added ActionController::TestCase#rescue_action_in_public! to control whether the action under test should use the regular rescue_action path instead of simply raising the exception inline (great for error testing) *David Heinemeier Hansson*
+
+* Reduce number of instance variables being copied from controller to view. *Pratik Naik*
+
+* select_datetime and select_time default to Time.zone.now when config.time_zone is set *Geoff Buesing*
+
+* datetime_select defaults to Time.zone.now when config.time_zone is set *Geoff Buesing*
+
+* Remove ActionController::Base#view_controller_internals flag. *Pratik Naik*
+
+* Add conditional options to caches_page method. *Paul Horsfall*
+
+* Move missing template logic to ActionView. *Pratik Naik*
+
+* Introduce ActionView::InlineTemplate class. *Pratik Naik*
+
+* Automatically parse posted JSON content for Mime::JSON requests. *Rick Olson*
+
+ POST /posts
+ {"post": {"title": "Breaking News"}}
+
+ def create
+ @post = Post.create params[:post]
+ # ...
+ end
+
+* add json_escape ERB util to escape html entities in json strings that are output in HTML pages. *Rick Olson*
+
+* Provide a helper proxy to access helper methods from outside views. Closes #10839 *Josh Peek*
+ e.g. ApplicationController.helpers.simple_format(text)
+
+* Improve documentation. *Xavier Noria, leethal, jerome*
+
+* Ensure RJS redirect_to doesn't html-escapes string argument. Closes #8546 *Josh Peek, eventualbuddha, Pratik Naik*
+
+* Support render :partial => collection of heterogeneous elements. #11491 *Zach Dennis*
+
+* Avoid remote_ip spoofing. *Brian Candler*
+
+* Added support for regexp flags like ignoring case in the :requirements part of routes declarations #11421 *NeilW*
+
+* Fixed that ActionController::Base#read_multipart would fail if boundary was exactly 10240 bytes #10886 *ariejan*
+
+* Fixed HTML::Tokenizer (used in sanitize helper) didn't handle unclosed CDATA tags #10071 *esad, packagethief*
+
+* Improve documentation. *Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert*
+
+* Fixed that FormHelper#radio_button would produce invalid ids #11298 *harlancrystal*
+
+* Added :confirm option to submit_tag #11415 *Emilio Tagua*
+
+* Fixed NumberHelper#number_with_precision to properly round in a way that works equally on Mac, Windows, Linux (closes #11409, #8275, #10090, #8027) *zhangyuanyi*
+
+* Allow the #simple_format text_helper to take an html_options hash for each paragraph. #2448 *François Beausoleil, Chris O'Sullivan*
+
+* Fix regression from filter refactoring where re-adding a skipped filter resulted in it being called twice. *Rick Olson*
+
+* Refactor filters to use Active Support callbacks. #11235 *Josh Peek*
+
+* Fixed that polymorphic routes would modify the input array #11363 *thomas.lee*
+
+* Added :format option to NumberHelper#number_to_currency to enable better localization support #11149 *lylo*
+
+* Fixed that TextHelper#excerpt would include one character too many #11268 *Irfy*
+
+* Fix more obscure nested parameter hash parsing bug. #10797 *thomas.lee*
+
+* Added ActionView::Helpers::register_javascript/stylesheet_expansion to make it easier for plugin developers to inject multiple assets. #10350 *lotswholetime*
+
+* Fix nested parameter hash parsing bug. #10797 *thomas.lee*
+
+* Allow using named routes in ActionController::TestCase before any request has been made. Closes #11273 *Eloy Duran*
+
+* Fixed that sweepers defined by cache_sweeper will be added regardless of the perform_caching setting. Instead, control whether the sweeper should be run with the perform_caching setting. This makes testing easier when you want to turn perform_caching on/off *David Heinemeier Hansson*
+
+* Make MimeResponds::Responder#any work without explicit types. Closes #11140 *jaw6*
+
+* Better error message for type conflicts when parsing params. Closes #7962 *spicycode, matt*
+
+* Remove unused ActionController::Base.template_class. Closes #10787 *Pratik Naik*
+
+* Moved template handlers related code from ActionView::Base to ActionView::Template. *Pratik Naik*
+
+* Tests for div_for and content_tag_for helpers. Closes #11223 *Chris O'Sullivan*
+
+* Allow file uploads in Integration Tests. Closes #11091 *RubyRedRick*
+
+* Refactor partial rendering into a PartialTemplate class. *Pratik Naik*
+
+* Added that requests with JavaScript as the priority mime type in the accept header and no format extension in the parameters will be treated as though their format was :js when it comes to determining which template to render. This makes it possible for JS requests to automatically render action.js.rjs files without an explicit respond_to block *David Heinemeier Hansson*
+
+* Tests for distance_of_time_in_words with TimeWithZone instances. Closes #10914 *Ernesto Jimenez*
+
+* Remove support for multivalued (e.g., '&'-delimited) cookies. *Jamis Buck*
+
+* Fix problem with render :partial collections, records, and locals. #11057 *lotswholetime*
+
+* Added support for naming concrete classes in sweeper declarations *David Heinemeier Hansson*
+
+* Remove ERB trim variables from trace template in case ActionView::Base.erb_trim_mode is changed in the application. #10098 *Tim Pope, Chris Kampmeier*
+
+* Fix typo in form_helper documentation. #10650 *Xavier Shay, Chris Kampmeier*
+
+* Fix bug with setting Request#format= after the getter has cached the value. #10889 *cch1*
+
+* Correct inconsistencies in RequestForgeryProtection docs. #11032 *Mislav Marohnić*
+
+* Introduce a Template class to ActionView. #11024 *Pratik Naik*
+
+* Introduce the :index option for form_for and fields_for to simplify multi-model forms (see http://railscasts.com/episodes/75). #9883 *rmm5t*
+
+* Introduce map.resources :cards, :as => 'tarjetas' to use a custom resource name in the URL: cards_path == '/tarjetas'. #10578 *blj*
+
+* TestSession supports indifferent access. #7372 *tamc, Arsen7, mhackett, julik, jean.helou*
+
+* Make assert_routing aware of the HTTP method used. #8039 *mpalmer*
+ e.g. assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
+
+* Make map.root accept a single symbol as an argument to declare an alias. #10818 *bscofield*
+
+ e.g. map.dashboard '/dashboard', :controller=>'dashboard'
+ map.root :dashboard
+
+* Handle corner case with image_tag when passed 'messed up' image names. #9018 *Duncan Beevers, mpalmer*
+
+* Add label_tag helper for generating elements. #10802 *DefV*
+
+* Introduce TemplateFinder to handle view paths and lookups. #10800 *Pratik Naik*
+
+* Performance: optimize route recognition. Large speedup for apps with many resource routes. #10835 *oleganza*
+
+* Make render :partial recognise form builders and use the _form partial. #10814 *Damian Janowski*
+
+* Allow users to declare other namespaces when using the atom feed helpers. #10304 *david.calavera*
+
+* Introduce send_file :x_sendfile => true to send an X-Sendfile response header. *Jeremy Kemper*
+
+* Fixed ActionView::Helpers::ActiveRecordHelper::form for when protect_from_forgery is used #10739 *Jeremy Evans*
+
+* Provide nicer access to HTTP Headers. Instead of request.env["HTTP_REFERRER"] you can now use request.headers["Referrer"]. *Michael Koziarski*
+
+* UrlWriter respects relative_url_root. #10748 *Cheah Chu Yeow*
+
+* The asset_host block takes the controller request as an optional second argument. Example: use a single asset host for SSL requests. #10549 *Cheah Chu Yeow, Peter B, Tom Taylor*
+
+* Support render :text => nil. #6684 *tjennings, PotatoSalad, Cheah Chu Yeow*
+
+* assert_response failures include the exception message. #10688 *Seth Rasmussen*
+
+* All fragment cache keys are now by default prefixed with the "views/" namespace *David Heinemeier Hansson*
+
+* Moved the caching stores from ActionController::Caching::Fragments::* to ActiveSupport::Cache::*. If you're explicitly referring to a store, like ActionController::Caching::Fragments::MemoryStore, you need to update that reference with ActiveSupport::Cache::MemoryStore *David Heinemeier Hansson*
+
+* Deprecated ActionController::Base.fragment_cache_store for ActionController::Base.cache_store *David Heinemeier Hansson*
+
+* Made fragment caching in views work for rjs and builder as well #6642 *Dee Zsombor*
+
+* Fixed rendering of partials with layout when done from site layout #9209 *antramm*
+
+* Fix atom_feed_helper to comply with the atom spec. Closes #10672 *Xavier Shay*
+
+ * The tags created do not contain a date (http://feedvalidator.org/docs/error/InvalidTAG.html)
+ * IDs are not guaranteed unique
+ * A default self link was not provided, contrary to the documentation
+ * NOTE: This changes tags for existing atom entries, but at least they validate now.
+
+* Correct indentation in tests. Closes #10671 *Luca Guidi*
+
+* Fix that auto_link looks for ='s in url paths (Amazon urls have them). Closes #10640 *Brad Greenlee*
+
+* Ensure that test case setup is run even if overridden. #10382 *Josh Peek*
+
+* Fix HTML Sanitizer to allow trailing spaces in CSS style attributes. Closes #10566 *wesley.moxam*
+
+* Add :default option to time_zone_select. #10590 *Matt Aimonetti*
+
+
+## 2.0.2 (December 16th, 2007) ##
+
+* Added delete_via_redirect and put_via_redirect to integration testing #10497 *philodespotos*
+
+* Allow headers['Accept'] to be set by hand when calling xml_http_request #10461 *BMorearty*
+
+* Added OPTIONS to list of default accepted HTTP methods #10449 *holoway*
+
+* Added option to pass proc to ActionController::Base.asset_host for maximum configurability #10521 *Cheah Chu Yeow*. Example:
+
+ ActionController::Base.asset_host = Proc.new { |source|
+ if source.starts_with?('/images')
+ "http://images.example.com"
+ else
+ "http://assets.example.com"
+ end
+ }
+
+* Fixed that ActionView#file_exists? would be incorrect if @first_render is set #10569 *dbussink*
+
+* Added that Array#to_param calls to_param on all it's elements #10473 *brandon*
+
+* Ensure asset cache directories are automatically created. #10337 *Josh Peek, Cheah Chu Yeow*
+
+* render :xml and :json preserve custom content types. #10388 *jmettraux, Cheah Chu Yeow*
+
+* Refactor Action View template handlers. #10437, #10455 *Josh Peek*
+
+* Fix DoubleRenderError message and leave out mention of returning false from filters. Closes #10380 *Frederick Cheung*
+
+* Clean up some cruft around ActionController::Base#head. Closes #10417 *ssoroka*
+
+
+## 2.0.1 (December 7th, 2007) ##
+
+* Fixed send_file/binary_content for testing #8044 *tolsen*
+
+* When a NonInferrableControllerError is raised, make the proposed fix clearer in the error message. Closes #10199 *Jack Danger Canty*
+
+* Update Prototype to 1.6.0.1. *sam*
+
+* Update script.aculo.us to 1.8.0.1. *madrobby*
+
+* Add 'disabled' attribute to <OPTION> separators used in time zone and country selects. Closes #10354 *Josh Susser*
+
+* Added the same record identification guessing rules to fields_for as form_for has *David Heinemeier Hansson*
+
+* Fixed that verification violations with no specified action didn't halt the chain (now they do with a 400 Bad Request) *David Heinemeier Hansson*
+
+* Raise UnknownHttpMethod exception for unknown HTTP methods. Closes #10303 *Tarmo Tänav*
+
+* Update to Prototype -r8232. *sam*
+
+* Make sure the optimisation code for routes doesn't get used if :host, :anchor or :port are provided in the hash arguments. *pager, Michael Koziarski* #10292
+
+* Added protection from trailing slashes on page caching #10229 *devrieda*
+
+* Asset timestamps are appended, not prepended. Closes #10276 *Mike Naberezny*
+
+* Minor inconsistency in description of render example. Closes #10029 *ScottSchram*
+
+* Add #prepend_view_path and #append_view_path instance methods on ActionController::Base for consistency with the class methods. *Rick Olson*
+
+* Refactor sanitizer helpers into HTML classes and make it easy to swap them out with custom implementations. Closes #10129. *Rick Olson*
+
+* Add deprecation for old subtemplate syntax for ActionMailer templates, use render :partial *Rick Olson*
+
+* Fix TemplateError so it doesn't bomb on exceptions while running tests *Rick Olson*
+
+* Fixed that named routes living under resources shouldn't have double slashes #10198 *Isaac Feliu*
+
+* Make sure that cookie sessions use a secret that is at least 30 chars in length. *Michael Koziarski*
+
+* Fixed that partial rendering should look at the type of the first render to determine its own type if no other clues are available (like when using text.plain.erb as the extension in AM) #10130 *java*
+
+* Fixed that has_many :through associations should render as collections too #9051 *mathie/Jack Danger Canty*
+
+* Added :mouseover short-cut to AssetTagHelper#image_tag for doing easy image swaps #6893 *joost*
+
+* Fixed handling of non-domain hosts #9479 *purp*
+
+* Fix syntax error in documentation example for cycle method. Closes #8735 *foca*
+
+* Document :with option for link_to_remote. Closes #8765 *Ryan Bates*
+
+* Document :minute_step option for time_select. Closes #8814 *brupm*
+
+* Explain how to use the :href option for link_to_remote to degrade gracefully in the absence of JavaScript. Closes #8911 *vlad*
+
+* Disambiguate :size option for text area tag. Closes #8955 *redbeard*
+
+* Fix broken tag in assert_tag documentation. Closes #9037 *mfazekas*
+
+* Add documentation for route conditions. Closes #9041 *innu, Manfred Stienstra*
+
+* Fix typo left over from previous typo fix in url helper. Closes #9414 *Henrik N*
+
+* Fixed that ActionController::CgiRequest#host_with_port() should handle standard port #10082 *moro*
+
+* Update Prototype to 1.6.0 and script.aculo.us to 1.8.0. *sam, madrobby*
+
+* Expose the cookie jar as a helper method (before the view would just get the raw cookie hash) *David Heinemeier Hansson*
+
+* Integration tests: get_ and post_via_redirect take a headers hash. #9130 *simonjefford*
+
+* Simplfy #view_paths implementation. ActionView templates get the exact object, not a dup. *Rick Olson*
+
+* Update tests for ActiveSupport's JSON escaping change. *Rick Olson*
+
+* FormHelper's auto_index should use #to_param instead of #id_before_type_cast. Closes #9994 *mattly*
+
+* Doc typo fixes for ActiveRecordHelper. Closes #9973 *mikong*
+
+* Make example parameters in restful routing docs idiomatic. Closes #9993 *Jack Danger Canty*
+
+* Make documentation comment for mime responders match documentation example. Closes #9357 *yon*
+
+* Introduce a new test case class for functional tests. ActionController::TestCase. *Michael Koziarski*
+
+* Fix incorrect path in helper rdoc. Closes #9926 *viktor tron*
+
+* Partials also set 'object' to the default partial variable. #8823 *Nick Retallack, Jeremy Kemper*
+
+* Request profiler. *Jeremy Kemper*
+ $ cat login_session.rb
+ get_with_redirect '/'
+ say "GET / => #{path}"
+ post_with_redirect '/sessions', :username => 'john', :password => 'doe'
+ say "POST /sessions => #{path}"
+ $ ./script/performance/request -n 10 login_session.rb
+
+* Disabled checkboxes don't submit a form value. #9301 *vladr, robinjfisher*
+
+* Added tests for options to ActiveRecordHelper#form. Closes #7213 *richcollins, mikong, Mislav Marohnić*
+
+* Changed before_filter halting to happen automatically on render or redirect but no longer on simply returning false *David Heinemeier Hansson*
+
+* Ensure that cookies handle array values correctly. Closes #9937 *queso*
+
+* Make sure resource routes don't clash with internal helpers like javascript_path, image_path etc. #9928 *Geoff Buesing*
+
+* caches_page uses a single after_filter instead of one per action. #9891 *Pratik Naik*
+
+* Update Prototype to 1.6.0_rc1 and script.aculo.us to 1.8.0 preview 0. *sam, madrobby*
+
+* Dispatcher: fix that to_prepare should only run once in production. #9889 *Nathaniel Talbott*
+
+* Memcached sessions: add session data on initialization; don't silently discard exceptions; add unit tests. #9823 *kamk*
+
+* error_messages_for also takes :message and :header_message options which defaults to the old "There were problems with the following fields:" and "<count> errors prohibited this <object_name> from being saved". #8270 *rmm5t, zach-inglis-lt3*
+
+* Make sure that custom inflections are picked up by map.resources. #9815 *Mislav Marohnić*
+
+* Changed SanitizeHelper#sanitize to only allow the custom attributes and tags when specified in the call *David Heinemeier Hansson*
+
+* Extracted sanitization methods from TextHelper to SanitizeHelper *David Heinemeier Hansson*
+
+* rescue_from accepts :with => lambda { |exception| ... } or a normal block. #9827 *Pratik Naik*
+
+* Add :status to redirect_to allowing users to choose their own response code without manually setting headers. #8297 *Coda Hale, chasgrundy*
+
+* Add link_to :back which uses your referrer with a fallback to a javascript link. #7366 *eventualbuddha, Tarmo Tänav*
+
+* error_messages_for and friends also work with local variables. #9699 *Frederick Cheung*
+
+* Fix url_for, redirect_to, etc. with :controller => :symbol instead of 'string'. #8562, #9525 *Justin Lynn, Tarmo Tänav, shoe*
+
+* Use #require_library_or_gem to load the memcache library for the MemCache session and fragment cache stores. Closes #8662. *Rick Olson*
+
+* Move ActionController::Routing.optimise_named_routes to ActionController::Base.optimise_named_routes. Now you can set it in the config. *Rick Olson*
+
+ config.action_controller.optimise_named_routes = false
+
+* ActionController::Routing::DynamicSegment#interpolation_chunk should call #to_s on all values before calling URI.escape. *Rick Olson*
+
+* Only accept session ids from cookies, prevents session fixation attacks. *bradediger*
+
+
+## 2.0.0 Preview Release (September 29th, 2007) Includes duplicates of changes from 1.12.2 - 1.13.3 ##
+
+* Fixed that render template did not honor exempt_from_layout #9698 *pezra*
+
+* Better error messages if you leave out the :secret option for request forgery protection. Closes #9670 *Rick Olson*
+
+* Allow ability to disable request forgery protection, disable it in test mode by default. Closes #9693 *Pratik Naik*
+
+* Avoid calling is_missing on LoadErrors. Closes #7460. *ntalbott*
+
+* Move Railties' Dispatcher to ActionController::Dispatcher, introduce before_ and after_dispatch callbacks, and warm up to non-CGI requests. *Jeremy Kemper*
+
+* The tag helper may bypass escaping. *Jeremy Kemper*
+
+* Cache asset ids. *Jeremy Kemper*
+
+* Optimized named routes respect AbstractRequest.relative_url_root. #9612 *Daniel Morrison, Jeremy Kemper*
+
+* Introduce ActionController::Base.rescue_from to declare exception-handling methods. Cleaner style than the case-heavy rescue_action_in_public. #9449 *Norbert Crombach*
+
+* Rename some RequestForgeryProtection methods. The class method is now #protect_from_forgery, and the default parameter is now 'authenticity_token'. *Rick Olson*
+
+* Merge csrf_killer plugin into rails. Adds RequestForgeryProtection model that verifies session-specific _tokens for non-GET requests. *Rick Olson*
+
+* Secure #sanitize, #strip_tags, and #strip_links helpers against xss attacks. Closes #8877. *Rick Olson, Pratik Naik, Jacques Distler*
+
+ This merges and renames the popular white_list helper (along with some css sanitizing from Jacques Distler version of the same plugin).
+ Also applied updated versions of #strip_tags and #strip_links from #8877.
+
+* Remove use of & logic operator. Closes #8114. *watson*
+
+* Fixed JavaScriptHelper#escape_javascript to also escape closing tags #8023 *Ruy Asan*
+
+* Fixed TextHelper#word_wrap for multiline strings with extra carrier returns #8663 *seth*
+
+* Fixed that setting the :host option in url_for would automatically turn off :only_path (since :host would otherwise not be shown) #9586 *Bounga*
+
+* Added FormHelper#label. #8641, #9850 *jcoglan, Jarkko Laine*
+
+* Added AtomFeedHelper (slightly improved from the atom_feed_helper plugin) *David Heinemeier Hansson*
+
+* Prevent errors when generating routes for uncountable resources, (i.e. sheep where plural == singluar). map.resources :sheep now creates sheep_index_url for the collection and sheep_url for the specific item. *Michael Koziarski*
+
+* Added support for HTTP Only cookies (works in IE6+ and FF 2.0.5+) as an improvement for XSS attacks #8895 *Pratik Naik, Mark Somerville*
+
+* Don't warn when a path segment precedes a required segment. Closes #9615. *Nicholas Seckar*
+
+* Fixed CaptureHelper#content_for to work with the optional content parameter instead of just the block #9434 *sandofsky/wildchild*.
+
+* Added Mime::Type.register_alias for dealing with different formats using the same mime type *DHH*. Example:
+
+ class PostsController < ApplicationController
+ before_filter :adjust_format_for_iphone
+
+ def index
+ @posts = Post.find(:all)
+
+ respond_to do |format|
+ format.html # => renders index.html.erb and uses "text/html" as the content type
+ format.iphone # => renders index.iphone.erb and uses "text/html" as the content type
+ end
+ end
+
+
+ private
+ def adjust_format_for_iphone
+ if request.env["HTTP_USER_AGENT"] && request.env["HTTP_USER_AGENT"][/iPhone/]
+ request.format = :iphone
+ end
+ end
+ end
+
+* Added that render :json will automatically call .to_json unless it's being passed a string *DHH*.
+
+* Autolink behaves well with emails embedded in URLs. #7313 *Jeremy McAnally, Tarmo Tänav*
+
+* Fixed that default layouts did not take the format into account #9564 *Pratik Naik*
+
+* Fixed optimized route segment escaping. #9562 *wildchild, Jeremy Kemper*
+
+* Added block acceptance to JavaScriptHelper#javascript_tag. #7527 *Bob Silva, Tarmo Tänav, rmm5t*
+
+* root_path returns '/' not ''. #9563 *Pratik Naik*
+
+* Fixed that setting request.format should also affect respond_to blocks *David Heinemeier Hansson*
+
+* Add option to force binary mode on tempfile used for fixture_file_upload. #6380 *Jonathan Viney*
+
+* Fixed that resource namespaces wouldn't stick to all nested resources #9399 *pixeltrix*
+
+* Moved ActionController::Macros::AutoComplete into the auto_complete plugin on the official Rails svn. #9512 *Pratik Naik*
+
+* Moved ActionController::Macros::InPlaceEditing into the in_place_editor plugin on the official Rails svn. #9513 *Pratik Naik*
+
+* Removed deprecated form of calling xml_http_request/xhr without the first argument being the http verb *David Heinemeier Hansson*
+
+* Removed deprecated methods *DHH*:
+
+ - ActionController::Base#keep_flash (use flash.keep instead)
+ - ActionController::Base#expire_matched_fragments (just call expire_fragment with a regular expression)
+ - ActionController::Base.template_root/= methods (use ActionController#Base.view_paths/= instead)
+ - ActionController::Base.cookie (use ActionController#Base.cookies[]= instead)
+
+* Removed the deprecated behavior of appending ".png" to image_tag/image_path calls without an existing extension *David Heinemeier Hansson*
+
+* Removed ActionController::Base.scaffold -- it went through the whole idea of scaffolding (card board walls you remove and tweak one by one). Use the scaffold generator instead (it does resources too now!) *David Heinemeier Hansson*
+
+* Optimise named route generation when using positional arguments. *Michael Koziarski*
+
+ This change delivers significant performance benefits for the most
+ common usage scenarios for modern rails applications by avoiding the
+ costly trip through url_for. Initial benchmarks indicate this is
+ between 6 and 20 times as fast.
+
+* Explicitly require active_record/query_cache before using it. *Jeremy Kemper*
+
+* Fix layout overriding response status. #9476 *lotswholetime*
+
+* Add field_set_tag for generating field_sets, closes #9477. *Damian Janowski*
+
+* Allow additional parameters to be passed to named route helpers when using positional arguments. Closes #8930 *Ian White*
+
+* Make render :partial work with a :collection of Hashes, previously this wasn't possible due to backwards compatibility restrictions. *Pratik Naik*
+
+* request.host works with IPv6 addresses. #9458 *yuya*
+
+* Fix bug where action caching sets the content type to the ActionCachePath object. Closes #9282 *mindforge*
+
+* Find layouts even if they're not in the first view_paths directory. Closes #9258 *caio*
+
+* Major improvement to the documentation for the options / select form helpers. Closes #9038 *Chris Kampmeier, jardeon, wesg*
+
+* Fix number_to_human_size when using different precisions. Closes #7536. *RichardStrand, mpalmer*
+
+* Added partial layouts (see example in action_view/lib/partials.rb) *David Heinemeier Hansson*
+
+* Allow you to set custom :conditions on resource routes. *Rick Olson*
+
+* Fixed that file.content_type for uploaded files would include a trailing \r #9053 *Brad Greenlee*
+
+* url_for now accepts a series of symbols representing the namespace of the record *Josh Knowles*
+
+* Make :trailing_slash work with query parameters for url_for. Closes #4004 *nov*
+
+* Make sure missing template exceptions actually say which template they were looking for. Closes #8683 *dasil003*
+
+* Fix errors with around_filters which do not yield, restore 1.1 behaviour with after filters. Closes #8891 *Stefan Kaes*
+
+ After filters will *no longer* be run if an around_filter fails to yield, users relying on
+ this behaviour are advised to put the code in question after a yield statement in an around filter.
+
+
+* Allow you to delete cookies with options. Closes #3685 *Josh Peek, Chris Wanstrath*
+
+* Allow you to render views with periods in the name. Closes #8076 *Norbert Crombach*
+
+ render :partial => 'show.html.erb'
+
+* Improve capture helper documentation. #8796 *Chris Kampmeier*
+
+* Prefix nested resource named routes with their action name, e.g. new_group_user_path(@group) instead of group_new_user_path(@group). The old nested action named route is deprecated in Rails 1.2.4. #8558 *David Chelimsky*
+
+* Allow sweepers to be created solely for expiring after controller actions, not model changes *David Heinemeier Hansson*
+
+* Added assigns method to ActionController::Caching::Sweeper to easily access instance variables on the controller *David Heinemeier Hansson*
+
+* Give the legacy X-POST_DATA_FORMAT header greater precedence during params parsing for backward compatibility. *Jeremy Kemper*
+
+* Fixed that link_to with an href of # when using :method will not allow for click-through without JavaScript #7037 *Steven Bristol, Josh Peek*
+
+* Fixed that radio_button_tag should generate unique ids #3353 *Bob Silva, Rebecca, Josh Peek*
+
+* Fixed that HTTP authentication should work if the header is called REDIRECT_X_HTTP_AUTHORIZATION as well #6754 *Mislav Marohnić*
+
+* Don't mistakenly interpret the request uri as the query string. #8731 *Pratik Naik, Jeremy Kemper*
+
+* Make ActionView#view_paths an attr_accessor for real this time. Also, don't perform an unnecessary #compact on the @view_paths array in #initialize. Closes #8582 *dasil003, julik, Rick Olson*
+
+* Tolerate missing content type on multipart file uploads. Fix for Safari 3. *Jeremy Kemper*
+
+* Deprecation: remove pagination. Install the classic_pagination plugin for forward compatibility, or move to the superior will_paginate plugin. #8157 *Josh Peek*
+
+* Action caching is limited to GET requests returning 200 OK status. #3335 *tom@craz8.com, halfbyte, Dan Kubb, Josh Peek*
+
+* Improve Text Helper test coverage. #7274 *Rob Sanheim, Josh Peek*
+
+* Improve helper test coverage. #7208, #7212, #7215, #7233, #7234, #7235, #7236, #7237, #7238, #7241, #7243, #7244 *Rich Collins, Josh Peek*
+
+* Improve UrlRewriter tests. #7207 *Rich Collins*
+
+* Resources: url_for([parent, child]) generates /parents/1/children/2 for the nested resource. Likewise with the other simply helpful methods like form_for and link_to. #6432 *mhw, Jonathan Vaught, lotswholetime*
+
+* Assume html format when rendering partials in RJS. #8076 *Rick Olson*
+
+* Don't double-escape url_for in views. #8144 *Rich Collins, Josh Peek*
+
+* Allow JSON-style values for the :with option of observe_field. Closes #8557 *kommen*
+
+* Remove RAILS_ROOT from backtrace paths. #8540 *Tim Pope*
+
+* Routing: map.resource :logo routes to LogosController so the controller may be reused for multiple nestings or namespaces. *Jeremy Kemper*
+
+* render :partial recognizes Active Record associations as Arrays. #8538 *Kamal Fariz Mahyuddin*
+
+* Routing: drop semicolon and comma as route separators. *Jeremy Kemper*
+
+* request.remote_ip understands X-Forwarded-For addresses with nonstandard whitespace. #7386 *moses*
+
+* Don't prepare response when rendering a component. #8493 *jsierles*
+
+* Reduce file stat calls when checking for template changes. #7736 *alex*
+
+* Added custom path cache_page/expire_page parameters in addition to the options hashes *DHH*. Example:
+
+ def index
+ caches_page(response.body, "/index.html")
+ end
+
+* Action Caching speedup. #8231 *Stefan Kaes*
+
+* Wordsmith resources documentation. #8484 *marclove*
+
+* Fix syntax error in code example for routing documentation. #8377. *Norbert Crombach*
+
+* Routing: respond with 405 Method Not Allowed status when the route path matches but the HTTP method does not. #6953 *Josh Peek, defeated, Dan Kubb, Coda Hale*
+
+* Add support for assert_select_rjs with :show and :hide. #7780 *dchelimsky*
+
+* Make assert_select's failure messages clearer about what failed. #7779 *dchelimsky*
+
+* Introduce a default respond_to block for custom types. #8174 *Josh Peek*
+
+* auto_complete_field takes a :method option so you can GET or POST. #8120 *zapnap*
+
+* Added option to suppress :size when using :maxlength for FormTagHelper#text_field #3112 *Tim Pope*
+
+* catch possible WSOD when trying to render a missing partial. Closes #8454 *Jonathan del Strother*
+
+* Rewind request body after reading it, if possible. #8438 *s450r1*
+
+* Resource namespaces are inherited by their has_many subresources. #8280 *marclove, Geoff Garside*
+
+* Fix filtered parameter logging with nil parameter values. #8422 *choonkeat*
+
+* Integration tests: alias xhr to xml_http_request and add a request_method argument instead of always using POST. #7124 *Nik Wakelin, François Beausoleil, Wizard*
+
+* Document caches_action. #5419 *Jarkko Laine*
+
+* Update to Prototype 1.5.1. *Sam Stephenson*
+
+* Allow routes to be decalred under namespaces *Tobias Lütke*:
+
+ map.namespace :admin do |admin|
+ admin.root :controller => "products"
+ admin.feed 'feed.xml', :controller => 'products', :action => 'feed', :format => 'xml'
+ end
+
+* Update to script.aculo.us 1.7.1_beta3. *Thomas Fuchs*
+
+* observe_form always sends the serialized form. #5271 *Manfred Stienstra, normelton@gmail.com*
+
+* Parse url-encoded and multipart requests ourselves instead of delegating to CGI. *Jeremy Kemper*
+
+* select :include_blank option can be set to a string instead of true, which just uses an empty string. #7664 *Wizard*
+
+* Added url_for usage on render :location, which allows for record identification *DHH*. Example:
+
+ render :xml => person, :status => :created, :location => person
+
+ ...expands the location to person_url(person).
+
+* Introduce the request.body stream. Lazy-read to parse parameters rather than always setting RAW_POST_DATA. Reduces the memory footprint of large binary PUT requests. *Jeremy Kemper*
+
+* Add some performance enhancements to ActionView.
+
+ * Cache base_paths in @@cached_base_paths
+ * Cache template extensions in @@cached_template_extension
+ * Remove unnecessary rescues
+
+* Assume that rendered partials go by the HTML format by default
+
+ def my_partial
+ render :update do |page|
+ # in this order
+ # _foo.html.erb
+ # _foo.erb
+ # _foo.rhtml
+ page.replace :foo, :partial => 'foo'
+ end
+ end
+
+* Added record identifications to FormHelper#form_for and PrototypeHelper#remote_form_for *DHH*. Examples:
+
+ <% form_for(@post) do |f| %>
+ ...
+ <% end %>
+
+ This will expand to be the same as:
+
+ <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
+ ...
+ <% end %>
+
+ And for new records:
+
+ <% form_for(Post.new) do |f| %>
+ ...
+ <% end %>
+
+ This will expand to be the same as:
+
+ <% form_for :post, @post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
+ ...
+ <% end %>
+
+* Rationalize route path escaping according to RFC 2396 section 3.3. #7544, #8307. *Jeremy Kemper, Chris Roos, begemot, jugend*
+
+* Added record identification with polymorphic routes for ActionController::Base#url_for and ActionView::Base#url_for *DHH*. Examples:
+
+ redirect_to(post) # => redirect_to(posts_url(post)) => Location: http://example.com/posts/1
+ link_to(post.title, post) # => link_to(post.title, posts_url(post)) => <a href="/posts/1">Hello world</a>
+
+ Any method that calls url_for on its parameters will automatically benefit from this.
+
+* Removed deprecated parameters_for_method_reference concept (legacy from before named routes) *David Heinemeier Hansson*
+
+* Add ActionController::Routing::Helpers, a module to contain common URL helpers such as polymorphic_url. *Nicholas Seckar*
+
+* Included the HttpAuthentication plugin as part of core (ActionController::HttpAuthentication::Basic) *David Heinemeier Hansson*
+
+* Modernize documentation for form helpers. *Jeremy McAnally*
+
+* Add brief introduction to REST to the resources documentation. *fearoffish*
+
+* Fix various documentation typos throughout ActionPack. *Henrik N*
+
+* Enhance documentation and add examples for url_for. *Jeremy McAnally*
+
+* Fix documentation typo in routes. *Norbert Crombach, pam*
+
+* Sweep flash when filter chain is halted. *Caio Chassot <lists@v2studio.com>*
+
+* Fixed that content_tag with a block will just return the result instead of concate it if not used in a ERb view #7857, #7432 *michael.niessner*
+
+* Replace the current block/continuation filter chain handling by an implementation based on a simple loop. #8226 *Stefan Kaes*
+
+* Update UrlWriter to accept :anchor parameter. Closes #6771. *Chris McGrath*
+
+* Added RecordTagHelper for using RecordIdentifier conventions on divs and other container elements *DHH*. Example:
+
+ <% div_for(post) do %> <div id="post_45" class="post">
+ <%= post.body %> What a wonderful world!
+ <% end %> </div>
+
+* Added page[record] accessor to JavaScriptGenerator that relies on RecordIdentifier to find the right dom id *DHH*. Example:
+
+ format.js do
+ # Calls: new Effect.fade('post_45');
+ render(:update) { |page| page[post].visual_effect(:fade) }
+ end
+
+* Added RecordIdentifier to enforce view conventions on records for dom ids, classes, and partial paths *David Heinemeier Hansson*
+
+* Added map.namespace to deal with the common situation of admin sections and the like *David Heinemeier Hansson*
+
+ Before:
+
+ map.resources :products, :path_prefix => "admin", :controller => "admin/products", :collection => { :inventory => :get }, :member => { :duplicate => :post }
+ map.resources :tags, :name_prefix => 'admin_product_', :path_prefix => "admin/products/:product_id", :controller => "admin/product_tags"
+ map.resources :images, :name_prefix => 'admin_product_', :path_prefix => "admin/products/:product_id", :controller => "admin/product_images"
+ map.resources :variants, :name_prefix => 'admin_product_', :path_prefix => "admin/products/:product_id", :controller => "admin/product_variants"
+
+ After:
+
+ map.namespace(:admin) do |admin|
+ admin.resources :products,
+ :collection => { :inventory => :get },
+ :member => { :duplicate => :post },
+ :has_many => [ :tags, :images, :variants ]
+ end
+
+* Added :name_prefix as standard for nested resources *DHH*. WARNING: May be backwards incompatible with your app
+
+ Before:
+
+ map.resources :emails do |emails|
+ emails.resources :comments, :name_prefix => "email_"
+ emails.resources :attachments, :name_prefix => "email_"
+ end
+
+ After:
+
+ map.resources :emails do |emails|
+ emails.resources :comments
+ emails.resources :attachments
+ end
+
+ This does mean that if you intended to have comments_url go to /emails/5/comments, then you'll have to set :name_prefix to nil explicitly.
+
+* Added :has_many and :has_one for declaring plural and singular resources beneath the current *David Heinemeier Hansson*
+
+ Before:
+
+ map.resources :notes do |notes|
+ notes.resources :comments
+ notes.resources :attachments
+ notes.resource :author
+ end
+
+ After:
+
+ map.resources :notes, :has_many => [ :comments, :attachments ], :has_one => :author
+
+* Added that render :xml will try to call to_xml if it can *DHH*. Makes these work:
+
+ render :xml => post
+ render :xml => comments
+
+* Added :location option to render so that the common pattern of rendering a response after creating a new resource is now a 1-liner *David Heinemeier Hansson*
+
+ render :xml => post.to_xml, :status => :created, :location => post_url(post)
+
+* Ensure that render_text only adds string content to the body of the response *David Heinemeier Hansson*
+
+* Return the string representation from an Xml Builder when rendering a partial. Closes #5044 *Tim Pope*
+
+* Fixed that parameters from XML should also be presented in a hash with indifferent access *David Heinemeier Hansson*
+
+* Tweak template format rules so that the ACCEPT header is only used if it's text/javascript. This is so ajax actions without a :format param get recognized as Mime::JS. *Rick Olson*
+
+* The default respond_to blocks don't set a specific extension anymore, so that both 'show.rjs' and 'show.js.rjs' will work. *Rick Olson*
+
+* Allow layouts with extension of .html.erb. Closes #8032 *Josh Knowles*
+
+* Change default respond_to templates for xml and rjs formats. *Rick Olson*
+
+ * Default xml template goes from #{action_name}.rxml => #{action_name}.xml.builder.
+ * Default rjs template goes from #{action_name}.rjs => #{action_name}.js.rjs.
+
+ You can still specify your old templates:
+
+ respond_to do |format|
+ format.xml do
+ render :action => "#{action_name}.rxml"
+ end
+ end
+
+* Fix WSOD due to modification of a formatted template extension so that requests to templates like 'foo.html.erb' fail on the second hit. *Rick Olson*
+
+* Fix WSOD when template compilation fails *Rick Olson*
+
+* Change ActionView template defaults. Look for templates using the request format first, such as "show.html.erb" or "show.xml.builder", before looking for the old defaults like "show.erb" or "show.builder" *Rick Olson*
+
+* Highlight helper highlights one or many terms in a single pass. *Jeremy Kemper*
+
+* Dropped the use of ; as a separator of non-crud actions on resources and went back to the vanilla slash. It was a neat idea, but lots of the non-crud actions turned out not to be RPC (as the ; was primarily intended to discourage), but legitimate sub-resources, like /parties/recent, which didn't deserve the uglification of /parties;recent. Further more, the semicolon caused issues with caching and HTTP authentication in Safari. Just Not Worth It *David Heinemeier Hansson*
+
+* Added that FormTagHelper#submit_tag will return to its original state if the submit fails and you're using :disable_with *David Heinemeier Hansson*
+
+* Cleaned up, corrected, and mildly expanded ActionPack documentation. Closes #7190 *Jeremy McAnally*
+
+* Small collection of ActionController documentation cleanups. Closes #7319 *Jeremy McAnally*
+
+* Make sure the route expiry hash is constructed by comparing the to_param-ized values of each hash. *Jamis Buck*
+
+* Allow configuration of the default action cache path for #caches_action calls. *Rick Olson*
+
+ class ListsController < ApplicationController
+ caches_action :index, :cache_path => Proc.new { |controller|
+ controller.params[:user_id] ?
+ controller.send(:user_lists_url, c.params[:user_id]) :
+ controller.send(:lists_url) }
+ end
+
+* Performance: patch cgi/session/pstore to require digest/md5 once rather than per #initialize. #7583 *Stefan Kaes*
+
+* Cookie session store: ensure that new sessions doesn't reuse data from a deleted session in the same request. *Jeremy Kemper*
+
+* Deprecation: verification with :redirect_to => :named_route shouldn't be deprecated. #7525 *Justin French*
+
+* Cookie session store: raise ArgumentError when :session_key is blank. *Jeremy Kemper*
+
+* Deprecation: remove deprecated request, redirect, and dependency methods. Remove deprecated instance variables. Remove deprecated url_for(:symbol, *args) and redirect_to(:symbol, *args) in favor of named routes. Remove uses_component_template_root for toplevel components directory. Privatize deprecated render_partial and render_partial_collection view methods. Remove deprecated link_to_image, link_image_to, update_element_function, start_form_tag, and end_form_tag helper methods. Remove deprecated human_size helper alias. *Jeremy Kemper*
+
+* Consistent public/protected/private visibility for chained methods. #7813 *Dan Manges*
+
+* Prefer MIME constants to strings. #7707 *Dan Kubb*
+
+* Allow array and hash query parameters. Array route parameters are converted/to/a/path as before. #6765, #7047, #7462 *bgipsy, Jeremy McAnally, Dan Kubb, brendan*
+
+ \# Add a #dbman attr_reader for CGI::Session and make CGI::Session::CookieStore#generate_digest public so it's easy to generate digests using the cookie store's secret. *Rick Olson*
+* Added Request#url that returns the complete URL used for the request *David Heinemeier Hansson*
+
+* Extract dynamic scaffolding into a plugin. #7700 *Josh Peek*
+
+* Added user/password options for url_for to add http authentication in a URL *David Heinemeier Hansson*
+
+* Fixed that FormTagHelper#text_area_tag should disregard :size option if it's not a string *Brendon Davidson*
+
+* Set the original button value in an attribute of the button when using the :disable_with key with submit_tag, so that the original can be restored later. *Jamis Buck*
+
+* session_enabled? works with session :off. #6680 *Jonathan del Strother*
+
+* Added :port and :host handling to UrlRewriter (which unified url_for usage, regardless of whether it's called in view or controller) #7616 *alancfrancis*
+
+* Allow send_file/send_data to use a registered mime type as the :type parameter #7620 *jonathan*
+
+* Allow routing requirements on map.resource(s) #7633 *quixoten*. Example:
+
+ map.resources :network_interfaces, :requirements => { :id => /^\d+\.\d+\.\d+\.\d+$/ }
+
+* Cookie session store: empty and unchanged sessions don't write a cookie. *Jeremy Kemper*
+
+* Added helper(:all) as a way to include all helpers from app/helpers/**/*.rb in ApplicationController *David Heinemeier Hansson*
+
+* Integration tests: introduce methods for other HTTP methods. #6353 *caboose*
+
+* Routing: better support for escaped values in route segments. #7544 [Chris
+ Roos]
+* Introduce a cookie-based session store as the Rails default. Sessions typically contain at most a user_id and flash message; both fit within the 4K cookie size limit. A secure message digest is included with the cookie to ensure data integrity (a user cannot alter his user_id without knowing the secret key included in the digest). If you have more than 4K of session data or don't want your data to be visible to the user, pick another session store. Cookie-based sessions are dramatically faster than the alternatives. *Jeremy Kemper*
+
+ Example config/environment.rb:
+ # Use an application-wide secret key and the default SHA1 message digest.
+ config.action_controller.session = { :secret => "can't touch this" }
+
+ # Store a secret key per user and employ a stronger message digest.
+ config.action_controller.session = {
+ :digest => 'SHA512',
+ :secret => Proc.new { User.current.secret_key }
+ }
+
+* Added .erb and .builder as preferred aliases to the now deprecated .rhtml and .rxml extensions *Chad Fowler*. This is done to separate the renderer from the mime type. .erb templates are often used to render emails, atom, csv, whatever. So labeling them .rhtml doesn't make too much sense. The same goes for .rxml, which can be used to build everything from HTML to Atom to whatever. .rhtml and .rxml will continue to work until Rails 3.0, though. So this is a slow phasing out. All generators and examples will start using the new aliases, though.
+
+* Added caching option to AssetTagHelper#stylesheet_link_tag and AssetTagHelper#javascript_include_tag *DHH*. Examples:
+
+ stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
+ <link href="/stylesheets/style1.css" media="screen" rel="Stylesheet" type="text/css" />
+ <link href="/stylesheets/styleB.css" media="screen" rel="Stylesheet" type="text/css" />
+ <link href="/stylesheets/styleX2.css" media="screen" rel="Stylesheet" type="text/css" />
+
+ stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
+ <link href="/stylesheets/all.css" media="screen" rel="Stylesheet" type="text/css" />
+
+ ...when caching is on, all.css is the concatenation of style1.css, styleB.css, and styleX2.css.
+ Same deal for JavaScripts.
+
+* Work around the two connection per host browser limit: use asset%d.myapp.com to distribute asset requests among asset[0123].myapp.com. Use a DNS wildcard or CNAMEs to map these hosts to your asset server. See http://www.die.net/musings/page_load_time/ for background. *Jeremy Kemper*
+
+* Added default mime type for CSS (Mime::CSS) *David Heinemeier Hansson*
+
+* Added that rendering will automatically insert the etag header on 200 OK responses. The etag is calculated using MD5 of the response body. If a request comes in that has a matching etag, the response will be changed to a 304 Not Modified and the response body will be set to an empty string. *David Heinemeier Hansson*
+
+* Added X-Runtime to all responses with the request run time *David Heinemeier Hansson*
+
+* Add Mime::Type convenience methods to check the current mime type. *Rick Olson*
+
+ request.format.html? # => true if Mime::HTML
+ request.format.jpg? # => true if Mime::JPG
+
+ \# ActionController sample usage:
+ \# the session will be disabled for non html/ajax requests
+ session :off, :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
+
+* Performance: patch cgi/session to require digest/md5 once rather than per #create_new_id. *Stefan Kaes*
+
+* Add a :url_based_filename => true option to ActionController::Streaming::send_file, which allows URL-based filenames. *Thomas Fuchs*
+
+* Fix that FormTagHelper#submit_tag using :disable_with should trigger the onsubmit handler of its form if available *David Heinemeier Hansson*
+
+* Fix #render_file so that TemplateError is called with the correct params and you don't get the WSOD. *Rick Olson*
+
+* Fix issue with deprecation messing up #template_root= usage. Add #prepend_view_path and #append_view_path to allow modification of a copy of the
+ superclass' view_paths. *Rick Olson*
+* Allow Controllers to have multiple view_paths instead of a single template_root. Closes #2754 *John Long*
+
+* Add much-needed html-scanner tests. Fixed CDATA parsing bug. *Rick Olson*
+
+* improve error message for Routing for named routes. Closes #7346 *Rob Sanheim*
+
+* Added enhanced docs to routing assertions. Closes #7359 *Rob Sanheim*
+
+* fix form_for example in ActionController::Resources documentation. Closes #7362 *gnarg*
+
+* Make sure that the string returned by TextHelper#truncate is actually a string, not a char proxy -- that should only be used internally while working on a multibyte-safe way of truncating *David Heinemeier Hansson*
+
+* Added FormBuilder#submit as a delegate for FormTagHelper#submit_tag *David Heinemeier Hansson*
+
+* Allow Routes to generate all urls for a set of options by specifying :generate_all => true. Allows caching to properly set or expire all paths for a resource. References #1739. *Nicholas Seckar*
+
+* Change the query parser to map empty GET params to "" rather than nil. Closes #5694. *Nicholas Seckar*
+
+* date_select and datetime_select take a :default option. #7052 *Nik Wakelin*
+ date_select "post", "written_on", :default => 3.days.from_now
+ date_select "credit_card", "bill_due", :default => { :day => 20 }
+
+* select :multiple => true suffixes the attribute name with [] unless already suffixed. #6977 *nik.kakelin, ben, julik*
+
+* Improve routes documentation. #7095 *zackchandler*
+
+* mail_to :encode => 'hex' also encodes the mailto: part of the href attribute as well as the linked email when no name is given. #2061 *Jarkko Laine, pfc.pille@gmx.net*
+
+* Resource member routes require :id, eliminating the ambiguous overlap with collection routes. #7229 *dkubb*
+
+* Remove deprecated assertions. *Jeremy Kemper*
+
+* Change session restoration to allow namespaced models to be autoloaded. Closes #6348. *Nicholas Seckar*
+
+* Fix doubly appearing parameters due to string and symbol mixups. Closes #2551. *Anthony Eden*
+
+* Fix overly greedy rescues when loading helpers. Fixes #6268. *Nicholas Seckar*
+
+* Fixed NumberHelper#number_with_delimiter to use "." always for splitting the original number, not the delimiter parameter #7389 *ceefour*
+
+* Autolinking recognizes trailing and embedded . , : ; #7354 *Jarkko Laine*
+
+* Make TextHelper::auto_link recognize URLs with colons in path correctly, fixes #7268. *imajes*
+
+* Update to script.aculo.us 1.7.0. *Thomas Fuchs*
+
+* Modernize cookie testing code, and increase coverage (Heckle++) #7101 *Kevin Clark*
+
+* Improve Test Coverage for ActionController::Routing::Route#matches_controller_and_action? (Heckle++) #7115 *Kevin Clark*
+
+* Heckling ActionController::Resources::Resource revealed that set_prefixes didn't break when :name_prefix was munged. #7081 *Kevin Clark*
+
+* Fix #distance_of_time_in_words to report accurately against the Duration class. #7114 *eventualbuddha*
+
+* Refactor #form_tag to allow easy extending. *Rick Olson*
+
+* Update to Prototype 1.5.0. *Sam Stephenson*
+
+* RecordInvalid, RecordNotSaved => 422 Unprocessable Entity, StaleObjectError => 409 Conflict. #7097 *dkubb*
+
+* Allow fields_for to be nested inside form_for, so that the name and id get properly constructed *Jamis Buck*
+
+* Allow inGroupsOf and eachSlice to be called through rjs. #7046 *Cody Fauser*
+
+* Allow exempt_from_layout :rhtml. #6742, #7026 *Dan Manges, Squeegy*
+
+* Recognize the .txt extension as Mime::TEXT *Rick Olson*
+
+* Fix parsing of array[] CGI parameters so extra empty values aren't included. #6252 *Nicholas Seckar, aiwilliams, brentrowland*
+
+* link_to_unless_current works with full URLs as well as paths. #6891 *Jarkko Laine, Manfred Stienstra, idrifter*
+
+* Lookup the mime type for #auto_discovery_link_tag in the Mime::Type class. Closes #6941 *Josh Peek*
+
+* Fix bug where nested resources ignore a parent singleton parent's path prefix. Closes #6940 *Dan Kubb*
+
+* Fix no method error with error_messages_on. Closes #6935 *nik.wakelin Koz*
+
+* Slight doc tweak to the ActionView::Helpers::PrototypeHelper#replace docs. Closes #6922 *Steven Bristol*
+
+* Slight doc tweak to #prepend_filter. Closes #6493 *Jeremy Voorhis*
+
+* Add more extensive documentation to the AssetTagHelper. Closes #6452 *Bob Silva*
+
+* Clean up multiple calls to #stringify_keys in TagHelper, add better documentation and testing for TagHelper. Closes #6394 *Bob Silva*
+
+* [DOCS] fix reference to ActionController::Macros::AutoComplete for #text_field_with_auto_complete. Closes #2578 *Jan Prill*
+
+* Make sure html_document is reset between integration test requests. *ctm*
+
+* Set session to an empty hash if :new_session => false and no session cookie or param is present. CGI::Session was raising an unrescued ArgumentError. *Josh Susser*
+
+* Routing uses URI escaping for path components and CGI escaping for query parameters. *darix, Jeremy Kemper*
+
+* Fix assert_redirected_to bug where redirecting from a nested to to a top-level controller incorrectly added the current controller's nesting. Closes #6128. *Rick Olson*
+
+* Singleton resources: POST /singleton => create, GET /singleton/new => new. *Jeremy Kemper*
+
+* Use 400 Bad Request status for unrescued ActiveRecord::RecordInvalid exceptions. *Jeremy Kemper*
+
+* Silence log_error deprecation warnings from inspecting deprecated instance variables. *Nate Wiger*
+
+* Only cache GET requests with a 200 OK response. #6514, #6743 *RSL, anamba*
+
+* Add a 'referer' attribute to TestRequest. *Jamis Buck*
+
+* Ensure render :json => ... skips the layout. Closes #6808 *Josh Peek*
+
+* Fix HTML::Node to output double quotes instead of single quotes. Closes #6845 *mitreandy*
+
+* Correctly report which filter halted the chain. #6699 *Martin Emde*
+
+* Fix a bug in Routing where a parameter taken from the path of the current request could not be used as a query parameter for the next. Closes #6752. *Nicholas Seckar*
+
+* Unrescued ActiveRecord::RecordNotFound responds with 404 instead of 500. *Jeremy Kemper*
+
+* Improved auto_link to match more valid urls correctly *Tobias Lütke*
+
+* Add singleton resources. *Rick Olson*
+
+ map.resource :account
+
+ GET /account
+ GET /account;edit
+ UPDATE /account
+ DELETE /account
+
+* respond_to recognizes JSON. render :json => @person.to_json automatically sets the content type and takes a :callback option to specify a client-side function to call using the rendered JSON as an argument. #4185 *Scott Raymond, eventualbuddha*
+ # application/json response with body 'Element.show({:name: "David"})'
+ respond_to do |format|
+ format.json { render :json => { :name => "David" }.to_json, :callback => 'Element.show' }
+ end
+
+* Makes :discard_year work without breaking multi-attribute parsing in AR. #1260, #3800 *sean@ardismg.com, jmartin@desertflood.com, stephen@touset.org, Bob Silva*
+
+* Adds html id attribute to date helper elements. #1050, #1382 *mortonda@dgrmm.net, David North, Bob Silva*
+
+* Add :index and @auto_index capability to model driven date/time selects. #847, #2655 *moriq, Doug Fales, Bob Silva*
+
+* Add :order to datetime_select, select_datetime, and select_date. #1427 *Timothee Peignier, Patrick Lenz, Bob Silva*
+
+* Added time_select to work with time values in models. Update scaffolding. #2489, #2833 *Justin Palmer, Andre Caum, Bob Silva*
+
+* Added :include_seconds to select_datetime, datetime_select and time_select. #2998 *csn, Bob Silva*
+
+* All date/datetime selects can now accept an array of month names with :use_month_names. Allows for localization. #363 *tomasj, Bob Silva*
+
+* Adds :time_separator to select_time and :date_separator to select_datetime. Preserves BC. #3811 *Bob Silva*
+
+* Added map.root as an alias for map.connect '' *David Heinemeier Hansson*
+
+* Added Request#format to return the format used for the request as a mime type. If no format is specified, the first Request#accepts type is used. This means you can stop using respond_to for anything else than responses *DHH*. Examples:
+
+ GET /posts/5.xml | request.format => Mime::XML
+ GET /posts/5.xhtml | request.format => Mime::HTML
+ GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
+
+* Added the option for extension aliases to mime type registration *DHH*. Example (already in the default routes):
+
+ Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml )
+
+ ...will respond on both .html and .xhtml.
+
+* @response.redirect_url works with 201 Created responses: just return headers['Location'] rather than checking the response status. *Jeremy Kemper*
+
+* Added CSV to Mime::SET so that respond_to csv will work *Cody Fauser*
+
+* Fixed that HEAD should return the proper Content-Length header (that is, actually use @body.size, not just 0) *David Heinemeier Hansson*
+
+* Added GET-masquarading for HEAD, so request.method will return :get even for HEADs. This will help anyone relying on case request.method to automatically work with HEAD and map.resources will also allow HEADs to all GET actions. Rails automatically throws away the response content in a reply to HEAD, so you don't even need to worry about that. If you, for whatever reason, still need to distinguish between GET and HEAD in some edge case, you can use Request#head? and even Request.headers["REQUEST_METHOD"] for get the "real" answer. Closes #6694 *David Heinemeier Hansson*
+
+* Update Routing to complain when :controller is not specified by a route. Closes #6669. *Nicholas Seckar*
+
+* Ensure render_to_string cleans up after itself when an exception is raised. #6658 *Rob Sanheim*
+
+* Extract template_changed_since? from compile_template? so plugins may override its behavior for non-file-based templates. #6651 *Jeff Barczewski*
+
+* Update to Prototype and script.aculo.us [5579]. *Thomas Fuchs*
+
+* simple_format helper doesn't choke on nil. #6644 *jerry426*
+
+* Update to Prototype 1.5.0_rc2 [5550] which makes it work in Opera again *Thomas Fuchs*
+
+* Reuse named route helper module between Routing reloads. Use remove_method to delete named route methods after each load. Since the module is never collected, this fixes a significant memory leak. *Nicholas Seckar*
+
+* ActionView::Base.erb_variable accessor names the buffer variable used to render templates. Defaults to _erbout; use _buf for erubis. *Rick Olson*
+
+* assert_select_rjs :remove. *Dylan Egan*
+
+* Always clear model associations from session. #4795 *sd@notso.net, andylien@gmail.com*
+
+* Update to Prototype 1.5.0_rc2. *Sam Stephenson*
+
+* Remove JavaScriptLiteral in favor of ActiveSupport::JSON::Variable. *Sam Stephenson*
+
+* Sync ActionController::StatusCodes::STATUS_CODES with http://www.iana.org/assignments/http-status-codes. #6586 *dkubb*
+
+* Multipart form values may have a content type without being treated as uploaded files if they do not provide a filename. #6401 *Andreas Schwarz, Jeremy Kemper*
+
+* assert_response supports symbolic status codes. #6569 *Kevin Clark*
+ assert_response :ok
+ assert_response :not_found
+ assert_response :forbidden
+
+* Cache parsed query parameters. #6559 *Stefan Kaes*
+
+* Deprecate JavaScriptHelper#update_element_function, which is superseeded by RJS *Thomas Fuchs*
+
+* pluralize helper interprets nil as zero. #6474 *Tim Pope*
+
+* Fix invalid test fixture exposed by stricter Ruby 1.8.5 multipart parsing. #6524 *Bob Silva*
+
+* Set ActionView::Base.default_form_builder once rather than passing the :builder option to every form or overriding the form helper methods. *Jeremy Kemper*
+
+* Deprecate expire_matched_fragments. Use expire_fragment instead. #6535 *Bob Silva*
+
+* Update to latest Prototype, which doesn't serialize disabled form elements, adds clone() to arrays, empty/non-string Element.update() and adds a fixes excessive error reporting in WebKit beta versions *Thomas Fuchs*
+
+* Deprecate start_form_tag and end_form_tag. Use form_tag / '</form>' from now on. *Rick Olson*
+
+* Added block-usage to PrototypeHelper#form_remote_tag, document block-usage of FormTagHelper#form_tag *Rick Olson*
+
+* Add a 0 margin/padding div around the hidden _method input tag that form_tag outputs. *Rick Olson*
+
+* Added block-usage to TagHelper#content_tag *DHH*. Example:
+
+ <% content_tag :div, :class => "strong" %>
+ Hello world!
+ <% end %>
+
+ Will output:
+ <div class="strong">Hello world!</div>
+
+* Deprecated UrlHelper#link_to_image and UrlHelper#link_to :post => true #6409 *Bob Silva*
+
+* Upgraded NumberHelper with number_to_phone support international formats to comply with ITU E.123 by supporting area codes with less than 3 digits, added precision argument to number_to_human_size (defaults to 1) #6421 *Bob Silva*
+
+* Fixed that setting RAILS_ASSET_ID to "" should not add a trailing slash after assets #6454 *Bob Silva/chrismear*
+
+* Force *_url named routes to show the host in ActionView *Rick Olson*
+
+ <%= url_for ... %> # no host
+ <%= foo_path %> # no host
+ <%= foo_url %> # host!
+
+* Add support for converting blocks into function arguments to JavaScriptGenerator#call and JavaScriptProxy#call. *Sam Stephenson*
+
+* Add JavaScriptGenerator#literal for wrapping a string in an object whose #to_json is the string itself. *Sam Stephenson*
+
+* Add <%= escape_once html %> to escape html while leaving any currently escaped entities alone. Fix button_to double-escaping issue. *Rick Olson*
+
+* Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc. *Rick Olson*
+
+* Fix deprecation warnings when rendering the template error template. *Nicholas Seckar*
+
+* Fix routing to correctly determine when generation fails. Closes #6300. *psross*.
+
+* Fix broken assert_generates when extra keys are being checked. *Jamis Buck*
+
+* Replace KCODE checks with String#chars for truncate. Closes #6385 *Manfred Stienstra*
+
+* Make page caching respect the format of the resource that is being requested even if the current route is the default route so that, e.g. posts.rss is not transformed by url_for to '/' and subsequently cached as '/index.html' when it should be cached as '/posts.rss'. *Marcel Molina Jr.*
+
+* Use String#chars in TextHelper::excerpt. Closes #6386 *Manfred Stienstra*
+
+* Install named routes into ActionView::Base instead of proxying them to the view via helper_method. Closes #5932. *Nicholas Seckar*
+
+* Update to latest Prototype and script.aculo.us trunk versions *Thomas Fuchs*
+
+* Fix relative URL root matching problems. *Mark Imbriaco*
+
+* Fix filter skipping in controller subclasses. #5949, #6297, #6299 *Martin Emde*
+
+* render_text may optionally append to the response body. render_javascript appends by default. This allows you to chain multiple render :update calls by setting @performed_render = false between them (awaiting a better public API). *Jeremy Kemper*
+
+* Rename test assertion to prevent shadowing. Closes #6306. *psross*
+
+* Fixed that NumberHelper#number_to_delimiter should respect precision of higher than two digits #6231 *Philip Hallstrom*
+
+* Fixed that FormHelper#radio_button didn't respect an :id being passed in #6266 *evansj*
+
+* Added an html_options hash parameter to javascript_tag() and update_page_tag() helpers #6311 *tzaharia*. Example:
+
+ update_page_tag :defer => 'true' { |page| ... }
+
+ Gives:
+
+ <script defer="true" type="text/javascript">...</script>
+
+ Which is needed for dealing with the IE6 DOM when it's not yet fully loaded.
+
+* Fixed that rescue template path shouldn't be hardcoded, then it's easier to hook in your own #6295 *Mike Naberezny*
+
+* Fixed escaping of backslashes in JavaScriptHelper#escape_javascript #6302 *sven@c3d2.de*
+
+* Fixed that some 500 rescues would cause 500's themselves because the response had not yet been generated #6329 *cmselmer*
+
+* respond_to :html doesn't assume .rhtml. #6281 *Hampton Catlin*
+
+* Fixed some deprecation warnings in ActionPack *Rick Olson*
+
+* assert_select_rjs decodes escaped unicode chars since the Javascript generators encode them. #6240 *japgolly*
+
+* Deprecation: @cookies, @headers, @request, @response will be removed after 1.2. Use the corresponding method instead. *Jeremy Kemper*
+
+* Make the :status parameter expand to the default message for that status code if it is an integer. Also support symbol statuses. *Jamis Buck*. Examples:
+
+ head :status => 404 # expands to "404 Not Found"
+ head :status => :not_found # expands to "404 Not Found"
+ head :status => :created # expands to "201 Created"
+
+* Add head(options = {}) for responses that have no body. *Jamis Buck*. Examples:
+
+ head :status => 404 # return an empty response with a 404 status
+ head :location => person_path(@person), :status => 201
+
+* Fix bug that kept any before_filter except the first one from being able to halt the before_filter chain. *Rick Olson*
+
+* strip_links is case-insensitive. #6285 *tagoh, Bob Silva*
+
+* Clear the cache of possible controllers whenever Routes are reloaded. *Nicholas Seckar*
+
+* Filters overhaul including meantime filter support using around filters + blocks. #5949 *Martin Emde, Roman Le Negrate, Stefan Kaes, Jeremy Kemper*
+
+* Update RJS render tests. *sam*
+
+* Update CGI process to allow sessions to contain namespaced models. Closes #4638. *dfelstead@site5.com*
+
+* Fix routing to respect user provided requirements and defaults when assigning default routing options (such as :action => 'index'). Closes #5950. *Nicholas Seckar*
+
+* Rescue Errno::ECONNRESET to handle an unexpectedly closed socket connection. Improves SCGI reliability. #3368, #6226 *sdsykes, fhanshaw@vesaria.com*
+
+* Added that respond_to blocks will automatically set the content type to be the same as is requested *DHH*. Examples:
+
+ respond_to do |format|
+ format.html { render :text => "I'm being sent as text/html" }
+ format.rss { render :text => "I'm being sent as application/rss+xml" }
+ format.atom { render :text => "I'm being sent as application/xml", :content_type => Mime::XML }
+ end
+
+* Added utf-8 as the default charset for all renders. You can change this default using ActionController::Base.default_charset=(encoding) *David Heinemeier Hansson*
+
+* Added proper getters and setters for content type and charset *DHH*. Example of what we used to do:
+
+ response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
+
+ ...now:
+
+ response.content_type = Mime::ATOM
+ response.charset = "utf-8"
+
+* Updated prototype.js to 1.5.0_rc1 with latest fixes. *Rick Olson*
+
+ - XPATH support
+ - Make Form.getElements() return elements in the correct order
+ - fix broken Form.serialize return
+
+* Declare file extensions exempt from layouts. #6219 *brandon*
+ Example: ActionController::Base.exempt_from_layout 'rpdf'
+
+* Add chained replace/update support for assert_select_rjs *Rick Olson*
+
+ Given RJS like...
+
+ page['test1'].replace "<div id=\"1\">foo</div>"
+ page['test2'].replace_html "<div id=\"2\">foo</div>"
+
+ Test it with...
+
+ assert_select_rjs :chained_replace
+ assert_select_rjs :chained_replace, "test1"
+
+ assert_select_rjs :chained_replace_html
+ assert_select_rjs :chained_replace_html, "test2"
+
+* Load helpers in alphabetical order for consistency. Resolve cyclic javascript_helper dependency. #6132, #6178 *choonkeat@gmail.com*
+
+* Skip params with empty names, such as the &=Save query string from <input type="submit"/>. #2569 *Manfred Stienstra, raphinou@yahoo.com*
+
+* Fix assert_tag so that :content => "foo" does not match substrings, but only exact strings. Use :content => /foo/ to match substrings. #2799 *Eric Hodel*
+
+* Add descriptive messages to the exceptions thrown by cgi_methods. #6091, #6103 *Nicholas Seckar, Bob Silva*
+
+* Update JavaScriptGenerator#show/hide/toggle/remove to new Prototype syntax for multiple ids, #6068 *petermichaux@gmail.com*
+
+* Update UrlWriter to support :only_path. *Nicholas Seckar, Dave Thomas*
+
+* Fixed JavaScriptHelper#link_to_function and JavaScriptHelper#button_to_function to have the script argument be optional *DHH*. So what used to require a nil, like this:
+
+ link_to("Hider", nil, :class => "hider_link") { |p| p[:something].hide }
+
+ ...can be written like this:
+
+ link_to("Hider", :class => "hider_link") { |p| p[:something].hide }
+
+* Update to script.aculo.us 1.6.3 *Thomas Fuchs*
+
+* Update to Prototype 1.5.0_rc1 *sam*
+
+* Added access to nested attributes in RJS #4548 *richcollins@gmail.com*. 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';
+
+* Fixed that AssetTagHelper#image_tag and others using compute_public_path should not modify the incoming source argument (closes #5102) *eule@space.ch*
+
+* Deprecated the auto-appending of .png to AssetTagHelper#image_tag calls that doesn't have an extension *David Heinemeier Hansson*
+
+* Fixed FormOptionsHelper#select to respect :selected value #5813
+
+* Fixed TextHelper#simple_format to deal with multiple single returns within a single paragraph #5835 *moriq@moriq.com*
+
+* Fixed TextHelper#pluralize to handle 1 as a string #5909 *rails@bencurtis.com*
+
+* Improved resolution of DateHelper#distance_of_time_in_words for better precision #5994 *Bob Silva*
+
+* Changed that uncaught exceptions raised any where in the application will cause RAILS_ROOT/public/500.html to be read and shown instead of just the static "Application error (Rails)" *David Heinemeier Hansson*
+
+* Integration tests: thoroughly test ActionController::Integration::Session. #6022 *Kevin Clark*
+ (tests skipped unless you `gem install mocha`)
+
+* Added deprecation language for pagination which will become a plugin by Rails 2.0 *David Heinemeier Hansson*
+
+* Added deprecation language for in_place_editor and auto_complete_field that both pieces will become plugins by Rails 2.0 *David Heinemeier Hansson*
+
+* Deprecated all of ActionController::Dependencies. All dependency loading is now handled from Active Support *David Heinemeier Hansson*
+
+* Added assert_select* for CSS selector-based testing (deprecates assert_tag) #5936 *assaf.arkin@gmail.com*
+
+* radio_button_tag generates unique id attributes. #3353 *Bob Silva, somekool@gmail.com*
+
+* strip_tags passes through blank args such as nil or "". #2229, #6702 *duncan@whomwah.com, dharana*
+
+* Cleanup assert_tag :children counting. #2181 *jamie@bravenet.com*
+
+* button_to accepts :method so you can PUT and DELETE with it. #6005 *Dan Webb*
+
+* Update sanitize text helper to strip plaintext tags, and <img src="javascript:bang">. *Rick Olson*
+
+* Update routing documentation. Closes #6017 *Nathan Witmer*
+
+* Add routing tests to assert that RoutingError is raised when conditions aren't met. Closes #6016 *Nathan Witmer*
+
+* Deprecation: update docs. #5998 *Jakob Skjerning, Kevin Clark*
+
+* Make auto_link parse a greater subset of valid url formats. *Jamis Buck*
+
+* Integration tests: headers beginning with X aren't excluded from the HTTP_ prefix, so X-Requested-With becomes HTTP_X_REQUESTED_WITH as expected. *Mike Clark*
+
+* Tighten rescue clauses. #5985 *james@grayproductions.net*
+
+* Fix send_data documentation typo. #5982 *brad@madriska.com*
+
+* Switch to using FormEncodedPairParser for parsing request parameters. *Nicholas Seckar, David Heinemeier Hansson*
+
+* respond_to .html now always renders #{action_name}.rhtml so that registered custom template handlers do not override it in priority. Custom mime types require a block and throw proper error now. *Tobias Lütke*
+
+* Deprecation: test deprecated instance vars in partials. *Jeremy Kemper*
+
+* Add UrlWriter to allow writing urls from Mailers and scripts. *Nicholas Seckar*
+
+* Clean up and run the Active Record integration tests by default. #5854 *Kevin Clark, Jeremy Kemper*
+
+* Correct example in cookies docs. #5832 *jessemerriman@warpmail.net*
+
+* Updated to script.aculo.us 1.6.2 *Thomas Fuchs*
+
+* Relax Routing's anchor pattern warning; it was preventing use of [^/] inside restrictions. *Nicholas Seckar*
+
+* Add controller_paths variable to Routing. *Nicholas Seckar*
+
+* Fix assert_redirected_to issue with named routes for module controllers. *Rick Olson*
+
+* Tweak RoutingError message to show option diffs, not just missing named route significant keys. *Rick Olson*
+
+* Invoke method_missing directly on hidden actions. Closes #3030. *Nicholas Seckar*
+
+* Require Tempfile explicitly for TestUploadedFile due to changes in class auto loading. *Rick Olson*
+
+* Add RoutingError exception when RouteSet fails to generate a path from a Named Route. *Rick Olson*
+
+* Replace Reloadable with Reloadable::Deprecated. *Nicholas Seckar*
+
+* Deprecation: check whether instance variables have been monkeyed with before assigning them to deprecation proxies. Raises a RuntimeError if so. *Jeremy Kemper*
+
+* Add support for the param_name parameter to the auto_complete_field helper. #5026 *david.a.williams@gmail.com*
+
+* Deprecation! @params, @session, @flash will be removed after 1.2. Use the corresponding instance methods instead. You'll get printed warnings during tests and logged warnings in dev mode when you access either instance variable directly. *Jeremy Kemper*
+
+* Make Routing noisy when an anchor regexp is assigned to a segment. #5674 *François Beausoleil*
+
+* Added months and years to the resolution of DateHelper#distance_of_time_in_words, such that "60 days ago" becomes "2 months ago" #5611 *pjhyett@gmail.com*
+
+* Short documentation to mention use of Mime::Type.register. #5710 *choonkeat@gmail.com*
+
+* Make controller_path available as an instance method. #5724 *jmckible@gmail.com*
+
+* Update query parser to support adjacent hashes. *Nicholas Seckar*
+
+* Make action caching aware of different formats for the same action so that, e.g. foo.xml is cached separately from foo.html. Implicitly set content type when reading in cached content with mime revealing extensions so the entire onous isn't on the webserver. *Marcel Molina Jr.*
+
+* Restrict Request Method hacking with ?_method to POST requests. *Rick Olson*
+
+* Fix bug when passing multiple options to SimplyRestful, like :new => { :preview => :get, :draft => :get }. *Rick Olson, Josh Susser, Lars Pind*
+
+* Dup the options passed to map.resources so that multiple resources get the same options. *Rick Olson*
+
+* Fixed the new_#{resource}_url route and added named route tests for Simply Restful. *Rick Olson*
+
+* Added map.resources from the Simply Restful plugin *DHH*. Examples (the API has changed to use plurals!):
+
+ map.resources :messages
+ map.resources :messages, :comments
+ map.resources :messages, :new => { :preview => :post }
+
+* Fixed that integration simulation of XHRs should set Accept header as well *Edward Frederick*
+
+* TestRequest#reset_session should restore a TestSession, not a hash *Michael Koziarski*
+
+* Don't search a load-path of '.' for controller files *Jamis Buck*
+
+* Update integration.rb to require test_process explicitly instead of via Dependencies. *Nicholas Seckar*
+
+* Fixed that you can still access the flash after the flash has been reset in reset_session. Closes #5584 *lmarlow*
+
+* Allow form_for and fields_for to work with indexed form inputs. *Jeremy Kemper, Matt Lyon*
+
+ <% form_for 'post[]', @post do |f| -%>
+ <% end -%>
+
+* Remove leak in development mode by replacing define_method with module_eval. *Nicholas Seckar*
+
+* Provide support for decimal columns to form helpers. Closes #5672. *Dave Thomas*
+
+* Update documentation for erb trim syntax. #5651 *matt@mattmargolis.net*
+
+* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 *olivier_ansaldi@yahoo.com*
+
+* Reset @html_document between requests so assert_tag works. #4810 *Jarkko Laine, easleydp@gmail.com*
+
+* Update render :partial documentation. #5646 *matt@mattmargolis.net*
+
+* Integration tests behave well with render_component. #4632 *edward.frederick@revolution.com, dev.rubyonrails@maxdunn.com*
+
+* Added exception handling of missing layouts #5373 *chris@ozmm.org*
+
+* Fixed that real files and symlinks should be treated the same when compiling templates #5438 *zachary@panandscan.com*
+
+* Fixed that the flash should be reset when reset_session is called #5584 *Shugo Maeda*
+
+* Added special case for "1 Byte" in NumberHelper#number_to_human_size #5593 *murpyh@rubychan.de*
+
+* Fixed proper form-encoded parameter parsing for requests with "Content-Type: application/x-www-form-urlencoded; charset=utf-8" (note the presence of a charset directive) *David Heinemeier Hansson*
+
+* Add route_name_path method to generate only the path for a named routes. For example, map.person will add person_path. *Nicholas Seckar*
+
+* Avoid naming collision among compiled view methods. *Jeremy Kemper*
+
+* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 *Mislav Marohnić*
+
+* Determine the correct template_root for deeply nested components. #2841 *s.brink@web.de*
+
+* Fix that routes with *path segments in the recall can generate URLs. *Rick Olson*
+
+* Fix strip_links so that it doesn't hang on multiline <acronym> tags *Jamis Buck*
+
+* Remove problematic control chars in rescue template. #5316 *Stefan Kaes*
+
+* Make sure passed routing options are not mutated by routing code. #5314 *Blair Zajac*
+
+* Make sure changing the controller from foo/bar to bing/bang does not change relative to foo. *Jamis Buck*
+
+* Escape the path before routing recognition. #3671
+
+* Make sure :id and friends are unescaped properly. #5275 *me@julik.nl*
+
+* Fix documentation for with_routing to reflect new reality. #5281 *rramdas@gmail.com*
+
+* Rewind readable CGI params so others may reread them (such as CGI::Session when passing the session id in a multipart form). #210 *mklame@atxeu.com, matthew@walker.wattle.id.au*
+
+* Added Mime::TEXT (text/plain) and Mime::ICS (text/calendar) as new default types *David Heinemeier Hansson*
+
+* Added Mime::Type.register(string, symbol, synonyms = []) for adding new custom mime types *DHH*. Example: Mime::Type.register("image/gif", :gif)
+
+* Added support for Mime objects in render :content_type option *DHH*. Example: render :text => some_atom, :content_type => Mime::ATOM
+
+* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 *Manfred Stienstra <m.stienstra@fngtps.com>*
+
+* Routing rewrite. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes. *Nicholas Seckar, Jamis Buck*
+
+ map.connect '/foo/:id', :controller => '...', :action => '...'
+ map.connect '/foo/:id.:format', :controller => '...', :action => '...'
+ map.connect '/foo/:id', ..., :conditions => { :method => :get }
+
+* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. *Jeremy Kemper*
+
+* Accept multipart PUT parameters. #5235 *guy.naor@famundo.com*
+
+* Added interrogation of params[:format] to determine Accept type. If :format is specified and matches a declared extension, like "rss" or "xml", that mime type will be put in front of the accept handler. This means you can link to the same action from different extensions and use that fact to determine output *DHH*. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find :all
+
+ respond_to do |format|
+ format.html
+ format.xml { render :xml => @posts.to_xml }
+ format.rss { render :action => "feed.rxml" }
+ end
+ end
+ end
+
+ \# returns HTML when requested by a browser, since the browser
+ \# has the HTML mimetype at the top of its priority list
+ Accept: text/html
+ GET /weblog
+
+ \# returns the XML
+ Accept: application/xml
+ GET /weblog
+
+ \# returns the HTML
+ Accept: application/xml
+ GET /weblog.html
+
+ \# returns the XML
+ Accept: text/html
+ GET /weblog.xml
+
+ All this relies on the fact that you have a route that includes .:format.
+
+* Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post *David Heinemeier Hansson*
+
+* Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete *David Heinemeier Hansson*
+
+* follow_redirect doesn't complain about being redirected to the same controller. #5153 *dymo@mk.ukrtelecom.ua*
+
+* Add layout attribute to response object with the name of the layout that was rendered, or nil if none rendered. *Kevin Clark*
+
+* Fix NoMethodError when parsing params like &&. *Adam Greenfield*
+
+* Fix flip flopped logic in docs for url_for's :only_path option. Closes #4998. *esad@esse.at*
+
+* form.text_area handles the :size option just like the original text_area (:size => '60x10' becomes cols="60" rows="10"). *Jeremy Kemper*
+
+* Excise ingrown code from FormOptionsHelper#options_for_select. #5008 *anonymous*
+
+* Small fix in routing to allow dynamic routes (broken after [4242]) *Rick Olson*
+
+ map.connect '*path', :controller => 'files', :action => 'show'
+
+* Replace alias method chaining with Module#alias_method_chain. *Marcel Molina Jr.*
+
+* Replace Ruby's deprecated append_features in favor of included. *Marcel Molina Jr.*
+
+* Use #flush between switching from #write to #syswrite. Closes #4907. *Blair Zajac <blair@orcaware.com>*
+
+* Documentation fix: integration test scripts don't require integration_test. Closes #4914. *Frederick Ros <sl33p3r@free.fr>*
+
+* ActionController::Base Summary documentation rewrite. Closes #4900. *Kevin Clark*
+
+* Fix text_helper.rb documentation rendering. Closes #4725. *Frederick Ros*
+
+* Fixes bad rendering of JavaScriptMacrosHelper rdoc (closes #4910) *Frederick Ros*
+
+* Allow error_messages_for to report errors for multiple objects, as well as support for customizing the name of the object in the error summary header. Closes #4186. *andrew@redlinesoftware.com, Marcel Molina Jr.*
+
+ error_messages_for :account, :user, :subscription, :object_name => :account
+
+* Enhance documentation for setting headers in integration tests. Skip auto HTTP prepending when its already there. Closes #4079. *Rick Olson*
+
+* Documentation for AbstractRequest. Closes #4895. *Kevin Clark*
+
+* Refactor various InstanceTag instance method to class methods. Closes #4800. *Stefan Kaes*
+
+* Remove all remaining references to @params in the documentation. *Marcel Molina Jr.*
+
+* Add documentation for redirect_to :back's RedirectBackError exception. *Marcel Molina Jr.*
+
+* Update layout and content_for documentation to use yield rather than magic @content_for instance variables. *Marcel Molina Jr.*
+
+* Fix assert_redirected_to tests according to real-world usage. Also, don't fail if you add an extra :controller option: *Rick Olson*
+
+ redirect_to :action => 'new'
+ assert_redirected_to :controller => 'monkeys', :action => 'new'
+
+* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. *Rick Olson*
+
+* Diff compared routing options. Allow #assert_recognizes to take a second arg as a hash to specify optional request method *Rick Olson*
+
+ assert_recognizes({:controller => 'users', :action => 'index'}, 'users')
+ assert_recognizes({:controller => 'users', :action => 'create'}, {:path => 'users', :method => :post})
+
+* Diff compared options with #assert_redirected_to *Rick Olson*
+
+* Add support in routes for semicolon delimited "subpaths", like /books/:id;:action *Jamis Buck*
+
+* Change link_to_function and button_to_function to (optionally) take an update_page block instead of a JavaScript string. Closes #4804. *zraii@comcast.net, Sam Stephenson*
+
+* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for *David Heinemeier Hansson*
+
+* Modify routing so that you can say :require => { :method => :post } for a route, and the route will never be selected unless the request method is POST. Only works for route recognition, not for route generation. *Jamis Buck*
+
+* Added :add_headers option to verify which merges a hash of name/value pairs into the response's headers hash if the prerequisites cannot be satisfied. *Sam Stephenson*
+ ex. verify :only => :speak, :method => :post,
+ :render => { :status => 405, :text => "Must be post" },
+ :add_headers => { "Allow" => "POST" }
+
+* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled #1897 *jeremye@bsa.ca.gov*
+
+
+## 1.13.3 (March 12th, 2007) ##
+
+* Apply [5709] to stable.
+
+* session_enabled? works with session :off. #6680 *Jonathan del Strother*
+
+* Performance: patch cgi/session to require digest/md5 once rather than per #create_new_id. *Stefan Kaes*
+
+
+## 1.13.2 (February 5th, 2007) ##
+
+* Add much-needed html-scanner tests. Fixed CDATA parsing bug. *Rick Olson*
+
+* improve error message for Routing for named routes. *Rob Sanheim*
+
+* Added enhanced docs to routing assertions. *Rob Sanheim*
+
+* fix form_for example in ActionController::Resources documentation. *gnarg*
+
+* Add singleton resources from trunk *Rick Olson*
+
+* select :multiple => true suffixes the attribute name with [] unless already suffixed. #6977 *nik.kakelin, ben, julik*
+
+* Improve routes documentation. #7095 *zackchandler*
+
+* Resource member routes require :id, eliminating the ambiguous overlap with collection routes. #7229 *dkubb*
+
+* Fixed NumberHelper#number_with_delimiter to use "." always for splitting the original number, not the delimiter parameter #7389 *ceefour*
+
+* Autolinking recognizes trailing and embedded . , : ; #7354 *Jarkko Laine*
+
+* Make TextHelper::auto_link recognize URLs with colons in path correctly, fixes #7268. *imajes*
+
+* Improved auto_link to match more valid urls correctly *Tobias Lütke*
+
+
+## 1.13.1 (January 18th, 2007) ##
+
+* Fixed content-type bug in Prototype *sam*
+
+
+## 1.13.0 (January 16th, 2007) ##
+
+* Modernize cookie testing code, and increase coverage (Heckle++) #7101 *Kevin Clark*
+
+* Heckling ActionController::Resources::Resource revealed that set_prefixes didn't break when :name_prefix was munged. #7081 *Kevin Clark*
+
+* Update to Prototype 1.5.0. *Sam Stephenson*
+
+* Allow exempt_from_layout :rhtml. #6742, #7026 *Dan Manges, Squeegy*
+
+* Fix parsing of array[] CGI parameters so extra empty values aren't included. #6252 *Nicholas Seckar, aiwilliams, brentrowland*
+
+* link_to_unless_current works with full URLs as well as paths. #6891 *Jarkko Laine, Manfred Stienstra, idrifter*
+
+* Fix HTML::Node to output double quotes instead of single quotes. Closes #6845 *mitreandy*
+
+* Fix no method error with error_messages_on. Closes #6935 *nik.wakelin Koz*
+
+* Slight doc tweak to the ActionView::Helpers::PrototypeHelper#replace docs. Closes #6922 *Steven Bristol*
+
+* Slight doc tweak to #prepend_filter. Closes #6493 *Jeremy Voorhis*
+
+* Add more extensive documentation to the AssetTagHelper. Closes #6452 *Bob Silva*
+
+* Clean up multiple calls to #stringify_keys in TagHelper, add better documentation and testing for TagHelper. Closes #6394 *Bob Silva*
+
+* [DOCS] fix reference to ActionController::Macros::AutoComplete for #text_field_with_auto_complete. Closes #2578 *Jan Prill*
+
+* Make sure html_document is reset between integration test requests. *ctm*
+
+* Set session to an empty hash if :new_session => false and no session cookie or param is present. CGI::Session was raising an unrescued ArgumentError. *Josh Susser*
+
+* Fix assert_redirected_to bug where redirecting from a nested to to a top-level controller incorrectly added the current controller's nesting. Closes #6128. *Rick Olson*
+
+* Ensure render :json => ... skips the layout. #6808 *Josh Peek*
+
+* Silence log_error deprecation warnings from inspecting deprecated instance variables. *Nate Wiger*
+
+* Only cache GET requests with a 200 OK response. #6514, #6743 *RSL, anamba*
+
+* Correctly report which filter halted the chain. #6699 *Martin Emde*
+
+* respond_to recognizes JSON. render :json => @person.to_json automatically sets the content type and takes a :callback option to specify a client-side function to call using the rendered JSON as an argument. #4185 *Scott Raymond, eventualbuddha*
+ # application/json response with body 'Element.show({:name: "David"})'
+ respond_to do |format|
+ format.json { render :json => { :name => "David" }.to_json, :callback => 'Element.show' }
+ end
+
+* Makes :discard_year work without breaking multi-attribute parsing in AR. #1260, #3800 *sean@ardismg.com, jmartin@desertflood.com, stephen@touset.org, Bob Silva*
+
+* Adds html id attribute to date helper elements. #1050, #1382 *mortonda@dgrmm.net, David North, Bob Silva*
+
+* Add :index and @auto_index capability to model driven date/time selects. #847, #2655 *moriq, Doug Fales, Bob Silva*
+
+* Add :order to datetime_select, select_datetime, and select_date. #1427 *Timothee Peignier, Patrick Lenz, Bob Silva*
+
+* Added time_select to work with time values in models. Update scaffolding. #2489, #2833 *Justin Palmer, Andre Caum, Bob Silva*
+
+* Added :include_seconds to select_datetime, datetime_select and time_select. #2998 *csn, Bob Silva*
+
+* All date/datetime selects can now accept an array of month names with :use_month_names. Allows for localization. #363 *tomasj, Bob Silva*
+
+* Adds :time_separator to select_time and :date_separator to select_datetime. Preserves BC. #3811 *Bob Silva*
+
+* @response.redirect_url works with 201 Created responses: just return headers['Location'] rather than checking the response status. *Jeremy Kemper*
+
+* Fixed that HEAD should return the proper Content-Length header (that is, actually use @body.size, not just 0) *David Heinemeier Hansson*
+
+* Added GET-masquarading for HEAD, so request.method will return :get even for HEADs. This will help anyone relying on case request.method to automatically work with HEAD and map.resources will also allow HEADs to all GET actions. Rails automatically throws away the response content in a reply to HEAD, so you don't even need to worry about that. If you, for whatever reason, still need to distinguish between GET and HEAD in some edge case, you can use Request#head? and even Request.headers["REQUEST_METHOD"] for get the "real" answer. Closes #6694 *David Heinemeier Hansson*
+
+
+## 1.13.0 RC1 (r5619, November 22nd, 2006) ##
+
+* Update Routing to complain when :controller is not specified by a route. Closes #6669. *Nicholas Seckar*
+
+* Ensure render_to_string cleans up after itself when an exception is raised. #6658 *rsanheim*
+
+* Update to Prototype and script.aculo.us [5579]. *Sam Stephenson, Thomas Fuchs*
+
+* simple_format helper doesn't choke on nil. #6644 *jerry426*
+
+* Reuse named route helper module between Routing reloads. Use remove_method to delete named route methods after each load. Since the module is never collected, this fixes a significant memory leak. *Nicholas Seckar*
+
+* Deprecate standalone components. *Jeremy Kemper*
+
+* Always clear model associations from session. #4795 *sd@notso.net, andylien@gmail.com*
+
+* Remove JavaScriptLiteral in favor of ActiveSupport::JSON::Variable. *Sam Stephenson*
+
+* Sync ActionController::StatusCodes::STATUS_CODES with http://www.iana.org/assignments/http-status-codes. #6586 *dkubb*
+
+* Multipart form values may have a content type without being treated as uploaded files if they do not provide a filename. #6401 *Andreas Schwarz, Jeremy Kemper*
+
+* assert_response supports symbolic status codes. #6569 *Kevin Clark*
+ assert_response :ok
+ assert_response :not_found
+ assert_response :forbidden
+
+* Cache parsed query parameters. #6559 *Stefan Kaes*
+
+* Deprecate JavaScriptHelper#update_element_function, which is superseeded by RJS *Thomas Fuchs*
+
+* Fix invalid test fixture exposed by stricter Ruby 1.8.5 multipart parsing. #6524 *Bob Silva*
+
+* Set ActionView::Base.default_form_builder once rather than passing the :builder option to every form or overriding the form helper methods. *Jeremy Kemper*
+
+* Deprecate expire_matched_fragments. Use expire_fragment instead. #6535 *Bob Silva*
+
+* Deprecate start_form_tag and end_form_tag. Use form_tag / '</form>' from now on. *Rick Olson*
+
+* Added block-usage to PrototypeHelper#form_remote_tag, document block-usage of FormTagHelper#form_tag *Rick Olson*
+
+* Add a 0 margin/padding div around the hidden _method input tag that form_tag outputs. *Rick Olson*
+
+* Added block-usage to TagHelper#content_tag *DHH*. Example:
+
+ <% content_tag :div, :class => "strong" %>
+ Hello world!
+ <% end %>
+
+ Will output:
+ <div class="strong">Hello world!</div>
+
+* Deprecated UrlHelper#link_to_image and UrlHelper#link_to :post => true #6409 *Bob Silva*
+
+* Upgraded NumberHelper with number_to_phone support international formats to comply with ITU E.123 by supporting area codes with less than 3 digits, added precision argument to number_to_human_size (defaults to 1) #6421 *Bob Silva*
+
+* Fixed that setting RAILS_ASSET_ID to "" should not add a trailing slash after assets #6454 *Bob Silva/chrismear*
+
+* Force *_url named routes to show the host in ActionView *Rick Olson*
+
+ <%= url_for ... %> # no host
+ <%= foo_path %> # no host
+ <%= foo_url %> # host!
+
+* Add support for converting blocks into function arguments to JavaScriptGenerator#call and JavaScriptProxy#call. *Sam Stephenson*
+
+* Add JavaScriptGenerator#literal for wrapping a string in an object whose #to_json is the string itself. *Sam Stephenson*
+
+* Add <%= escape_once html %> to escape html while leaving any currently escaped entities alone. Fix button_to double-escaping issue. *Rick Olson*
+
+* Fix double-escaped entities, such as &amp;amp;, &amp;#123;, etc. *Rick Olson*
+
+* Fix routing to correctly determine when generation fails. Closes #6300. *psross*.
+
+* Fix broken assert_generates when extra keys are being checked. *Jamis Buck*
+
+* Replace KCODE checks with String#chars for truncate. Closes #6385 *Manfred Stienstra*
+
+* Make page caching respect the format of the resource that is being requested even if the current route is the default route so that, e.g. posts.rss is not transformed by url_for to '/' and subsequently cached as '/index.html' when it should be cached as '/posts.rss'. *Marcel Molina Jr.*
+
+* Use String#chars in TextHelper::excerpt. Closes #6386 *Manfred Stienstra*
+
+* Fix relative URL root matching problems. *Mark Imbriaco*
+
+* Fix filter skipping in controller subclasses. #5949, #6297, #6299 *Martin Emde*
+
+* render_text may optionally append to the response body. render_javascript appends by default. This allows you to chain multiple render :update calls by setting @performed_render = false between them (awaiting a better public API). *Jeremy Kemper*
+
+* Rename test assertion to prevent shadowing. Closes #6306. *psross*
+
+* Fixed that NumberHelper#number_to_delimiter should respect precision of higher than two digits #6231 *Philip Hallstrom*
+
+* Fixed that FormHelper#radio_button didn't respect an :id being passed in #6266 *evansj*
+
+* Added an html_options hash parameter to javascript_tag() and update_page_tag() helpers #6311 *tzaharia*. Example:
+
+ update_page_tag :defer => 'true' { |page| ... }
+
+ Gives:
+
+ <script defer="true" type="text/javascript">...</script>
+
+ Which is needed for dealing with the IE6 DOM when it's not yet fully loaded.
+
+* Fixed that rescue template path shouldn't be hardcoded, then it's easier to hook in your own #6295 *Mike Naberezny*
+
+* Fixed escaping of backslashes in JavaScriptHelper#escape_javascript #6302 *sven@c3d2.de*
+
+* Fixed that some 500 rescues would cause 500's themselves because the response had not yet been generated #6329 *cmselmer*
+
+* respond_to :html doesn't assume .rhtml. #6281 *Hampton Catlin*
+
+* Fixed some deprecation warnings in ActionPack *Rick Olson*
+
+* assert_select_rjs decodes escaped unicode chars since the Javascript generators encode them. #6240 *japgolly*
+
+* Deprecation: @cookies, @headers, @request, @response will be removed after 1.2. Use the corresponding method instead. *Jeremy Kemper*
+
+* Make the :status parameter expand to the default message for that status code if it is an integer. Also support symbol statuses. *Jamis Buck*. Examples:
+
+ head :status => 404 # expands to "404 Not Found"
+ head :status => :not_found # expands to "404 Not Found"
+ head :status => :created # expands to "201 Created"
+
+* Add head(options = {}) for responses that have no body. *Jamis Buck*. Examples:
+
+ head :status => 404 # return an empty response with a 404 status
+ head :location => person_path(@person), :status => 201
+
+* Fix bug that kept any before_filter except the first one from being able to halt the before_filter chain. *Rick Olson*
+
+* strip_links is case-insensitive. #6285 *tagoh, Bob Silva*
+
+* Clear the cache of possible controllers whenever Routes are reloaded. *Nicholas Seckar*
+
+* Filters overhaul including meantime filter support using around filters + blocks. #5949 *Martin Emde, Roman Le Negrate, Stefan Kaes, Jeremy Kemper*
+
+* Update CGI process to allow sessions to contain namespaced models. Closes #4638. *dfelstead@site5.com*
+
+* Fix routing to respect user provided requirements and defaults when assigning default routing options (such as :action => 'index'). Closes #5950. *Nicholas Seckar*
+
+* Rescue Errno::ECONNRESET to handle an unexpectedly closed socket connection. Improves SCGI reliability. #3368, #6226 *sdsykes, fhanshaw@vesaria.com*
+
+* Added that respond_to blocks will automatically set the content type to be the same as is requested *DHH*. Examples:
+
+ respond_to do |format|
+ format.html { render :text => "I'm being sent as text/html" }
+ format.rss { render :text => "I'm being sent as application/rss+xml" }
+ format.atom { render :text => "I'm being sent as application/xml", :content_type => Mime::XML }
+ end
+
+* Added utf-8 as the default charset for all renders. You can change this default using ActionController::Base.default_charset=(encoding) *David Heinemeier Hansson*
+
+* Added proper getters and setters for content type and charset *DHH*. Example of what we used to do:
+
+ response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
+
+ ...now:
+
+ response.content_type = Mime::ATOM
+ response.charset = "utf-8"
+
+* Declare file extensions exempt from layouts. #6219 *brandon*
+ Example: ActionController::Base.exempt_from_layout 'rpdf'
+
+* Add chained replace/update support for assert_select_rjs *Rick Olson*
+
+ Given RJS like...
+
+ page['test1'].replace "<div id=\"1\">foo</div>"
+ page['test2'].replace_html "<div id=\"2\">foo</div>"
+
+ Test it with...
+
+ assert_select_rjs :chained_replace
+ assert_select_rjs :chained_replace, "test1"
+
+ assert_select_rjs :chained_replace_html
+ assert_select_rjs :chained_replace_html, "test2"
+
+* Load helpers in alphabetical order for consistency. Resolve cyclic javascript_helper dependency. #6132, #6178 *choonkeat@gmail.com*
+
+* Skip params with empty names, such as the &=Save query string from <input type="submit"/>. #2569 *Manfred Stienstra, raphinou@yahoo.com*
+
+* Fix assert_tag so that :content => "foo" does not match substrings, but only exact strings. Use :content => /foo/ to match substrings. #2799 *Eric Hodel*
+
+* Update JavaScriptGenerator#show/hide/toggle/remove to new Prototype syntax for multiple ids, #6068 *petermichaux@gmail.com*
+
+* Update UrlWriter to support :only_path. *Nicholas Seckar, Dave Thomas*
+
+* Fixed JavaScriptHelper#link_to_function and JavaScriptHelper#button_to_function to have the script argument be optional *DHH*. So what used to require a nil, like this:
+
+ link_to("Hider", nil, :class => "hider_link") { |p| p[:something].hide }
+
+ ...can be written like this:
+
+ link_to("Hider", :class => "hider_link") { |p| p[:something].hide }
+
+* Added access to nested attributes in RJS #4548 *richcollins@gmail.com*. 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';
+
+* Fixed that AssetTagHelper#image_tag and others using compute_public_path should not modify the incoming source argument (closes #5102) *eule@space.ch*
+
+* Deprecated the auto-appending of .png to AssetTagHelper#image_tag calls that doesn't have an extension *David Heinemeier Hansson*
+
+* Fixed FormOptionsHelper#select to respect :selected value #5813
+
+* Fixed TextHelper#simple_format to deal with multiple single returns within a single paragraph #5835 *moriq@moriq.com*
+
+* Fixed TextHelper#pluralize to handle 1 as a string #5909 *rails@bencurtis.com*
+
+* Improved resolution of DateHelper#distance_of_time_in_words for better precision #5994 *Bob Silva*
+
+* Changed that uncaught exceptions raised any where in the application will cause RAILS_ROOT/public/500.html to be read and shown instead of just the static "Application error (Rails)" *David Heinemeier Hansson*
+
+* Added deprecation language for pagination which will become a plugin by Rails 2.0 *David Heinemeier Hansson*
+
+* Added deprecation language for in_place_editor and auto_complete_field that both pieces will become plugins by Rails 2.0 *David Heinemeier Hansson*
+
+* Deprecated all of ActionController::Dependencies. All dependency loading is now handled from Active Support *David Heinemeier Hansson*
+
+* Added assert_select* for CSS selector-based testing (deprecates assert_tag) #5936 *assaf.arkin@gmail.com*
+
+* radio_button_tag generates unique id attributes. #3353 *Bob Silva, somekool@gmail.com*
+
+* strip_tags passes through blank args such as nil or "". #2229, #6702 *duncan@whomwah.com, dharana*
+
+* Cleanup assert_tag :children counting. #2181 *jamie@bravenet.com*
+
+* button_to accepts :method so you can PUT and DELETE with it. #6005 *Dan Webb*
+
+* Update sanitize text helper to strip plaintext tags, and <img src="javascript:bang">. *Rick Olson*
+
+* Add routing tests to assert that RoutingError is raised when conditions aren't met. Closes #6016 *Nathan Witmer*
+
+* Make auto_link parse a greater subset of valid url formats. *Jamis Buck*
+
+* Integration tests: headers beginning with X aren't excluded from the HTTP_ prefix, so X-Requested-With becomes HTTP_X_REQUESTED_WITH as expected. *Mike Clark*
+
+* Switch to using FormEncodedPairParser for parsing request parameters. *Nicholas Seckar, David Heinemeier Hansson*
+
+* respond_to .html now always renders #{action_name}.rhtml so that registered custom template handlers do not override it in priority. Custom mime types require a block and throw proper error now. *Tobias Lütke*
+
+* Deprecation: test deprecated instance vars in partials. *Jeremy Kemper*
+
+* Add UrlWriter to allow writing urls from Mailers and scripts. *Nicholas Seckar*
+
+* Relax Routing's anchor pattern warning; it was preventing use of [^/] inside restrictions. *Nicholas Seckar*
+
+* Add controller_paths variable to Routing. *Nicholas Seckar*
+
+* Fix assert_redirected_to issue with named routes for module controllers. *Rick Olson*
+
+* Tweak RoutingError message to show option diffs, not just missing named route significant keys. *Rick Olson*
+
+* Invoke method_missing directly on hidden actions. Closes #3030. *Nicholas Seckar*
+
+* Add RoutingError exception when RouteSet fails to generate a path from a Named Route. *Rick Olson*
+
+* Replace Reloadable with Reloadable::Deprecated. *Nicholas Seckar*
+
+* Deprecation: check whether instance variables have been monkeyed with before assigning them to deprecation proxies. Raises a RuntimeError if so. *Jeremy Kemper*
+
+* Add support for the param_name parameter to the auto_complete_field helper. #5026 *david.a.williams@gmail.com*
+
+* Deprecation! @params, @session, @flash will be removed after 1.2. Use the corresponding instance methods instead. You'll get printed warnings during tests and logged warnings in dev mode when you access either instance variable directly. *Jeremy Kemper*
+
+* Make Routing noisy when an anchor regexp is assigned to a segment. #5674 *François Beausoleil*
+
+* Added months and years to the resolution of DateHelper#distance_of_time_in_words, such that "60 days ago" becomes "2 months ago" #5611 *pjhyett@gmail.com*
+
+* Make controller_path available as an instance method. #5724 *jmckible@gmail.com*
+
+* Update query parser to support adjacent hashes. *Nicholas Seckar*
+
+* Make action caching aware of different formats for the same action so that, e.g. foo.xml is cached separately from foo.html. Implicitly set content type when reading in cached content with mime revealing extensions so the entire onous isn't on the webserver. *Marcel Molina Jr.*
+
+* Restrict Request Method hacking with ?_method to POST requests. *Rick Olson*
+
+* Fixed the new_#{resource}_url route and added named route tests for Simply Restful. *Rick Olson*
+
+* Added map.resources from the Simply Restful plugin *DHH*. Examples (the API has changed to use plurals!):
+
+ map.resources :messages
+ map.resources :messages, :comments
+ map.resources :messages, :new => { :preview => :post }
+
+* Fixed that integration simulation of XHRs should set Accept header as well *Edward Frederick*
+
+* TestRequest#reset_session should restore a TestSession, not a hash *Michael Koziarski*
+
+* Don't search a load-path of '.' for controller files *Jamis Buck*
+
+* Update integration.rb to require test_process explicitly instead of via Dependencies. *Nicholas Seckar*
+
+* Fixed that you can still access the flash after the flash has been reset in reset_session. Closes #5584 *lmarlow*
+
+* Allow form_for and fields_for to work with indexed form inputs. *Jeremy Kemper, Matt Lyon*
+
+ <% form_for 'post[]', @post do |f| -%>
+ <% end -%>
+
+* Remove leak in development mode by replacing define_method with module_eval. *Nicholas Seckar*
+
+* Provide support for decimal columns to form helpers. Closes #5672. *Dave Thomas*
+
+* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 *olivier_ansaldi@yahoo.com*
+
+* Reset @html_document between requests so assert_tag works. #4810 *Jarkko Laine, easleydp@gmail.com*
+
+* Integration tests behave well with render_component. #4632 *edward.frederick@revolution.com, dev.rubyonrails@maxdunn.com*
+
+* Added exception handling of missing layouts #5373 *chris@ozmm.org*
+
+* Fixed that real files and symlinks should be treated the same when compiling templates #5438 *zachary@panandscan.com*
+
+* Fixed that the flash should be reset when reset_session is called #5584 *Shugo Maeda*
+
+* Added special case for "1 Byte" in NumberHelper#number_to_human_size #5593 *murpyh@rubychan.de*
+
+* Fixed proper form-encoded parameter parsing for requests with "Content-Type: application/x-www-form-urlencoded; charset=utf-8" (note the presence of a charset directive) *David Heinemeier Hansson*
+
+* Add route_name_path method to generate only the path for a named routes. For example, map.person will add person_path. *Nicholas Seckar*
+
+* Avoid naming collision among compiled view methods. *Jeremy Kemper*
+
+* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 *Mislav Marohnić*
+
+* Determine the correct template_root for deeply nested components. #2841 *s.brink@web.de*
+
+* Fix that routes with *path segments in the recall can generate URLs. *Rick Olson*
+
+* Fix strip_links so that it doesn't hang on multiline <acronym> tags *Jamis Buck*
+
+* Remove problematic control chars in rescue template. #5316 *Stefan Kaes*
+
+* Make sure passed routing options are not mutated by routing code. #5314 *Blair Zajac*
+
+* Make sure changing the controller from foo/bar to bing/bang does not change relative to foo. *Jamis Buck*
+
+* Escape the path before routing recognition. #3671
+
+* Make sure :id and friends are unescaped properly. #5275 *me@julik.nl*
+
+* Rewind readable CGI params so others may reread them (such as CGI::Session when passing the session id in a multipart form). #210 *mklame@atxeu.com, matthew@walker.wattle.id.au*
+
+* Added Mime::TEXT (text/plain) and Mime::ICS (text/calendar) as new default types *David Heinemeier Hansson*
+
+* Added Mime::Type.register(string, symbol, synonyms = []) for adding new custom mime types *DHH*. Example: Mime::Type.register("image/gif", :gif)
+
+* Added support for Mime objects in render :content_type option *DHH*. Example: render :text => some_atom, :content_type => Mime::ATOM
+
+* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 *Manfred Stienstra <m.stienstra@fngtps.com>*
+
+* Routing rewrite. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes. *Nicholas Seckar, Jamis Buck*
+
+ map.connect '/foo/:id', :controller => '...', :action => '...'
+ map.connect '/foo/:id.:format', :controller => '...', :action => '...'
+ map.connect '/foo/:id', ..., :conditions => { :method => :get }
+
+* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. *Jeremy Kemper*
+
+* Accept multipart PUT parameters. #5235 *guy.naor@famundo.com*
+
+* Added interrogation of params[:format] to determine Accept type. If :format is specified and matches a declared extension, like "rss" or "xml", that mime type will be put in front of the accept handler. This means you can link to the same action from different extensions and use that fact to determine output *DHH*. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find :all
+
+ respond_to do |format|
+ format.html
+ format.xml { render :xml => @posts.to_xml }
+ format.rss { render :action => "feed.rxml" }
+ end
+ end
+ end
+
+ \# returns HTML when requested by a browser, since the browser
+ \# has the HTML mimetype at the top of its priority list
+ Accept: text/html
+ GET /weblog
+
+ \# returns the XML
+ Accept: application/xml
+ GET /weblog
+
+ \# returns the HTML
+ Accept: application/xml
+ GET /weblog.html
+
+ \# returns the XML
+ Accept: text/html
+ GET /weblog.xml
+
+ All this relies on the fact that you have a route that includes .:format.
+
+* Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post *David Heinemeier Hansson*
+
+* Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete *David Heinemeier Hansson*
+
+* follow_redirect doesn't complain about being redirected to the same controller. #5153 *dymo@mk.ukrtelecom.ua*
+
+* Add layout attribute to response object with the name of the layout that was rendered, or nil if none rendered. *Kevin Clark*
+
+* Fix NoMethodError when parsing params like &&. *Adam Greenfield*
+
+* form.text_area handles the :size option just like the original text_area (:size => '60x10' becomes cols="60" rows="10"). *Jeremy Kemper*
+
+* Excise ingrown code from FormOptionsHelper#options_for_select. #5008 *anonymous*
+
+* Small fix in routing to allow dynamic routes (broken after [4242]) *Rick Olson*
+
+ map.connect '*path', :controller => 'files', :action => 'show'
+
+* Use #flush between switching from #write to #syswrite. Closes #4907. *Blair Zajac <blair@orcaware.com>*
+
+* Allow error_messages_for to report errors for multiple objects, as well as support for customizing the name of the object in the error summary header. Closes #4186. *andrew@redlinesoftware.com, Marcel Molina Jr.*
+
+ error_messages_for :account, :user, :subscription, :object_name => :account
+
+* Fix assert_redirected_to tests according to real-world usage. Also, don't fail if you add an extra :controller option: *Rick Olson*
+
+ redirect_to :action => 'new'
+ assert_redirected_to :controller => 'monkeys', :action => 'new'
+
+* Diff compared routing options. Allow #assert_recognizes to take a second arg as a hash to specify optional request method *Rick Olson*
+
+ assert_recognizes({:controller => 'users', :action => 'index'}, 'users')
+ assert_recognizes({:controller => 'users', :action => 'create'}, {:path => 'users', :method => :post})
+
+* Diff compared options with #assert_redirected_to *Rick Olson*
+
+* Add support in routes for semicolon delimited "subpaths", like /books/:id;:action *Jamis Buck*
+
+* Change link_to_function and button_to_function to (optionally) take an update_page block instead of a JavaScript string. Closes #4804. *zraii@comcast.net, Sam Stephenson*
+
+* Modify routing so that you can say :require => { :method => :post } for a route, and the route will never be selected unless the request method is POST. Only works for route recognition, not for route generation. *Jamis Buck*
+
+* Added :add_headers option to verify which merges a hash of name/value pairs into the response's headers hash if the prerequisites cannot be satisfied. *Sam Stephenson*
+ ex. verify :only => :speak, :method => :post,
+ :render => { :status => 405, :text => "Must be post" },
+ :add_headers => { "Allow" => "POST" }
+
+
+## 1.12.5 (August 10th, 2006) ##
+
+* Updated security fix
+
+
+## 1.12.4 (August 8th, 2006) ##
+
+* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. *Rick Olson*
+
+* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for *David Heinemeier Hansson*
+
+* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled. #1897 *jeremye@bsa.ca.gov*
+
+* Fixed that real files and symlinks should be treated the same when compiling templates. #5438 *zachary@panandscan.com*
+
+* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 *Manfred Stienstra <m.stienstra@fngtps.com>*
+
+* Update documentation for erb trim syntax. #5651 *matt@mattmargolis.net*
+
+* Short documentation to mention use of Mime::Type.register. #5710 *choonkeat@gmail.com*
+
+
+## 1.12.3 (June 28th, 2006) ##
+
+* Fix broken traverse_to_controller. We now:
+ Look for a _controller.rb file under RAILS_ROOT to load.
+ If we find it, we require_dependency it and return the controller it defined. (If none was defined we stop looking.)
+ If we don't find it, we look for a .rb file under RAILS_ROOT to load. If we find it, and it loads a constant we keep looking.
+ Otherwise we check to see if a directory of the same name exists, and if it does we create a module for it.
+
+
+## 1.12.2 (June 27th, 2006) ##
+
+* Refinement to avoid exceptions in traverse_to_controller.
+
+* (Hackish) Fix loading of arbitrary files in Ruby's load path by traverse_to_controller. *Nicholas Seckar*
+
+
+## 1.12.1 (April 6th, 2006) ##
+
+* Fixed that template extensions would be cached development mode #4624 *Stefan Kaes*
+
+* Update to Prototype 1.5.0_rc0 *Sam Stephenson*
+
+* Honor skipping filters conditionally for only certain actions even when the parent class sets that filter to conditionally be executed only for the same actions. #4522 *Marcel Molina Jr.*
+
+* Delegate xml_http_request in integration tests to the session instance. *Jamis Buck*
+
+* Update the diagnostics template skip the useless '<controller not set>' text. *Nicholas Seckar*
+
+* CHANGED DEFAULT: Don't parse YAML input by default, but keep it available as an easy option *David Heinemeier Hansson*
+
+* Add additional autocompleter options *aballai, Thomas Fuchs*
+
+* Fixed fragment caching of binary data on Windows #4493 *bellis@deepthought.org*
+
+* Applied Prototype $() performance patches (#4465, #4477) and updated script.aculo.us *Sam Stephenson, Thomas Fuchs*
+
+* Added automated timestamping to AssetTagHelper methods for stylesheets, javascripts, and images when Action Controller is run under Rails *DHH*. Example:
+
+ image_tag("rails.png") # => '<img alt="Rails" src="/images/rails.png?1143664135" />'
+
+ ...to avoid frequent stats (not a problem for most people), you can set RAILS_ASSET_ID in the ENV to avoid stats:
+
+ ENV["RAILS_ASSET_ID"] = "2345"
+ image_tag("rails.png") # => '<img alt="Rails" src="/images/rails.png?2345" />'
+
+ This can be used by deployment managers to set the asset id by application revision
+
+
+## 1.12.0 (March 27th, 2006) ##
+
+* Add documentation for respond_to. *Jamis Buck*
+
+* Fixed require of bluecloth and redcloth when gems haven't been loaded #4446 *murphy@cYcnus.de*
+
+* Update to Prototype 1.5.0_pre1 *Sam Stephenson*
+
+* Change #form_for and #fields_for so that the second argument is not required *Dave Thomas*
+
+ <% form_for :post, @post, :url => { :action => 'create' } do |f| -%>
+
+ becomes...
+
+ <% form_for :post, :url => { :action => 'create' } do |f| -%>
+
+* Update to script.aculo.us 1.6 *Thomas Fuchs*
+
+* Enable application/x-yaml processing by default *Jamis Buck*
+
+* Fix double url escaping of remote_function. Add :escape => false option to ActionView's url_for. *Nicholas Seckar*
+
+* Add :script option to in_place_editor to support evalScripts (closes #4194) *Cody Fauser*
+
+* Fix mixed case enumerable methods in the JavaScript Collection Proxy (closes #4314) *Cody Fauser*
+
+* Undo accidental escaping for mail_to; add regression test. *Nicholas Seckar*
+
+* Added nicer message for assert_redirected_to (closes #4294) *court3nay*
+
+ assert_redirected_to :action => 'other_host', :only_path => false
+
+ when it was expecting...
+
+ redirected_to :action => 'other_host', :only_path => true, :host => 'other.test.host'
+
+ gives the error message...
+
+ response is not a redirection to all of the options supplied (redirection is <{:only_path=>false, :host=>"other.test.host", :action=>"other_host"}>), difference: <{:only_path=>"true", :host=>"other.test.host"}>
+
+* Change url_for to escape the resulting URLs when called from a view. *Nicholas Seckar, coffee2code*
+
+* Added easy support for testing file uploads with fixture_file_upload #4105 *turnip@turnipspatch.com*. Example:
+
+ # Looks in Test::Unit::TestCase.fixture_path + '/files/spongebob.png'
+ post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
+
+* Fixed UrlHelper#current_page? to behave even when url-escaped entities are present #3929 *jeremy@planetargon.com*
+
+* Add ability for relative_url_root to be specified via an environment variable RAILS_RELATIVE_URL_ROOT. *isaac@reuben.com, Nicholas Seckar*
+
+* Fixed link_to "somewhere", :post => true to produce valid XHTML by using the parentnode instead of document.body for the instant form #3007 *Bob Silva*
+
+* Added :function option to PrototypeHelper#observe_field/observe_form that allows you to call a function instead of submitting an ajax call as the trigger #4268 *jonathan@daikini.com*
+
+* Make Mime::Type.parse consider q values (if any) *Jamis Buck*
+
+* XML-formatted requests are typecast according to "type" attributes for :xml_simple *Jamis Buck*
+
+* Added protection against proxy setups treating requests as local even when they're not #3898 *Steve Purcell*
+
+* Added TestRequest#raw_post that simulate raw_post from CgiRequest #3042 *François Beausoleil*
+
+* Underscore dasherized keys in formatted requests *Jamis Buck*
+
+* Add MimeResponds::Responder#any for managing multiple types with identical responses *Jamis Buck*
+
+* Make the xml_http_request testing method set the HTTP_ACCEPT header *Jamis Buck*
+
+* Add Verification to scaffolds. Prevent destructive actions using GET *Michael Koziarski*
+
+* Avoid hitting the filesystem when using layouts by using a File.directory? cache. *Stefan Kaes, Nicholas Seckar*
+
+* Simplify ActionController::Base#controller_path *Nicholas Seckar*
+
+* Added simple alert() notifications for RJS exceptions when config.action_view.debug_rjs = true. *Sam Stephenson*
+
+* Added :content_type option to render, so you can change the content type on the fly *DHH*. Example: render :action => "atom.rxml", :content_type => "application/atom+xml"
+
+* CHANGED DEFAULT: The default content type for .rxml is now application/xml instead of type/xml, see http://www.xml.com/pub/a/2004/07/21/dive.html for reason *David Heinemeier Hansson*
+
+* Added option to render action/template/file of a specific extension (and here by template type). This means you can have multiple templates with the same name but a different extension *DHH*. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find :all
+
+ respond_to do |type|
+ type.html # using defaults, which will render weblog/index.rhtml
+ type.xml { render :action => "index.rxml" }
+ type.js { render :action => "index.rjs" }
+ end
+ end
+ end
+
+* Added better support for using the same actions to output for different sources depending on the Accept header *DHH*. Example:
+
+ class WeblogController < ActionController::Base
+ def create
+ @post = Post.create(params[:post])
+
+ respond_to do |type|
+ type.js { render } # renders create.rjs
+ type.html { redirect_to :action => "index" }
+ type.xml do
+ headers["Location"] = url_for(:action => "show", :id => @post)
+ render(:nothing, :status => "201 Created")
+ end
+ end
+ end
+ end
+
+* Added Base#render(:xml => xml) that works just like Base#render(:text => text), but sets the content-type to text/xml and the charset to UTF-8 *David Heinemeier Hansson*
+
+* Integration test's url_for now runs in the context of the last request (if any) so after post /products/show/1 url_for :action => 'new' will yield /product/new *Tobias Lütke*
+
+* Re-added mixed-in helper methods for the JavascriptGenerator. Moved JavascriptGenerators methods to a module that is mixed in after the helpers are added. Also fixed that variables set in the enumeration methods like #collect are set correctly. Documentation added for the enumeration methods *Rick Olson*. Examples:
+
+ page.select('#items li').collect('items') do |element|
+ element.hide
+ end
+ # => var items = $$('#items li').collect(function(value, index) { return value.hide(); });
+
+* Added plugin support for parameter parsers, which allows for better support for REST web services. By default, posts submitted with the application/xml content type is handled by creating a XmlSimple hash with the same name as the root element of the submitted xml. More handlers can easily be registered like this:
+
+ # Assign a new param parser to a new content type
+ ActionController::Base.param_parsers['application/atom+xml'] = Proc.new do |data|
+ node = REXML::Document.new(post)
+ { node.root.name => node.root }
+ end
+
+ # Assign the default XmlSimple to a new content type
+ ActionController::Base.param_parsers['application/backpack+xml'] = :xml_simple
+
+ Default YAML web services were retired, ActionController::Base.param_parsers carries an example which shows how to get this functionality back. As part of this new plugin support, request.[formatted_post?, xml_post?, yaml_post? and post_format] were all deprecated in favor of request.content_type *Tobias Lütke*
+* Fixed Effect.Appear in effects.js to work with floats in Safari #3524, #3813, #3044 *Thomas Fuchs*
+
+* Fixed that default image extension was not appended when using a full URL with AssetTagHelper#image_tag #4032, #3728 *rubyonrails@beautifulpixel.com*
+
+* Added that page caching will only happen if the response code is less than 400 #4033 *g.bucher@teti.ch*
+
+* Add ActionController::IntegrationTest to allow high-level testing of the way the controllers and routes all work together *Jamis Buck*
+
+* Added support to AssetTagHelper#javascript_include_tag for having :defaults appear anywhere in the list, so you can now make one call ala javascript_include_tag(:defaults, "my_scripts") or javascript_include_tag("my_scripts", :defaults) depending on how you want the load order #3506 *Bob Silva*
+
+* Added support for visual effects scoped queues to the visual_effect helper #3530 *Abdur-Rahman Advany*
+
+* Added .rxml (and any non-rhtml template, really) supportfor CaptureHelper#content_for and CaptureHelper#capture #3287 *Brian Takita*
+
+* Added script.aculo.us drag and drop helpers to RJS *Thomas Fuchs*. Examples:
+
+ page.draggable 'product-1'
+ page.drop_receiving 'wastebasket', :url => { :action => 'delete' }
+ page.sortable 'todolist', :url => { action => 'change_order' }
+
+* Fixed that form elements would strip the trailing [] from the first parameter #3545 *ruby@bobsilva.com*
+
+* During controller resolution, update the NameError suppression to check for the expected constant. *Nicholas Seckar*
+
+* Update script.aculo.us to V1.5.3 *Thomas Fuchs*
+
+* Added various InPlaceEditor options, #3746, #3891, #3896, #3906 *Bill Burcham, ruairi, sl33p3r*
+
+* Added :count option to pagination that'll make it possible for the ActiveRecord::Base.count call to using something else than * for the count. Especially important for count queries using DISTINCT #3839 *Stefan Kaes*
+
+* Update script.aculo.us to V1.5.2 *Thomas Fuchs*
+
+* Added element and collection proxies to RJS *DHH*. Examples:
+
+ page['blank_slate'] # => $('blank_slate');
+ page['blank_slate'].show # => $('blank_slate').show();
+ page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
+
+ 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();
+
+* Add JavaScriptGenerator#replace for replacing an element's "outer HTML". #3246 *tom@craz8.com, Sam Stephenson*
+
+* Remove over-engineered form_for code for a leaner implementation. *Nicholas Seckar*
+
+* Document form_for's :html option. *Nicholas Seckar*
+
+* Major components cleanup and speedup. #3527 *Stefan Kaes*
+
+* Fix problems with pagination and :include. *Kevin Clark*
+
+* Add ActiveRecordTestCase for testing AR integration. *Kevin Clark*
+
+* Add Unit Tests for pagination *Kevin Clark*
+
+* Add :html option for specifying form tag options in form_for. *Sam Stephenson*
+
+* Replace dubious controller parent class in filter docs. #3655, #3722 *info@rhalff.com, eigentone@gmail.com*
+
+* Don't interpret the :value option on text_area as an html attribute. Set the text_area's value. #3752 *gabriel@gironda.org*
+
+* Fix remote_form_for creates a non-ajax form. *Rick Olson*
+
+* Don't let arbitrary classes match as controllers -- a potentially dangerous bug. *Nicholas Seckar*
+
+* Fix Routing tests. Fix routing where failing to match a controller would prevent the rest of routes from being attempted. *Nicholas Seckar*
+
+* Add :builder => option to form_for and friends. *Nicholas Seckar, Rick Olson*
+
+* Fix controller resolution to avoid accidentally inheriting a controller from a parent module. *Nicholas Seckar*
+
+* Set sweeper's @controller to nil after a request so that the controller may be collected between requests. *Nicholas Seckar*
+
+* Subclasses of ActionController::Caching::Sweeper should be Reloadable. *Rick Olson*
+
+* Document the :xhr option for verifications. #3666 *leeo*
+
+* Added :only and :except controls to skip_before/after_filter just like for when you add filters *David Heinemeier Hansson*
+
+* Ensure that the instance variables are copied to the template when performing render :update. *Nicholas Seckar*
+
+* Add the ability to call JavaScriptGenerator methods from helpers called in update blocks. *Sam Stephenson* Example:
+ module ApplicationHelper
+ def update_time
+ page.replace_html 'time', Time.now.to_s(:db)
+ page.visual_effect :highlight, 'time'
+ end
+ end
+
+ class UserController < ApplicationController
+ def poll
+ render :update { |page| page.update_time }
+ end
+ end
+
+* Add render(:update) to ActionView::Base. *Sam Stephenson*
+
+* Fix render(:update) to not render layouts. *Sam Stephenson*
+
+* Fixed that SSL would not correctly be detected when running lighttpd/fcgi behind lighttpd w/mod_proxy #3548 *Steve Purcell*
+
+* Added the possibility to specify atomatic expiration for the memcachd session container #3571 *Stefan Kaes*
+
+* Change layout discovery to take into account the change in semantics with File.join and nil arguments. *Marcel Molina Jr.*
+
+* Raise a RedirectBackError if redirect_to :back is called when there's no HTTP_REFERER defined #3049 *Kevin Clark*
+
+* Treat timestamps like datetimes for scaffolding purposes #3388 *Maik Schmidt*
+
+* Fix IE bug with link_to "something", :post => true #3443 *Justin Palmer*
+
+* Extract Test::Unit::TestCase test process behavior into an ActionController::TestProcess module. *Sam Stephenson*
+
+* Pass along blocks from render_to_string to render. *Sam Stephenson*
+
+* Add render :update for inline RJS. *Sam Stephenson* Example:
+ class UserController < ApplicationController
+ def refresh
+ render :update do |page|
+ page.replace_html 'user_list', :partial => 'user', :collection => @users
+ page.visual_effect :highlight, 'user_list'
+ end
+ end
+ end
+
+* allow nil objects for error_messages_for *Michael Koziarski*
+
+* Refactor human_size to exclude decimal place if it is zero. *Marcel Molina Jr.*
+
+* Update to Prototype 1.5.0_pre0 *Sam Stephenson*
+
+* Automatically discover layouts when a controller is namespaced. #2199, #3424 *me@jonnii.com rails@jeffcole.net Marcel Molina Jr.*
+
+* Add support for multiple proxy servers to CgiRequest#host *gaetanot@comcast.net*
+
+* Documentation typo fix. #2367 *Blair Zajac*
+
+* Remove Upload Progress. #2871 *Sean Treadway*
+
+* Fix typo in function name mapping in auto_complete_field. #2929 #3446 *doppler@gmail.com phil.ross@gmail.com*
+
+* Allow auto-discovery of third party template library layouts. *Marcel Molina Jr.*
+
+* Have the form builder output radio button, not check box, when calling the radio button helper. #3331 *LouisStAmour@gmail.com*
+
+* Added assignment of the Autocompleter object created by JavaScriptMacroHelper#auto_complete_field to a local javascript variables *David Heinemeier Hansson*
+
+* Added :on option for PrototypeHelper#observe_field that allows you to specify a different callback hook to have the observer trigger on *David Heinemeier Hansson*
+
+* Added JavaScriptHelper#button_to_function that works just like JavaScriptHelper#link_to_function but uses a button instead of a href *David Heinemeier Hansson*
+
+* Added that JavaScriptHelper#link_to_function will honor existing :onclick definitions when adding the function call *David Heinemeier Hansson*
+
+* Added :disable_with option to FormTagHelper#submit_tag to allow for easily disabled submit buttons with different text *David Heinemeier Hansson*
+
+* Make auto_link handle nil by returning quickly if blank? *Scott Barron*
+
+* Make auto_link match urls with a port number specified. *Marcel Molina Jr.*
+
+* Added support for toggling visual effects to ScriptaculousHelper::visual_effect, #3323. *Thomas Fuchs*
+
+* Update to script.aculo.us to 1.5.0 rev. 3343 *Thomas Fuchs*
+
+* Added :select option for JavaScriptMacroHelper#auto_complete_field that makes it easier to only use part of the auto-complete suggestion as the value for insertion *Thomas Fuchs*
+
+* Added delayed execution of Javascript from within RJS #3264 *devslashnull@gmail.com*. Example:
+
+ page.delay(20) do
+ page.visual_effect :fade, 'notice'
+ end
+
+* Add session ID to default logging, but remove the verbose description of every step *David Heinemeier Hansson*
+
+* Add the following RJS methods: *Sam Stephenson*
+
+ * alert - Displays an alert() dialog
+ * redirect_to - Changes window.location.href to simulate a browser redirect
+ * call - Calls a JavaScript function
+ * assign - Assigns to a JavaScript variable
+ * << - Inserts an arbitrary JavaScript string
+
+* Fix incorrect documentation for form_for *Nicholas Seckar*
+
+* Don't include a layout when rendering an rjs template using render's :template option. *Marcel Molina Jr.*
+
+## 1.1.2 (December 13th, 2005) ##
+
+* Become part of Rails 1.0
+
+* Update to script.aculo.us 1.5.0 final (equals 1.5.0_rc6) *Thomas Fuchs*
+
+* Update to Prototype 1.4.0 final *Sam Stephenson*
+
+* Added form_remote_for (form_for meets form_remote_tag) *David Heinemeier Hansson*
+
+* Update to script.aculo.us 1.5.0_rc6
+
+* More robust relative url root discovery for SCGI compatibility. This solves the 'SCGI routes problem' -- you no longer need to prefix all your routes with the name of the SCGI mountpoint. #3070 *Dave Ringoen*
+
+* Fix docs for text_area_tag. #3083. *Christopher Cotton*
+
+* Change form_for and fields_for method signatures to take object name and object as separate arguments rather than as a Hash. *David Heinemeier Hansson*
+
+* Introduce :selected option to the select helper. Allows you to specify a selection other than the current value of object.method. Specify :selected => nil to leave all options unselected. #2991 *Jonathan Viney <jonathan@bluewire.net.nz>*
+
+* Initialize @optional in routing code to avoid warnings about uninitialized access to an instance variable. *Nicholas Seckar*
+
+* Make ActionController's render honor the :locals option when rendering a :file. #1665. *Emanuel Borsboom, Marcel Molina Jr.*
+
+* Allow assert_tag(:conditions) to match the empty string when a tag has no children. Closes #2959. *Jamis Buck*
+
+* Update html-scanner to handle CDATA sections better. Closes #2970. *Jamis Buck*
+
+* Don't put flash in session if sessions are disabled. *Jeremy Kemper*
+
+* Strip out trailing &_= for raw post bodies. Closes #2868. *Sam Stephenson*
+
+* Pass multiple arguments to Element.show and Element.hide in JavaScriptGenerator instead of using iterators. *Sam Stephenson*
+
+* Improve expire_fragment documentation. #2966 *court3nay*
+
+* Correct docs for automatic layout assignment. #2610. *Charles M. Gerungan*
+
+* Always create new AR sessions rather than trying too hard to avoid database traffic. #2731 *Jeremy Kemper*
+
+* Update to Prototype 1.4.0_rc4. Closes #2943 (old Array.prototype.reverse behavior can be obtained by passing false as an argument). *Sam Stephenson*
+
+* Use Element.update('id', 'html') instead of $('id').innerHTML = 'html' in JavaScriptGenerator#replace_html so that script tags are evaluated. *Sam Stephenson*
+
+* Make rjs templates always implicitly skip out on layouts. *Marcel Molina Jr.*
+
+* Correct length for the truncate text helper. #2913 *Stefan Kaes*
+
+* Update to Prototype 1.4.0_rc3. Closes #1893, #2505, #2550, #2748, #2783. *Sam Stephenson*
+
+* Add support for new rjs templates which wrap an update_page block. *Marcel Molina Jr.*
+
+* Rename Version constant to VERSION. #2802 *Marcel Molina Jr.*
+
+* Correct time_zone_options_for_select docs. #2892 *pudeyo@rpi.com*
+
+* Remove the unused, slow response_dump and session_dump variables from error pages. #1222 *lmarlow*
+
+* Performance tweaks: use Set instead of Array to speed up prototype helper include? calls. Avoid logging code if logger is nil. Inline commonly-called template presence checks. #2880, #2881, #2882, #2883 *Stefan Kaes*
+
+* MemCache store may be given multiple addresses. #2869 *Ryan Carver <ryan@fivesevensix.com>*
+
+* Handle cookie parsing irregularity for certain Nokia phones. #2530 *zaitzow@gmail.com*
+
+* Added PrototypeHelper::JavaScriptGenerator and PrototypeHelper#update_page for easily modifying multiple elements in an Ajax response. *Sam Stephenson* Example:
+
+ update_page do |page|
+ page.insert_html :bottom, 'list', '<li>Last item</li>'
+ page.visual_effect :highlight, 'list'
+ page.hide 'status-indicator', 'cancel-link'
+ end
+
+ generates the following JavaScript:
+
+ new Insertion.Bottom("list", "<li>Last item</li>");
+ new Effect.Highlight("list");
+ ["status-indicator", "cancel-link"].each(Element.hide);
+
+* Refactored JavaScriptHelper into PrototypeHelper and ScriptaculousHelper *Sam Stephenson*
+
+* Update to latest script.aculo.us version (as of [3031])
+
+* Updated docs for in_place_editor, fixes a couple bugs and offers extended support for external controls *Justin Palmer*
+
+* Update documentation for render :file. #2858 *Tom Werner*
+
+* Only include builtin filters whose filenames match /^[a-z][a-z_]*_helper.rb$/ to avoid including operating system metadata such as ._foo_helper.rb. #2855 *court3nay*
+
+* Added FormHelper#form_for and FormHelper#fields_for that makes it easier to work with forms for single objects also if they don't reside in instance variables *DHH*. Examples:
+
+ <% form_for :person, @person, :url => { :action => "update" } do |f| %>
+ First name: <%= f.text_field :first_name %>
+ Last name : <%= f.text_field :last_name %>
+ Biography : <%= f.text_area :biography %>
+ Admin? : <%= f.check_box :admin %>
+ <% end %>
+
+ <% form_for :person, person, :url => { :action => "update" } do |person_form| %>
+ First name: <%= person_form.text_field :first_name %>
+ Last name : <%= person_form.text_field :last_name %>
+
+ <% fields_for :permission => person.permission do |permission_fields| %>
+ Admin? : <%= permission_fields.check_box :admin %>
+ <% end %>
+ <% end %>
+
+* options_for_select allows any objects which respond_to? :first and :last rather than restricting to Array and Range. #2824 *Jacob Robbins <jrobbins@cmj.com>, Jeremy Kemper*
+
+* The auto_link text helper accepts an optional block to format the link text for each url and email address. Example: auto_link(post.body) { |text| truncate(text, 10) } *Jeremy Kemper*
+
+* assert_tag uses exact matches for string conditions, instead of partial matches. Use regex to do partial matches. #2799 *Jamis Buck*
+
+* CGI::Session::ActiveRecordStore.data_column_name = 'foobar' to use a different session data column than the 'data' default. *nbpwie102@sneakemail.com*
+
+* Do not raise an exception when default helper is missing; log a debug message instead. It's nice to delete empty helpers. *Jeremy Kemper*
+
+* Controllers with acronyms in their names (e.g. PDFController) require the correct default helper (PDFHelper in file pdf_helper.rb). #2262 *jeff@opendbms.com*
+
+
+## 1.11.0 (November 7th, 2005) ##
+
+* Added request as instance method to views, so you can do <%= request.env["HTTP_REFERER"] %>, just like you can already access response, session, and the likes *David Heinemeier Hansson*
+
+* Fix conflict with assert_tag and Glue gem #2255 *david.felstead@gmail.com*
+
+* Add documentation to assert_tag indicating that it only works with well-formed XHTML #1937, #2570 *Jamis Buck*
+
+* Added action_pack.rb stub so that ActionPack::Version loads properly *Sam Stephenson*
+
+* Added short-hand to assert_tag so assert_tag :tag => "span" can be written as assert_tag "span" *David Heinemeier Hansson*
+
+* Added skip_before_filter/skip_after_filter for easier control of the filter chain in inheritance hierachies *DHH*. Example:
+
+ class ApplicationController < ActionController::Base
+ before_filter :authenticate
+ end
+
+ class WeblogController < ApplicationController
+ # will run the :authenticate filter
+ end
+
+ class SignupController < ActionController::Base
+ # will not run the :authenticate filter
+ skip_before_filter :authenticate
+ end
+
+* Added redirect_to :back as a short-hand for redirect_to(request.env["HTTP_REFERER"]) *David Heinemeier Hansson*
+
+* Change javascript_include_tag :defaults to not use script.aculo.us loader, which facilitates the use of plugins for future script.aculo.us and third party javascript extensions, and provide register_javascript_include_default for plugins to specify additional JavaScript files to load. Removed slider.js and builder.js from actionpack. *Thomas Fuchs*
+
+* Fix problem where redirecting components can cause an infinite loop *Rick Olson*
+
+* Added support for the queue option on visual_effect *Thomas Fuchs*
+
+* Update script.aculo.us to V1.5_rc4 *Thomas Fuchs*
+
+* Fix that render :text didn't interpolate instance variables #2629, #2626 *Stefan Kaes*
+
+* Fix line number detection and escape RAILS_ROOT in backtrace Regexp *Nicholas Seckar*
+
+* Fixed document.getElementsByClassName from Prototype to be speedy again *Sam Stephenson*
+
+* Recognize ./#{RAILS_ROOT} as RAILS_ROOT in error traces *Nicholas Seckar*
+
+* Remove ARStore session fingerprinting *Nicholas Seckar*
+
+* Fix obscure bug in ARStore *Nicholas Seckar*
+
+* Added TextHelper#strip_tags for removing HTML tags from a string (using HTMLTokenizer) #2229 *marcin@junkheap.net*
+
+* Added a reader for flash.now, so it's possible to do stuff like flash.now[:alert] ||= 'New if not set' #2422 *Caio Chassot*
+
+
+## 1.10.2 (October 26th, 2005) ##
+
+* Reset template variables after using render_to_string *Stefan Kaes*
+
+* Expose the session model backing CGI::Session
+
+* Abbreviate RAILS_ROOT in traces
+
+
+## 1.10.1 (October 19th, 2005) ##
+
+* Update error trace templates *Nicholas Seckar*
+
+* Stop showing generated routing code in application traces *Nicholas Seckar*
+
+
+## 1.10.0 (October 16th, 2005) ##
+
+* Make string-keys locals assigns optional. Add documentation describing depreciated state *Stefan Kaes*
+
+* Improve line number detection for template errors *Nicholas Seckar*
+
+* Update/clean up documentation (rdoc)
+
+* Upgrade to Prototype 1.4.0_rc0 *Sam Stephenson*
+
+* Added assert_vaild. Reports the proper AR error messages as fail message when the passed record is invalid *Tobias Lütke*
+
+* Add temporary support for passing locals to render using string keys *Nicholas Seckar*
+
+* Clean up error pages by providing better backtraces *Nicholas Seckar*
+
+* Raise an exception if an attempt is made to insert more session data into the ActiveRecordStore data column than the column can hold. #2234. *justin@textdrive.com*
+
+* Removed references to assertions.rb from actionpack assert's backtraces. Makes error reports in functional unit tests much less noisy. *Tobias Lütke*
+
+* Updated and clarified documentation for JavaScriptHelper to be more concise about the various options for including the JavaScript libs. *Thomas Fuchs*
+
+* Hide "Retry with Breakpoint" button on error pages until feature is functional. *David Heinemeier Hansson*
+
+* Fix Request#host_with_port to use the standard port when Rails is behind a proxy. *Nicholas Seckar*
+
+* Escape query strings in the href attribute of URLs created by url_helper. #2333 *Michael Schuerig <michael@schuerig.de>*
+
+* Improved line number reporting for template errors *Nicholas Seckar*
+
+* Added :locals support for render :inline #2463 *mdabney@cavoksolutions.com*
+
+* Unset the X-Requested-With header when using the xhr wrapper in functional tests so that future requests aren't accidentally xhr'ed #2352 *me@julik.nl, Sam Stephenson*
+
+* Unescape paths before writing cache to file system. #1877. *Damien Pollet*
+
+* Wrap javascript_tag contents in a CDATA section and add a cdata_section method to TagHelper #1691 *Michael Schuerig, Sam Stephenson*
+
+* Misc doc fixes (typos/grammar/etc). #2445. *coffee2code*
+
+* Speed improvement for session_options. #2287. *Stefan Kaes*
+
+* Make cacheing binary files friendly with Windows. #1975. *Rich Olson*
+
+* Convert boolean form options form the tag_helper. #809. *Michael Schuerig <michael@schuerig.de>*
+
+* Fixed that an instance variable with the same name as a partial should be implicitly passed as the partial :object #2269 *court3nay*
+
+* Update Prototype to V1.4.0_pre11, script.aculo.us to [2502] *Thomas Fuchs*
+
+* Make assert_tag :children count appropriately. Closes #2181. *jamie@bravenet.com*
+
+* Forced newer versions of RedCloth to use hard breaks *David Heinemeier Hansson*
+
+* Added new scriptaculous options for auto_complete_field #2343 *Manfred Stienstra*
+
+* Don't prepend the asset host if the string is already a fully-qualified URL
+
+* Updated to script.aculo.us V1.5.0_rc2 and Prototype to V1.4.0_pre7 *Thomas Fuchs*
+
+* Undo condition change made in [2345] to prevent normal parameters arriving as StringIO.
+
+* Tolerate consecutive delimiters in query parameters. #2295 *darashi@gmail.com*
+
+* Streamline render process, code cleaning. Closes #2294. *skae*
+
+* Keep flash after components are rendered. #2291 *Rick Olson, Scott*
+
+* Shorten IE file upload path to filename only to match other browsers. #1507 *court3nay*
+
+* Fix open/save dialog in IE not opening files send with send_file/send_data, #2279 *Thomas Fuchs*
+
+* Fixed that auto_discovery_link_tag couldn't take a string as the URL *David Heinemeier Hansson*
+
+* Fixed problem with send_file and WEBrick using stdout #1812 *David Heinemeier Hansson*
+
+* Optimized tag_options to not sort keys, which is no longer necessary when assert_dom_equal and friend is available #1995 *skae*
+
+* Added assert_dom_equal and assert_dom_not_equal to compare tags generated by the helpers in an order-indifferent manner #1995 *skae*
+
+* Fixed that Request#domain caused an exception if the domain header wasn't set in the original http request #1795 *Michael Koziarski*
+
+* Make the truncate() helper multi-byte safe (assuming $KCODE has been set to something other than "NONE") #2103
+
+* Add routing tests from #1945 *ben@groovie.org*
+
+* Add a routing test case covering #2101 *Nicholas Seckar*
+
+* Cache relative_url_root for all webservers, not just Apache #2193 *skae*
+
+* Speed up cookie use by decreasing string copying #2194 *skae*
+
+* Fixed access to "Host" header with requests made by crappy old HTTP/1.0 clients #2124 *Marcel Molina Jr.*
+
+* Added easy assignment of fragment cache store through use of symbols for included stores (old way still works too)
+
+ Before:
+ ActionController::Base.fragment_cache_store =
+ ActionController::Base::Caching::Fragments::FileStore.new("/path/to/cache/directory")
+
+ After:
+ ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
+
+* Added ActionController::Base.session_store=, session_store, and session_options to make it easier to tweak the session options (instead of going straight to ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS)
+
+* Added TextHelper#cycle to cycle over an array of values on each hit (useful for alternating row colors etc) #2154 *dave-ml@dribin.org*
+
+* Ensure that request.path never returns nil. Closes #1675 *Nicholas Seckar*
+
+* Add ability to specify Route Regexps for controllers. Closes #1917. *Sebastian Kanthak*
+
+* Provide Named Route's hash methods as helper methods. Closes #1744. *Nicholas Seckar, Steve Purcell*
+
+* Added :multipart option to ActiveRecordHelper#form to make it possible to add file input fields #2034 *jstirk@oobleyboo.com*
+
+* Moved auto-completion and in-place editing into the Macros module and their helper counterparts into JavaScriptMacrosHelper
+
+* Added in-place editing support in the spirit of auto complete with ActionController::Base.in_place_edit_for, JavascriptHelper#in_place_editor_field, and Javascript support from script.aculo.us #2038 *Jon Tirsen*
+
+* Added :disabled option to all data selects that'll make the elements inaccessible for change #2167, #253 *eigentone*
+
+* Fixed that TextHelper#auto_link_urls would include punctuation in the links #2166, #1671 *eigentone*
+
+* Fixed that number_to_currency(1000, {:precision => 0})) should return "$1,000", instead of "$1,000." #2122 *sd@notso.net*
+
+* Allow link_to_remote to use any DOM-element as the parent of the form elements to be submitted #2137 *erik@ruby-lang.nl*. Example:
+
+ <tr id="row023">
+ <td><input name="foo"/></td>
+ <td><input name="bar"/></td>
+ <td><%= link_to_remote 'Save', :update => "row023",
+ :submit => "row023", :url => {:action => 'save_row'} %></td>
+ </tr>
+
+* Fixed that render :partial would fail when :object was a Hash (due to backwards compatibility issues) #2148 *Sam Stephenson*
+
+* Fixed JavascriptHelper#auto_complete_for to only include unique items #2153 *Thomas Fuchs*
+
+* Fixed all AssetHelper methods to work with relative paths, such that javascript_include_tag('stdlib/standard') will look in /javascripts/stdlib/standard instead of '/stdlib/standard/' #1963
+
+* Avoid extending view instance with helper modules each request. Closes #1979
+
+* Performance improvements to CGI methods. Closes #1980 *Stefan Kaes*
+
+* Added :post option to UrlHelper#link_to that makes it possible to do POST requests through normal ahref links using Javascript
+
+* Fixed overwrite_params
+
+* Added ActionController::Base.benchmark and ActionController::Base.silence to allow for easy benchmarking and turning off the log
+
+* Updated vendor copy of html-scanner to support better xml parsing
+
+* Added :popup option to UrlHelper#link_to #1996 *gabriel.gironda@gmail.com*. Examples:
+
+ link_to "Help", { :action => "help" }, :popup => true
+ link_to "Busy loop", { :action => "busy" }, :popup => ['new_window', 'height=300,width=600']
+
+* Drop trailing \000 if present on RAW_POST_DATA (works around bug in Safari Ajax implementation) #918
+
+* Fix observe_field to fall back to event-based observation if frequency <= 0 #1916 *Michael Schubert*
+
+* Allow use of the :with option for submit_to_remote #1936 *jon@instance-design.co.uk*
+
+* AbstractRequest#domain returns nil when host is an ip address #2012 *Kevin Clark*
+
+* ActionController documentation update #2051 *François Beausoleil*
+
+* Yield @content_for_ variables to templates #2058 *Sam Stephenson*
+
+* Make rendering an empty partial collection behave like :nothing => true #2080 *Sam Stephenson*
+
+* Add option to specify the singular name used by pagination.
+
+* Use string key to obtain action value. Allows indifferent hashes to be disabled.
+
+* Added ActionView::Base.cache_template_loading back.
+
+* Rewrote compiled templates to decrease code complexity. Removed template load caching in favour of compiled caching. Fixed template error messages. *Nicholas Seckar*
+
+* Fix Routing to handle :some_param => nil better. *Nicholas Seckar, Luminas*
+
+* Add support for :include with pagination (subject to existing constraints for :include with :limit and :offset) #1478 *Michael Schubert*
+
+* Prevent the benchmark module from blowing up if a non-HTTP/1.1 request is processed
+
+* Added :use_short_month option to select_month helper to show month names as abbreviations
+
+* Make link_to escape the javascript in the confirm option #1964 *nicolas.pouillard@gmail.com*
+
+* Make assert_redirected_to properly check URL's passed as strings #1910 *Scott Barron*
+
+* Make sure :layout => false is always used when rendering inside a layout
+
+* Use raise instead of assert_not_nil in Test::Unit::TestCase#process to ensure that the test variables (controller, request, response) have been set
+
+* Make sure assigns are built for every request when testing #1866
+
+* Allow remote_addr to be queried on TestRequest #1668
+
+* Fixed bug when a partial render was passing a local with the same name as the partial
+
+* Improved performance of test app req/sec with ~10% refactoring the render method #1823 *Stefan Kaes*
+
+* Improved performance of test app req/sec with 5-30% through a series of Action Pack optimizations #1811 *Stefan Kaes*
+
+* Changed caching/expiration/hit to report using the DEBUG log level and errors to use the ERROR log level instead of both using INFO
+
+* Added support for per-action session management #1763
+
+* Improved rendering speed on complicated templates by up to 100% (the more complex the templates, the higher the speedup) #1234 *Stefan Kaes*. This did necessasitate a change to the internals of ActionView#render_template that now has four parameters. Developers of custom view handlers (like Amrita) need to update for that.
+
+* Added options hash as third argument to FormHelper#input, so you can do input('person', 'zip', :size=>10) #1719 *jeremye@bsa.ca.gov*
+
+* Added Base#expires_in(seconds)/Base#expires_now to control HTTP content cache headers #1755 *Thomas Fuchs*
+
+* Fixed line number reporting for Builder template errors #1753 *piotr*
+
+* Fixed assert_routing so that testing controllers in modules works as expected *Nicholas Seckar, Rick Olson*
+
+* Fixed bug with :success/:failure callbacks for the JavaScriptHelper methods #1730 *court3nay/Thomas Fuchs*
+
+* Added named_route method to RouteSet instances so that RouteSet instance methods do not prevent certain names from being used. *Nicholas Seckar*
+
+* Fixed routes so that routes which do not specify :action in the path or in the requirements have a default of :action => 'index', In addition, fixed url generation so that :action => 'index' does not need to be provided for such urls. *Nicholas Seckar, Markjuh*
+
+* Worked around a Safari bug where it wouldn't pass headers through if the response was zero length by having render :nothing return ' ' instead of ''
+
+* Fixed Request#subdomains to handle "foo.foo.com" correctly
+
+
+## 1.9.1 (11 July, 2005) ##
+
+* Fixed that auto_complete_for didn't force the input string to lower case even as the db comparison was
+
+* Fixed that Action View should always use the included Builder, never attempt to require the gem, to ensure compatibility
+
+* Added that nil options are not included in tags, so tag("p", :ignore => nil) now returns <p /> not <p ignore="" /> but that tag("p", :ignore => "") still includes it #1465 *Michael Schuerig*
+
+* Fixed that UrlHelper#link_to_unless/link_to_if used html_escape on the name if no link was to be applied. This is unnecessary and breaks its use with images #1649 *joergd@pobox.com*
+
+* Improved error message for DoubleRenderError
+
+* Fixed routing to allow for testing of *path components #1650 *Nicholas Seckar*
+
+* Added :handle as an option to sortable_element to restrict the drag handle to a given class #1642 *thejohnny*
+
+* Added a bunch of script.aculo.us features #1644, #1677, #1695 *Thomas Fuchs*
+ * Effect.ScrollTo, to smoothly scroll the page to an element
+ * Better Firefox flickering handling on SlideUp/SlideDown
+ * Removed a possible memory leak in IE with draggables
+ * Added support for cancelling dragging my hitting ESC
+ * Added capability to remove draggables/droppables and redeclare sortables in dragdrop.js (this makes it possible to call sortable_element on the same element more than once, e.g. in AJAX returns that modify the sortable element. all current sortable 'stuff' on the element will be discarded and the sortable will be rebuilt)
+ * Always reset background color on Effect.Highlight; this make change backwards-compatibility, to be sure include style="background-color:(target-color)" on your elements or else elements will fall back to their CSS rules (which is a good thing in most circumstances)
+ * Removed circular references from element to prevent memory leaks (still not completely gone in IE)
+ * Changes to class extension in effects.js
+ * Make Effect.Highlight restore any previously set background color when finishing (makes effect work with CSS classes that set a background color)
+ * Fixed myriads of memory leaks in IE and Gecko-based browsers *David Zülke*
+ * Added incremental and local autocompleting and loads of documentation to controls.js *Ivan Krstic*
+ * Extended the auto_complete_field helper to accept tokens option
+ * Changed object extension mechanism to favor Object.extend to make script.aculo.us easily adaptable to support 3rd party libs like IE7.js *David Zülke*
+
+* Fixed that named routes didn't use the default values for action and possible other parameters #1534 *Nicholas Seckar*
+
+* Fixed JavascriptHelper#visual_effect to use camelize such that :blind_up will work #1639 *pelletierm@eastmedia.net*
+
+* Fixed that a SessionRestoreError was thrown if a model object was placed in the session that wasn't available to all controllers. This means that it's no longer necessary to use the 'model :post' work-around in ApplicationController to have a Post model in your session.
+
+
+## 1.9.0 (6 July, 2005) ##
+
+* Added logging of the request URI in the benchmark statement (makes it easy to grep for slow actions)
+
+* Added javascript_include_tag :defaults shortcut that'll include all the default javascripts included with Action Pack (prototype, effects, controls, dragdrop)
+
+* Cache several controller variables that are expensive to calculate #1229 *Stefan Kaes*
+
+* The session class backing CGI::Session::ActiveRecordStore may be replaced with any class that duck-types with a subset of Active Record. See docs for details #1238 *Stefan Kaes*
+
+* Fixed that hashes was not working properly when passed by GET to lighttpd #849 *Nicholas Seckar*
+
+* Fixed assert_template nil will be true when no template was rendered #1565 *maceywj@telus.net*
+
+* Added :prompt option to FormOptions#select (and the users of it, like FormOptions#select_country etc) to create "Please select" style descriptors #1181 *Michael Schuerig*
+
+* Added JavascriptHelper#update_element_function, which returns a Javascript function (or expression) that'll update a DOM element according to the options passed #933 *mortonda@dgrmm.net*. Examples:
+
+ <%= update_element_function("products", :action => :insert, :position => :bottom, :content => "<p>New product!</p>") %>
+
+ <% update_element_function("products", :action => :replace, :binding => binding) do %>
+ <p>Product 1</p>
+ <p>Product 2</p>
+ <% end %>
+
+* Added :field_name option to DateHelper#select_(year|month|day) to deviate from the year/month/day defaults #1266 *Marcel Molina Jr.*
+
+* Added JavascriptHelper#draggable_element and JavascriptHelper#drop_receiving_element to facilitate easy dragging and dropping through the script.aculo.us libraries #1578 *Thomas Fuchs*
+
+* Added that UrlHelper#mail_to will now also encode the default link title #749 *f.svehla@gmail.com*
+
+* Removed the default option of wrap=virtual on FormHelper#text_area to ensure XHTML compatibility #1300 *thomas@columbus.rr.com*
+
+* Adds the ability to include XML CDATA tags using Builder #1563 *Josh Knowles*. Example:
+
+ xml.cdata! "some text" # => <![CDATA[some text]]>
+
+* Added evaluation of SCRIPT blocks in content returned to Ajax calls #1577 *Thomas Fuchs/court3nay/Sean Treadway*
+
+* Directly generate paths with a leading slash instead of tacking it on later. #1543 *Nicholas Seckar*
+
+* Fixed errant parameter modification in functional tests. #1542 *Nicholas Seckar*
+
+* Routes fail with leading slash #1540 *Nicholas Seckar*
+
+* Added support for graceful error handling of Ajax calls #1217 *Jamis Buck/Thomas Fuchs*. Example:
+
+ link_to_remote(
+ "test",
+ :url => { :action => "faulty" },
+ :update => { :success => "good", :failure => "bad" },
+ 403 => "alert('Forbidden- got ya!')",
+ 404 => "alert('Nothing there...?')",
+ :failure => "alert('Unkown error ' + request.status)")
+
+* Attempt to explicitly flush the output at the end of CgiProcess#out
+
+* Fixed assert_redirected_to to handle absolute controller paths properly #1472 *Rick Olson/Nicholas Seckar*
+
+* Added event-based observations when frequency is not set on observe_field/form #1474 *flash@vanklinkenbergsoftware.nl*
+
+* Added script.aculo.us Javascripts (controls.js, dragdrop.js, effects.js) (NEEDS MORE DESCRIPTION) #1509 *Thomas Fuchs*
+
+* Fixed prototype to consider all fields it doesn't know as text (such as Safari's search) just like the browser in its serialization #1497 *Sean Treadway*
+
+* Improved performance of Routes generation by a factor of 5 #1434 *Nicholas Seckar*
+
+* Added named routes (NEEDS BETTER DESCRIPTION) #1434 *Nicholas Seckar*
+
+* Improved AbstractRequest documentation #1483 *court3nay*
+
+* Added ActionController::Base.allow_concurrency to control whether the application is thread-safe, so multi-threaded servers like WEBrick knows whether to apply a mutex around the performance of each action. Turned off by default. EXPERIMENTAL FEATURE.
+
+* Added TextHelper#word_wrap(text, line_length = 80) #1449 *tuxie@dekadance.se*
+
+* Added a fall-through action for form_remote_tag that'll be used in case Javascript is unavailable #1459 *Scott Barron*. Example:
+
+ form_remote_tag :html => { :action => url_for(:controller => "some", :action => "place") }
+
+* Added :xhr => true/false option to verify so you can ensure that a request is coming from an Ajax call or not #1464 *Thomas Fuchs*
+
+* Added tag_options as a third parameter to AssetHelper#auto_discovery_link_tag to control options like the title of the link #1430 *Kevin Clark*
+
+* Added option to pass in parameters to CaptureHelper#capture, so you can create more advanced view helper methods #1466 *duane.johnson@gmail.com*. Example:
+
+ <% show_calendar(:year => 2005, :month => 6) do |day, options| %>
+ <% options[:bgcolor] = '#dfd' if 10..15.include? day %>
+ [<%= day %>]
+ <% end %>
+
+* Changed the default name of the input tag generated by FormTagHelper#submit_tag from "submit" to "commit" so it doesn't clash with form.submit() calls in Javascript #1271
+
+* Fixed relative urls support for lighttpd #1048 *Nicholas Seckar/maznawak@nerim.net*
+
+* Correct distance_of_time_in_words for integer arguments and make the second arg optional, treating the first arg as a duration in seconds. #1458 *madrobby <thomas@fesch.at>*
+
+* Fixed query parser to deal gracefully with equal signs inside keys and values #1345 *gorou*.
+ Example: /?sig=abcdef=:foobar=&x=y will pass now.
+
+* Added Cuba to country list #1351 *todd*
+
+* Fixed radio_button to work with numeric values #1352 *demetrius*
+
+* Added :extension option to NumberHelper#number_to_phone #1361 *delynnb*
+
+* Added button_to as a form-based solution to deal with harmful actions that should be hidden behind POSTs. This makes it just as easy as link_to to create a safe trigger for actions like destroy, although it's limited by being a block element, the fixed look, and a no-no inside other forms. #1371 *tom@moertel.com*
+
+* Fixed image_tag so an exception is not thrown just because the image is missing and alt value can't be generated #1395 *Marcel Molina Jr.*
+
+* Added a third parameter to TextHelper#auto_link called href_options for specifying additional tag options on the links generated #1401 *tyler.kovacs@gmail.com*. Example: auto_link(text, :all, { :target => "_blank" }) to have all the generated links open in a new window.
+
+* Fixed TextHelper#highlight to return the text, not nil, if the phrase is blank #1409 *Patrick Lenz*
+
+* Fixed TagHelper such that :name and 'name' keys in the options doesn't result in two attributes #1455 *take_tk*
+
+* Ensure that helpers are only available to the controllers where they are defined and their subclasses. #1394 *kdole@tamu.edu*
+
+* render("foo/bar") works with a layout again
+
+* Fixed double-singularization on scaffolded pagination call (Address would be turned into Addres) #1216, #1404 *nilsga*
+
+* Removed the require hack used by functional testing to work around an earlier bug in rake.
+
+* Allow distance_of_time_in_words to work with any value that responds to #to_time (like dates) #969
+
+* Support :render option for :verify #1440 *Tobias Lütke*
+
+* Updated vendor copy of html-scanner lib to 0.5.2, for bug fixes and optimizations. The :content option may be used as expected--to find a tag whose textual content is a particular value--in assert_tag, now.
+
+* Changed test requests to come from 0.0.0.0 instead of 127.0.0.1 such that they don't trigger debugging screens on exceptions, but instead call rescue_action_in_public
+
+* Modernize scaffolding to match the generator: use the new render method and change style from the warty @params["id"] to the sleek params[:id]. #1367
+
+* Include :id in the action generated by the form helper method. Then, for example, the controller can do Model.find(params[:id]) for both edit and update actions. Updated scaffolding to take advantage. #1367
+
+* Add assertions with friendly messages to TestCase#process to ensure that @controller, @request, and @response are set. #1367
+
+* Arrays, hashes sent via multipart posts are converted to strings #1032 *dj@omelia.org, me@julik.nl*
+
+* render(:layout => true) is a synonym for render(:layout => nil)
+
+* Make sure the benchmarking render method always returns the output of the render.
+
+* render(:action), render(:template) and render() are the only three calls that default to using a layout. All other render calls assume :layout => false. This also fixes send_file, which was applying a layout if one existed for the current action.
+
+* verify with :redirect_to won't redirect if a redirect or render has already been performed #1350
+
+* render(:partial => true) is identical to the behavior of the deprecated render_partial()
+
+* Fixed render(:partial => "...") to use an empty Hash for the local assigns #1365
+
+* Fixed Caching::Fragments::FileStore.delete to not raise an exception if the delete fails.
+
+* Deprecated all render_* methods in favor of consolidating all rendering behavior in Base#render(options). This enables more natural use of combining options, such as layouts. Examples:
+
+ +---------------------------------------------------------------+-------------------------------------------------------+
+ | BEFORE | AFTER |
+ +---------------------------------------------------------------+-------------------------------------------------------+
+ | render_with_layout "weblog/show", "200 OK", "layouts/dialog" | render :action => "show", :layout => "dialog" |
+ | render_without_layout "weblog/show" | render :action => "show", :layout => false |
+ | render_action "error", "404 Not Found" | render :action => "error", :status => "404 Not Found" |
+ | render_template "xml.div('stuff')", "200 OK", :rxml | render :inline => "xml.div('stuff')", :type => :rxml |
+ | render_text "hello world!" | render :text => "hello world!" |
+ | render_partial_collection "person", @people, nil, :a => 1 | render :partial => "person", :collection => @people, |
+ | | :locals => { :a => 1 } |
+ +---------------------------------------------------------------+-------------------------------------------------------+
+
+* Deprecated redirect_to_path and redirect_to_url in favor of letting redirect_to do the right thing when passed either a path or url.
+
+* Fixed use of an integer as return code for renders, so render_text "hello world", 404 now works #1327
+
+* Fixed assert_redirect_to to work with redirect_to_path #869 *Nicholas Seckar*
+
+* Fixed escaping of :method option in remote_form_tag #1218 *Rick Olson*
+
+* Added Serbia and Montenegro to the country_select #1239 *todd@robotcoop.com*
+
+* Fixed Request#remote_ip in testing #1251 *Jeremy Kemper*
+
+* Fixed that compute_public_path should recognize external URLs, so image_tag("http://www.example.com/images/icon.gif") is not prefixed with the relative url path #1254 *victor-ronr-trac@carotena.net*
+
+* Added support for descending year values in DateHelper#select_year, like select_year(Date.today, :start_year => 2005, :end_year => 1900), which would count down from 2005 to 1900 instead of the other way #1274 *nwoods@mail.com*
+
+* Fixed that FormHelper#checkbox should return a checked checkbox if the value is the same as checked_value #1286 *Florian Weber*
+
+* Fixed Form.disable in Prototype #1317 *Wintermute*
+
+* Added accessors to logger, params, response, session, flash, and headers from the view, so you can write <% logger.info "stuff" %> instead of <% @logger.info "others" %> -- more consistent with the preferred way of accessing these attributes and collections from the controller
+
+* Added support for POST data in form of YAML or XML, which is controller through the Content-Type header. Example request:
+
+ Content-Type: application/xml
+ <request><item><content>HelloWorld</content></item></request>
+
+ ...is the same as:
+
+ Content-Type: application/x-yaml
+ ---
+ item:
+ content: HelloWorld
+
+ ...is the same as:
+
+ item[content]=HelloWorld
+
+ Which in the end turns into { "item" => { "content" => "HelloWorld" } }. This makes it a lot easier to publish REST web services on top of your regular actions (as they won't care).
+
+ Example Curl call:
+
+ curl -H 'Content-Type: application/xml' -d '<request><item><content>KillMeMore</content></item></request>' http://www.example.com/service
+
+ You can query to find out whether a given request came through as one of these types with:
+ - request.post_format? (:url_encoded, :xml or :yaml)
+ - request.formatted_post? (for either xml or yaml)
+ - request.xml_post?
+ - request.yaml_post?
+
+* Added bundling of XmlSimple by Maik Schmidt
+
+* Fixed that render_partial_collection should always return a string (and not sometimes an array, despite <%= %> not caring)
+
+* Added TextHelper#sanitize that can will remove any Javascript handlers, blocks, and forms from an input of HTML. This allows for use of HTML on public sites, but still be free of XSS issues. #1277 *Jamis Buck*
+
+* Fixed the HTML scanner used by assert_tag where a infinite loop could be caused by a stray less-than sign in the input #1270 *Jamis Buck*
+
+* Added functionality to assert_tag, so you can now do tests on the siblings of a node, to assert that some element comes before or after the element in question, or just to assert that some element exists as a sibling #1226 *Jamis Buck*
+
+* Added better error handling for regexp caching expiration
+
+* Fixed handling of requests coming from unknown HTTP methods not to kill the server
+
+* Added that both AssetHelper#stylesheet_link_tag and AssetHelper#javascript_include_tag now accept an option hash as the last parameter, so you can do stuff like: stylesheet_link_tag "style", :media => "all"
+
+* Added FormTagHelper#image_submit_tag for making submit buttons that uses images
+
+* Added ActionController::Base.asset_host that will then be used by all the asset helpers. This enables you to easily offload static content like javascripts and images to a separate server tuned just for that.
+
+* Fixed action/fragment caching using the filestore when a directory and a file wanted to use the same name. Now there's a .cache prefix that sidesteps the conflict #1188 *imbcmdth@hotmail.com*
+
+* Fixed missing id uniqueness in FormTag#radio_button #1207 *Jarkko Laine*
+
+* Fixed assert_redirected_to to work with :only_path => false #1204 *Alisdair McDiarmid*
+
+* Fixed render_partial_collection to output an empty string instead of nil when handed an empty array #1202 *Ryan Carver*
+
+* Improved the speed of regular expression expirations for caching by a factor of 10 #1221 *Jamis Buck*
+
+* Removed dumping of template assigns on the rescue page as it would very easily include a ton of data making page loads take seconds (and the information was rarely helpful) #1222
+
+* Added BenchmarkHelper that can measure the execution time of a block in a template and reports the result to the log. Example:
+
+ <% benchmark "Notes section" do %>
+ <%= expensive_notes_operation %>
+ <% end %>
+
+ Will add something like "Notes section (0.345234)" to the log.
+
+* Added ActionController::Caching::Sweeper as an improved an easier to use sweeper. The new sweepers work on a single-step approach instead of two-steps like the old ones. Before
+
+ def after_save(record)
+ @list = record.is_a?(List) ? record : record.list
+ end
+
+ def filter(controller)
+ controller.expire_page(:controller => "lists", :action => %w( show public feed ), :id => @list.id)
+ controller.expire_action(:controller => "lists", :action => "all")
+ @list.shares.each { |share| controller.expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
+ end
+
+ ..after:
+
+ def after_save(record)
+ list = record.is_a?(List) ? record : record.list
+ expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
+ expire_action(:controller => "lists", :action => "all")
+ list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
+ end
+
+ The new sweepers can also observe on the actions themselves by implementing methods according to (before|after)_$controller_$action. Example of a callback that'll be called after PagesController#update_title has been performed:
+
+ def after_pages_update_title
+ expire_fragment(%r{pages/#{controller.assigns["page"].id}/.*})
+ end
+
+ Note that missing_method is delegated to the controller instance, which is assigned in a before filter. This means that you can call expire_fragment instead of @controller.expire_fragment.
+
+* Added that Fragments#expire_fragment now accepts as a regular expression as the name thereby deprecating expire_matched_fragments
+
+* Fixed that fragments shouldn't use the current host and the path as part of the key like pages does
+
+* Added conditions to around_filters just like before_filter and after_filter
+
+
+## 1.8.1 (20th April, 2005) ##
+
+* Added xml_http_request/xhr method for simulating XMLHttpRequest in functional tests #1151 *Sam Stephenson*. Example:
+
+ xhr :post, :index
+
+* Fixed that Ajax.Base.options.asynchronous wasn't being respected in Ajax.Request (thanks Jon Casey)
+
+* Fixed that :get, :post, and the others should take a flash array as the third argument just like process #1144 *rails@cogentdude.com*
+
+* Fixed a problem with Flash.now
+
+* Fixed stringification on all assigned hashes. The sacrifice is that assigns[:person] won't work in testing. Instead assigns["person"] or assigns(:person) must be used. In other words, the keys of assigns stay strings but we've added a method-based accessor to appease the need for symbols.
+
+* Fixed that rendering a template would require a connection to the database #1146
+
+
+## 1.8.0 (19th April, 2005) ##
+
+* Added assert_tag and assert_no_tag as a much improved alternative to the deprecated assert_template_xpath_match #1126 *Jamis Buck*
+
+* Deprecated the majority of all the testing assertions and replaced them with a much smaller core and access to all the collections the old assertions relied on. That way the regular test/unit assertions can be used against these. Added documentation about how to use it all.
+
+* Added a wide range of new Javascript effects:
+ * Effect.Puff zooms the element out and makes it smoothly transparent at the same time, giving a "puff" illusion #996 *thomas@fesch.at*
+ After the animation is completed, the display property will be set to none.
+ This effect will work on relative and absolute positioned elements.
+
+ * Effect.Appear as the opposite of Effect.Fade #990 *thomas@fesch.at*
+ You should return elements with style="display:none;" or a like class for this to work best and have no chance of flicker.
+
+ * Effect.Squish for scaling down an element and making it disappear at the end #972 *thomas@fesch.at*
+
+ * Effect.Scale for smoothly scaling images or text up and down #972 *thomas@fesch.at*
+
+ * Effect.Fade which smoothly turns opacity from 100 to 0 and then hides the element #960 *thomas@fesch.at*
+
+* Added Request#xml_http_request? (and an alias xhr?) to that'll return true when the request came from one of the Javascript helper methods (Ajax). This can be used to give one behavior for modern browsers supporting Ajax, another to old browsers #1127 *Sam Stephenson*
+
+* Changed render_partial to take local assigns as the second parameter instead of an explicit object and then the assigns. So the API changes from:
+
+ <%= render_partial "account", person, "rules" => regulations.rules %>
+
+ ...to:
+
+ <%= render_partial "account", :account => person, :rules => regulations.rules %>
+
+ The old API will still work, though, and render_partial "account" will still assume :account => @account.
+
+* Added support for web servers that use PATH_INFO instead of REQUEST_URI like IIS #1014 *BradG/Nicholas Seckar*
+
+* Added graceful handling of PUT, DELETE, and OPTIONS requests for a complete coverage of REST functionality #1136 *Josh Knowles*
+
+* Fixed that you can now pass an alternative :href option to link_to_function/remote in order to point to somewhere other than # if the javascript fails or is turned off. You can do the same with form_remote_tag by passing in :action. #1113 *Sam Stephenson*
+
+* Fixed DateHelper to return values on the option tags such that they'll work properly in IE with form_remote_tag #1024 *Scott Raymond*
+
+* Fixed FormTagHelper#check_box to respect checked #1049 *DelynnB*
+
+* Added that render_partial called from a controller will use the action name as default #828 *Dan Peterson*
+
+* Added Element.toggle, Element.show, and Element.hide to the prototype javascript library. Toggle.display has been deprecated, but will still work #992 *Lucas Carlson*
+
+* Added that deleting a cookie should not just set it to an empty string but also instantly expire it #1118 *todd@robotcoop.com*
+
+* Added AssetTagHelper#image_path, AssetTagHelper#javascript_path, and AssetTagHelper#stylesheet_path #1110 *Larry Halff*
+
+* Fixed url_for(nil) in functional tests #1116 *Alisdair McDiarmid*
+
+* Fixed error handling of broken layouts #1115 *Michael Schubert*
+
+* Added submit_to_remote that allows you to trigger an Ajax form submition at the click of the submission button, which allows for multiple targets in a single form through the use of multiple submit buttons #930 *yrashk@gmail.com*
+
+* Fixed pagination to work with joins #1034 *scott@sigkill.org*
+
+* Fixed that *rest parameter in map.connect couldn't accept an empty list #1037 *Dee Zsombor*
+
+* Added :confirm option to link_to_remote just like link_to has #1082 *yrashk@fp.org.ua*
+
+* Added minute_step as an option to select_minute (and the helpers that use it) to jump in larger increments than just 1 minute. At 15, it would return 0, 15, 30, 45 options #1085 *ordwaye@evergreen.edu*
+
+* Fixed that an exception would be thrown when an empty form was submitted #1090 *jan@ulbrich-boerwang.de*
+
+* Moved TextHelper#human_size to NumberHelper#number_to_human_size, but kept an deprecated alias to the old method name
+
+* Fixed that the content-type for some browsers could include an additional \r which made wonky things happen #1067 *Thomas Fuchs*
+
+* Fixed that radio buttons shouldn't have a default size attribute #1074 *hendrik@mans.de*
+
+* Added ActionView::Helpers::InstanceTag::DEFAULT_RADIO_OPTIONS that contains a hash of default options for radio buttons #1074 *hendrik@mans.de*
+
+* Fixed that in some circumstances controllers outside of modules may have hidden ones inside modules. For example, admin/content might have been hidden by /content. #1075 *Nicholas Seckar*
+
+* Added JavascriptHelper#periodically_call_remote in order to create areas of a page that update automatically at a set interval #945 *Jon Tirsen*
+
+* Fixed Cache#expire_matched_fragments that couldn't recognize the difference between string and url_for options #1030 *Stefan Kaes*
+
+* Added simulation of @request.request_uri in functional tests #1038 *Jamis Buck*
+
+* Fixed autolinking to work better in more cases #1013 *Jamis Buck*
+
+* Added the possible of using symbols in form helpers that relate to instance variables like text_field :account, :name in addition to text_field "account", "name"'
+
+* Fixed javascript_include_tag to output type instead of language and conform to XHTML #1018 *Rick Olson*
+
+* Added NumberHelper for common string representations like phone number, currency, and percentage #1015 *DeLynn*
+
+* Added pagination for scaffolding (10 items per page) #964 *mortonda@dgrmm.net*
+
+* Added assert_no_cookie and fixed assert_cookie_equal to deal with non-existing cookies #979 *Jeremy Kemper*
+
+* Fixed :overwrite_param so it doesn't delete but reject elements from @request.parameters #982 *raphinou@yahoo.com*
+
+* Added :method option to verify for ensuring that either GET, POST, etc is allowed #984 *Jamis Buck*
+
+* Added options to set cc, bcc, subject, and body for UrlHelper#mail_to #966 *DeLynn*
+
+* Fixed include_blank for select_hour/minute/second #527 *edward@debian.org*
+
+* Improved the message display on the exception handler pages #963 *Johan Sorensen*
+
+* Fixed that on very rare occasions, webrick would raise a NoMethodError: private method 'split' called for nil #1001 *Flurin Egger*
+
+* Fixed problem with page caching #958 *Rick Olson*
+
+
+## 1.7.0 (27th March, 2005) ##
+
+* Added ActionController::Base.page_cache_extension for setting the page cache file extension (the default is .html) #903 *Andreas*
+
+* Fixed "bad environment variable value" exception caused by Safari, Apache, and Ajax calls #918
+
+* Fixed that pagination_helper would ignore :params #947 *Sebastian Kanthak*
+
+* Added :owerwrite_params back to url_for and friends -- it was AWL since the introduction of Routes #921 *raphinou*
+
+* Added :position option to link_to_remote/form_remote_tag that can be either :before, :top, :bottom, or :after and specifies where the return from the method should be inserted #952 *Matthew McCray/Sam Stephenson*
+
+* Added Effect.Highlight to prototype.js to do Yellow Fade Technique (of 37signals' fame) on any container #952 *Sam Stephenson/court3nay*
+
+* Added include_seconds option as the third parameter to distance_of_time_in_words which will render "less than a minute" in higher resolution ("less than 10 seconds" etc) #944 *thomas@fesch.at*
+
+* Added fourth option to process in test cases to specify the content of the flash #949 *Jamis Buck*
+
+* Added Verifications that allows you to specify preconditions to actions in form of statements like <tt>verify :only => :update_post, :params => "admin_privileges", :redirect_to => { :action => "settings" }</tt>, which ensure that the update_post action is only called if admin_privileges is available as a parameter -- otherwise the user is redirected to settings. #897 *Jamis Buck*
+
+* Fixed Form.Serialize for the JavascriptHelper to also seriliaze password fields #934 *dweitzman@gmail.com*
+
+* Added JavascriptHelper#escape_javascript as a public method (was private) and made it escape both single and double quotes and new lines #940 *mortonda@dgrmm.net*
+
+* Added trailing_slash option to url_for, so you can generate urls ending in a slash. Note that is currently not recommended unless you need it for special reasons since it breaks caching #937 *stian@grytoyr.net*
+
+* Added expire_matched_fragments(regular_expression) to clear out a lot of fragment caches at once #927 *Rick Olson*
+
+* Fixed the problems with : and ? in file names for fragment caches on Windows #927 *Rick Olson*
+
+* Added TextHelper#human_size for formatting file sizes, like human_size(1234567) => 1.2 MB #943 *thomas@fesch.at*
+
+* Fixed link_to :confirm #936 *Nicholas Seckar*
+
+* Improved error reporting especially around never shallowing exceptions. Debugging helpers should be much easier now #980 *Nicholas Seckar*
+
+* Fixed Toggle.display in prototype.js #902 *Lucas Carlson*
+
+
+## 1.6.0 (22th March, 2005) ##
+
+* Added a JavascriptHelper and accompanying prototype.js library that opens the world of Ajax to Action Pack with a large array of options for dynamically interacting with an application without reloading the page #884 *Sam Stephenson/David*
+
+* Added pagination support through both a controller and helper add-on #817 *Sam Stephenson*
+
+* Fixed routing and helpers to make Rails work on non-vhost setups #826 *Nicholas Seckar/Tobias Lütke*
+
+* Added a much improved Flash module that allows for finer-grained control on expiration and allows you to flash the current action #839 *Caio Chassot*. Example of flash.now:
+
+ class SomethingController < ApplicationController
+ def save
+ ...
+ if @something.save
+ # will redirect, use flash
+ flash[:message] = 'Save successful'
+ redirect_to :action => 'list'
+ else
+ # no redirect, message is for current action, use flash.now
+ flash.now[:message] = 'Save failed, review'
+ render_action 'edit'
+ end
+ end
+ end
+
+* Added to_param call for parameters when composing an url using url_for from something else than strings #812 *Sam Stephenson*. Example:
+
+ class Page
+   def initialize(number)
+     @number = number
+   end
+   # ...
+   def to_param
+     @number.to_s
+   end
+ end
+
+ You can now use instances of Page with url_for:
+
+ class BarController < ApplicationController
+   def baz
+     page = Page.new(4)
+     url = url_for :page => page # => "http://foo/bar/baz?page=4"
+   end
+ end
+
+* Fixed form helpers to query Model#id_before_type_cast instead of Model#id as a temporary workaround for Ruby 1.8.2 warnings #818 *DeLynn B*
+
+* Fixed TextHelper#markdown to use blank? instead of empty? so it can deal with nil strings passed #814 *Johan Sörensen*
+
+* Added TextHelper#simple_format as a non-dependency text presentation helper #814 *Johan Sörensen*
+
+* Added that the html options disabled, readonly, and multiple can all be treated as booleans. So specifying <tt>disabled => :true</tt> will give <tt>disabled="disabled"</tt>. #809 *mindel*
+
+* Added path collection syntax for Routes that will gobble up the rest of the url and pass it on to the controller #830 *rayners*. Example:
+
+ map.connect 'categories/*path_info', :controller => 'categories', :action => 'show'
+
+ A request for /categories/top-level-cat, would give @params[:path_info] with "top-level-cat".
+ A request for /categories/top-level-cat/level-1-cat, would give @params[:path_info] with "top-level-cat/level-1-cat" and so forth.
+
+ The @params[:path_info] return is really an array, but where to_s has been overwritten to do join("/").
+
+* Fixed options_for_select on selected line issue #624 *Florian Weber*
+
+* Added CaptureHelper with CaptureHelper#capture and CaptureHelper#content_for. See documentation in helper #837 *Tobias Lütke*
+
+* Fixed :anchor use in url_for #821 *Nicholas Seckar*
+
+* Removed the reliance on PATH_INFO as it was causing problems for caching and inhibited the new non-vhost support #822 *Nicholas Seckar*
+
+* Added assigns shortcut for @response.template.assigns to controller test cases *Jeremy Kemper*. Example:
+
+ Before:
+
+ def test_list
+ assert_equal 5, @response.template.assigns['recipes'].size
+ assert_equal 8, @response.template.assigns['categories'].size
+ end
+
+ After:
+
+ def test_list
+ assert_equal 5, assigns(:recipes).size
+ assert_equal 8, assigns(:categories).size
+ end
+
+* Added TagHelper#image_tag and deprecated UrlHelper#link_image_to (recommended approach is to combine image_tag and link_to instead)
+
+* Fixed textilize to be resilient to getting nil parsed (by using Object#blank? instead of String#empty?)
+
+* Fixed that the :multipart option in FormTagHelper#form_tag would be ignored *Yonatan Feldman*
+
+
+## 1.5.1 (7th March, 2005) ##
+
+* Fixed that the routes.rb file wouldn't be found on symlinked setups due to File.expand_path #793 *piotr@t-p-l.com*
+
+* Changed ActiveRecordStore to use Marshal instead of YAML as the latter proved troublesome in persisting circular dependencies. Updating existing applications MUST clear their existing session table from data to start using this updated store #739 *Jamis Buck*
+
+* Added shortcut :id assignment to render_component and friends (before you had to go through :params) #784 *Lucas Carlson*
+
+* Fixed that map.connect should convert arguments to strings #780 *Nicholas Seckar*
+
+* Added UrlHelper#link_to_if/link_to_unless to enable other conditions that just link_to_unless_current #757 *mindel*
+
+* Fixed that single quote was not escaped in a UrlHelper#link_to javascript confirm #549 *Scott Barron*
+
+* Removed the default border on link_image_to (it broke xhtml strict) -- can be specified with :border => 0 #517 *?/caleb*
+
+* Fixed that form helpers would treat string and symbol keys differently in html_options (and possibly create duplicate entries) #112 *Jeremy Kemper*
+
+* Fixed that broken pipe errors (clients disconnecting in mid-request) could bring down a fcgi process
+
+* Added the original exception message to session recall errors (so you can see which class wasnt required)
+
+* Fixed that RAILS_ROOT might not be defined when AP was loaded, so do a late initialization of the ROUTE_FILE #761 *Scott Barron*
+
+* Fix request.path_info and clear up LoadingModule behavior #754 *Nicholas Seckar*
+
+* Fixed caching to be aware of extensions (so you can cache files like api.wsdl or logo.png) #734 *Nicholas Seckar*
+
+* Fixed that Routes would raise NameErrors if a controller component contains characters that are not valid constant names #733 *Nicholas Seckar*
+
+* Added PATH_INFO access from the request that allows urls like the following to be interpreted by rails: http://www.example.com/dispatcher.cgi/controller/action -- that makes it possible to use rails as a CGI under lighttpd and would also allow (for example) Rublog to be ported to rails without breaking existing links to Rublog-powered blogs. #728 *Jamis Buck*
+
+* Fixed that caching the root would result in .html not index.html #731, #734 *alisdair/Nicholas Seckar*
+
+
+## 1.5.0 (24th February, 2005) ##
+
+* Added Routing as a replacement for mod_rewrite pretty urls [Nicholas Seckar]. Read more in ActionController::Base.url_for and on http://manuals.rubyonrails.com/read/book/9
+
+* Added components that allows you to call other actions for their rendered response while execution another action. You can either delegate the entire response rendering or you can mix a partial response in with your other content. Read more on http://manuals.rubyonrails.com/read/book/14
+
+*  Fixed that proxy IPs do not follow all RFC1918 nets #251 *caleb@aei-tech.com*
+
+* Added Base#render_to_string to parse a template and get the result back as a string #479
+
+* Fixed that send_file/data can work even if render* has been called before in action processing to render the content of a file to be send for example #601
+
+* Added FormOptionsHelper#time_zone_select and FormOptionsHelper#time_zone_options_for_select to work with the new value object TimeZone in Active Support #688 *Jamis Buck*
+
+* Added FormHelper#file_field and FormTagHelper#file_field_tag for creating file upload fields
+
+* Added :order option for date_select that allows control over the order in which the date dropdowns is used and which of them should be used #619 [Tim Bates]. Examples:
+
+ date_select("post", "written_on", :order => [:day, :month, :year])
+ date_select("user", "birthday", :order => [:month, :day])
+
+* Added ActionView::Base.register_template_handler for easy integration of an alternative template language to ERb and Builder. See test/controller/custom_handler_test.rb for a usage example #656 *Jamis Buck*
+
+* Added AssetTagHelper that provides methods for linking a HTML page together with other assets, such as javascripts, stylesheets, and feeds.
+
+* Added FormTagHelper that provides a number of methods for creating form tags that doesn't rely on conventions with an object assigned to the template like FormHelper does. With the FormTagHelper, you provide the names and values yourself.
+
+* Added Afghanistan, Iran, and Iraq to the countries list used by FormOptions#country_select and FormOptions#country_options_for_select
+
+* Renamed link_to_image to link_image_to (since thats what it actually does) -- kept alias for the old method name
+
+* Fixed textilize for RedCloth3 to keep doing hardbreaks
+
+* Fixed that assert_template_xpath_matches did not indicate when a path was not found #658 *Eric Hodel*
+
+* Added TextHelper#auto_link to turn email addresses and urls into ahrefs
+
+* Fixed that on validation errors, scaffold couldn't find template #654 *mindel*
+
+* Added Base#hide_action(*names) to hide public methods from a controller that would otherwise have been callable through the URL. For the majority of cases, its preferred just to make the methods you don't want to expose protected or private (so they'll automatically be hidden) -- but if you must have a public method, this is a way to make it uncallable. Base#hidden_actions retrieve the list of all hidden actions for the controller #644 *Nicholas Seckar*
+
+* Fixed that a bunch of methods from ActionController::Base was accessible as actions (callable through a URL) when they shouldn't have been #644 *Nicholas Seckar*
+
+* Added UrlHelper#current_page?(options) method to check if the url_for options passed corresponds to the current page
+
+* Fixed https handling on other ports than 443 *Alan Gano*
+
+* Added follow_redirect method for functional tests that'll get-request the redirect that was made. Example:
+
+ def test_create_post
+ post :create, "post" => { "title" => "Exciting!" }
+ assert_redirected_to :action => "show"
+
+ follow_redirect
+ assert_rendered_file "post/show"
+ end
+
+ Limitation: Only works for redirects to other actions within the same controller.
+
+* Fixed double requiring of models with the same name as the controller
+
+* Fixed that query params could be forced to nil on a POST due to the raw post fix #562 *moriq@moriq.com*
+
+* Fixed that cookies shouldn't be frozen in TestRequest #571 *Eric Hodel*
+
+
+## 1.4.0 (January 25th, 2005) ##
+
+* Fixed problems with ActiveRecordStore under the development environment in Rails
+
+* Fixed the ordering of attributes in the xml-decleration of Builder #540 *woeye*
+
+* Added @request.raw_post as a convenience access to @request.env['RAW_POST_DATA'] #534 *Tobias Lütke*
+
+* Added support for automatic id-based indexing for lists of items #532 [dblack]. Example:
+
+ <% @students.each do |@student| %>
+ <%= text_field "student[]", "first_name", :size => "20" %>
+ <%= text_field "student[]", "last_name" %>
+ <%= text_field "student[]", "grade", :size => "5" %>
+ <% end %>
+
+ ...would produce, for say David Black with id 123 and a grace of C+:
+
+ <input id="student_123_first_name" name="student[123][first_name]" size="20" size="30" type="text" value="David" />
+ <input id="student_123_last_name" name="student[123][last_name]" size="30" type="text" value="Black" />
+ <input id="student_123_grade" name="student[123][grade]" size="5" type="text" value="C+" />
+
+* Added :application_prefix to url_for and friends that makes it easier to setup Rails in non-vhost environments #516 *Jamis Buck*
+
+* Added :encode option to mail_to that'll allow you to masquarede the email address behind javascript or hex encoding #494 *Lucas Carlson*
+
+* Fixed that the content-header was being set to application/octet_stream instead of application/octet-stream on send_date/file *Alexey*
+
+* Removed the need for passing the binding when using CacheHelper#cache
+
+* Added TestResponse#binary_content that'll return as a string the data sent through send_data/send_file for testing #500 *Alexey*
+
+* Added @request.env['RAW_POST_DATA'] for people who need access to the data before Ruby's CGI has parsed it #505 *Jeremy Kemper*
+
+* Fixed that a default fragment store wan't being set to MemoryStore as intended.
+
+* Fixed that all redirect and render calls now return true, so you can use the pattern of "do and return". Example:
+
+ def show
+ redirect_to(:action => "login") and return unless @person.authenticated?
+ render_text "I won't happen unless the person is authenticated"
+ end
+
+* Added that renders and redirects called in before_filters will have the same effect as returning false: stopping the chain. Example:
+
+ class WeblogController
+ before_filter { |c| c.send(:redirect_to_url("http://www.farfaraway.com")}) }
+
+ def hello
+ render_text "I will never be called"
+ end
+ end
+
+
+* Added that only one render or redirect can happen per action. The first call wins and subsequent calls are ignored. Example:
+
+ def do_something
+ redirect_to :action => "elsewhere"
+ render_action "overthere"
+ end
+
+ Only the redirect happens. The rendering call is simply ignored.
+
+
+## 1.3.1 (January 18th, 2005) ##
+
+* Fixed a bug where cookies wouldn't be set if a symbol was used instead of a string as the key
+
+* Added assert_cookie_equal to assert the contents of a named cookie
+
+* Fixed bug in page caching that prevented it from working at all
+
+
+## 1.3.0 (January 17th, 2005) ##
+
+* Added an extensive caching module that offers three levels of granularity (page, action, fragment) and a variety of stores.
+ Read more in ActionController::Caching.
+
+* Added the option of passing a block to ActiveRecordHelper#form in order to add more to the auto-generated form #469 *dom@sisna.com*
+
+ form("entry", :action => "sign") do |form|
+ form << content_tag("b", "Department")
+ form << collection_select("department", "id", @departments, "id", "name")
+ end
+
+* Added arrays as a value option for params in url_for and friends #467 [Eric Anderson]. Example:
+
+ url_for(:controller => 'user', :action => 'delete', :params => { 'username' => %( paul john steve ) } )
+ # => /user/delete?username[]=paul&username[]=john&username[]=steve
+
+* Fixed that controller tests can now assert on the use of cookies #466 *Alexey*
+
+* Fixed that send_file would "remember" all the files sent by adding to the headers again and again #458 *Jeremy Kemper*
+
+* Fixed url rewriter confusion when the controller or action name was a substring of the controller_prefix or action_prefix
+
+* Added conditional layouts like <tt>layout "weblog_standard", :except => :rss</tt> #452 *Marcel Molina Jr.*
+
+* Fixed that MemCacheStore wasn't included by default and added default MemCache object pointing to localhost #447 *Lucas Carlson*
+
+* Added fourth argument to render_collection_of_partials that allows you to specify local_assigns -- just like render_partial #432 *Ryan Davis*
+
+* Fixed that host would choke when cgi.host returned nil #432 *Tobias Lütke*
+
+* Added that form helpers now take an index option #448 *Tim Bates*
+
+ Example:
+ text_field "person", "name", "index" => 3
+
+ Becomes:
+ <input type="text" name="person[3][name]" id="person_3_name" value="<%= @person.name %>" />
+
+* Fixed three issues with retrying breakpoints #417 *Florian Gross*
+
+ 1. Don't screw up pages that use multiple values for the same parameter (?foo=bar&foo=qux was converted to ?foo=barqux)
+ 2. Don't screw up all forms when you click the "Retry with Breakpoint" link multiple times instead of reloading
+ (This caused the parameters to be added multiple times for GET forms leading to trouble.)
+ 3. Don't add ?BP-RETRY=1 multiple times
+
+* Added that all renders and redirects now return false, so they can be used as the last line in before_filters to stop execution.
+
+ Before:
+ def authenticate
+ unless @session[:authenticated]
+ redirect_to :controller => "account", :action => "login"
+ return false
+ end
+ end
+
+ After:
+ def authenticate
+ redirect_to(:controller => "account", :action => "login") unless @session[:authenticated]
+ end
+
+* Added conditional filters #431 [Marcel Molina Jr.]. Example:
+
+ class JournalController < ActionController::Base
+ # only require authentication if the current action is edit or delete
+ before_filter :authorize, :only_on => [ :edit, :delete ]
+
+ private
+ def authorize
+ # redirect to login unless authenticated
+ end
+ end
+
+* Added Base#render_nothing as a cleaner way of doing render_text "" when you're not interested in returning anything but an empty response.
+
+* Added the possibility of passing nil to UrlHelper#link_to to use the link itself as the name
+
+
+## 1.2.0 (January 4th, 2005) ##
+
+* Added MemCacheStore for storing session data in Danga's MemCache system *Bob Cottrell*
+ Depends on: MemCached server (http://www.danga.com/memcached/), MemCache client (http://raa.ruby-lang.org/project/memcache/)
+
+* Added thread-safety to the DRbStore #66, #389 *Ben Stiglitz*
+
+* Added DateHelper#select_time and DateHelper#select_second #373 *Scott Baron*
+
+* Added :host and :protocol options to url_for and friends to redirect to another host and protocol than the current.
+
+* Added class declaration for the MissingFile exception #388 *Kent Sibilev*
+
+* Added "short hypertext note with a hyperlink to the new URI(s)" to redirects to fulfill compliance with RFC 2616 (HTTP/1.1) section 10.3.3 #397 *Tim Bates*
+
+* Added second boolean parameter to Base.redirect_to_url and Response#redirect to control whether the redirect is permanent or not (301 vs 302) #375 *Hodel*
+
+* Fixed redirects when the controller and action is named the same. Still haven't fixed same controller, module, and action, though #201 *Josh Peek*
+
+* Fixed problems with running multiple functional tests in Rails under 1.8.2 by including hack for test/unit weirdness
+
+* Fixed that @request.remote_ip didn't work in the test environment #369 *Bruno Mattarollo*
+
+
+## 1.1.0 ##
+
+* Added search through session to clear out association caches at the end of each request. This makes it possible to place Active Record objects
+ in the session without worrying about stale data in the associations (the main object is still subject to caching, naturally) #347 *Tobias Lütke*
+
+* Added more informative exception when using helper :some_helper and the helper requires another file that fails, you'll get an
+ error message tells you what file actually failed to load, rather than falling back on assuming it was the helper file itself #346 *dblack*
+
+* Added use of *_before_type_cast for all input and text fields. This is helpful for getting "100,000" back on a integer-based
+ validation where the value would normally be "100".
+
+* Added Request#port_string to get something like ":8080" back on 8080 and "" on 80 (or 443 with https).
+
+* Added Request#domain (returns string) and Request#subdomains (returns array).
+
+* Added POST support for the breakpoint retries, so form processing that raises an exception can be retried with the original request *Florian Gross*
+
+* Fixed regression with Base#reset_session that wouldn't use the DEFAULT_SESSION_OPTIONS *adam@the-kramers.net*
+
+* Fixed error rendering of rxml documents to not just swallow the exception and return 0 (still not guessing the right line, but hey)
+
+* Fixed that textilize and markdown would instantiate their engines even on empty strings. This also fixes #333 *Ulysses*
+
+* Fixed UrlHelper#link_to_unless so it doesn't care if the id is a string or fixnum *Ryan Davis*
+
+
+## 1.0.1 ##
+
+* Fixed a bug that would cause an ApplicationController to require itself three times and hence cause filters to be run three times *evl*
+
+
+## 1.0 ##
+
+* Added that controllers will now attempt to require a model dependency with their name and in a singular attempt for their name.
+ So both PostController and PostsController will automatically have the post.rb model required. If no model is found, no error is raised,
+ as it is then expected that no match is available and the programmer will have included his own models.
+
+* Fixed DateHelper#date_select so that you can pass include_blank as an option even if you don't use start_year and end_year #59 *what-a-day*
+
+* Added that controllers will now search for a layout in $template_root/layouts/$controller_name.r(html|xml), so PostsController will look
+ for layouts/posts.rhtml or layouts/posts.rxml and automatically configure this layout if found #307 *Marcel Molina Jr.*
+
+* Added FormHelper#radio_button to work with radio buttons like its already possible with check boxes *Michael Koziarski*
+
+* Added TemplateError#backtrace that makes it much easier to debug template errors from unit and functional tests
+
+* Added display of error messages with scaffolded form pages
+
+* Added option to ERB templates to swallow newlines by using <% if something -%> instead of just <% if something %>. Example:
+
+ class SomeController < ApplicationController
+ <% if options[:scaffold] %>
+ scaffold :<%= singular_name %>
+ <% end %>
+ helper :post
+
+ ...produces this on post as singular_name:
+
+ class SomeController < ApplicationController
+
+ scaffold :post
+
+ helper :post
+
+ ...where as:
+
+ class SomeController < ApplicationController
+ <% if options[:scaffold] -%>
+ scaffold :<%= singular_name %>
+ <% end -%>
+ helper :post
+
+ ...produces:
+
+ class SomeController < ApplicationController
+ scaffold :post
+ helper :post
+
+ *This undocumented gem for ERb was uncovered by bitsweat*
+
+* Fixed CgiRequest so that it'll now accept session options with Symbols as keys (as the documentation points out) *Suggested by Andreas*
+
+* Added that render_partial will always by default include a counter with value 1 unless there is a counter passed in via the
+ local_assigns hash that overrides it. As a result, render_collection_of_partials can still be written in terms of render_partial
+ and partials that make use of a counter can be called without problems from both render_collection_of_partials as well as
+ render_partial #295 *Marcel Molina Jr.*
+
+* Fixed CgiRequest#out to fall back to #write if $stdout doesn't have #syswrite *Jeremy Kemper*
+
+* Fixed all helpers so that they use XHTML compliant double quotes for values instead of single quotes *htonl/Jeremy Kemper*
+
+* Added link_to_image(src, options = {}, html_options = {}). Documentation:
+
+ Creates a link tag to the image residing at the +src+ using an URL created by the set of +options+. See the valid options in
+ link:classes/ActionController/Base.html#M000021. It's also possible to pass a string instead of an options hash to
+ get a link tag that just points without consideration. The <tt>html_options</tt> works jointly for the image and ahref tag by
+ letting the following special values enter the options on the image and the rest goes to the ahref:
+
+ ::alt: If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension)
+ ::size: Supplied as "XxY", so "30x45" becomes width="30" and height="45"
+ ::align: Sets the alignment, no special features
+
+ The +src+ can be supplied as a...
+ * full path, like "/my_images/image.gif"
+ * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
+ * file name without extension, like "logo", that gets expanded to "/images/logo.png"
+
+* Fixed to_input_field_tag so it no longer explicitly uses InstanceTag.value if value was specified in the options hash *evl*
+
+* Added the possibility of having validate be protected for assert_(in)valid_column #263 *Tobias Lütke*
+
+* Added that ActiveRecordHelper#form now calls url_for on the :action option.
+
+* Added all the HTTP methods as alternatives to the generic "process" for functional testing #276 *Tobias Lütke*. Examples:
+
+ # Calls Controller#miletone with a GET request
+ process :milestone
+
+ # Calls Controller#miletone with a POST request that has parameters
+ post :milestone, { "name" => "David" }
+
+ # Calls Controller#milestone with a HEAD request that has both parameters and session data
+ head :milestone, { "id" => 1 }, { "user_id" => 23 }
+
+ This is especially useful for testing idiomatic REST web services.
+
+* Added proper handling of HEAD requests, so that content isn't returned (Request#head? added as well) #277 *Eric Hodel*
+
+* Added indifference to whether @headers["Content-Type"], @headers["Content-type"], or @headers["content-type"] is used.
+
+* Added TestSession#session_id that returns an empty string to make it easier to functional test applications that doesn't use
+ cookie-based sessions #275 *jcf*
+
+* Fixed that cached template loading would still check the file system to see if the file existed #258 *Andreas Schwarz*
+
+* Added options to tailor header tag, div id, and div class on ActiveRecordHelper#error_messages_for *Josh Peek*
+
+* Added graceful handling of non-alphanumeric names and misplaced brackets in input parameters *Jeremy Kemper*
+
+* Added a new container for cookies that makes them more intuative to use. The old methods of cookie and @cookies have been deprecated.
+
+ Examples for writing:
+
+ cookies["user_name"] = "david" # => Will set a simple session cookie
+ cookies["login"] = { "value" => "XJ-122", "expires" => Time.now + 360} # => Will set a cookie that expires in 1 hour
+
+ Examples for reading:
+
+ cookies["user_name"] # => "david"
+ cookies.size # => 2
+
+ Read more in ActionController::Cookies
+
+ NOTE: If you were using the old accessor (cookies instead of @cookies), this could potentially break your code -- if you expect a full cookie object!
+
+* Added the opportunity to defined method_missing on a controller which will handle all requests for actions not otherwise defined #223 *timb*
+
+* Fixed AbstractRequest#remote_ip for users going through proxies - Patch #228 *Eric Hodel*
+
+* Added Request#ssl? which is shorthand for @request.protocol == "https://"
+
+* Added the choice to call form_tag with no arguments (resulting in a form posting to current action) *Jeremy Kemper*
+
+* Upgraded to Builder 1.2.1
+
+* Added :module as an alias for :controller_prefix to url_for and friends, so you can do redirect_to(:module => "shop", :controller => "purchases")
+ and go to /shop/purchases/
+
+* Added support for controllers in modules through @params["module"].
+
+* Added reloading for dependencies under cached environments like FastCGI and mod_ruby. This makes it possible to use those environments for development.
+ This is turned on by default, but can be turned off with ActionController::Base.reload_dependencies = false in production environments.
+
+ NOTE: This will only have an effect if you use the new model, service, and observer class methods to mark dependencies. All libraries loaded through
+ require will be "forever" cached. You can, however, use ActionController::Base.load_or_require("library") to get this behavior outside of the new
+ dependency style.
+
+* Added that controllers will automatically require their own helper if possible. So instead of doing:
+
+ class MsgController < ApplicationController
+ helper :msg
+ end
+
+ ...you can just do:
+
+ class MsgController < ApplicationController
+ end
+
+* Added dependencies_on(layer) to query the dependencies of a controller. Examples:
+
+ MsgController.dependencies_on(:model) # => [ :post, :comment, :attachment ]
+ MsgController.dependencies_on(:service) # => [ :notification_service ]
+ MsgController.dependencies_on(:observer) # => [ :comment_observer ]
+
+* Added a new dependency model with the class methods model, service, and observer. Example:
+
+ class MsgController < ApplicationController
+ model :post, :comment, :attachment
+ service :notification_service
+ observer :comment_observer
+ end
+
+ These new "keywords" remove the need for explicitly calling 'require' in most cases. The observer method even instantiates the
+ observer as well as requiring it.
+
+* Fixed that link_to would escape & in the url again after url_for already had done so
+
+
+## 0.9.5 (28) ##
+
+* Added helper_method to designate that a given private or protected method you should available as a helper in the view. *Jeremy Kemper*
+
+* Fixed assert_rendered_file so it actually verifies if that was the rendered file *htonl*
+
+* Added the option for sharing partial spacer templates just like partials themselves *radsaq*
+
+* Fixed that Russia was named twice in country_select *alexey*
+
+* Fixed request_origin to use remote_ip instead of remote_addr *Jeremy Kemper*
+
+* Fixed link_to breakage when nil was passed for html_options *alexey*
+
+* Fixed redirect_to on a virtual server setup with apache with a port other than the default where it would forget the port number *seanohalpin*
+
+* Fixed that auto-loading webrick on Windows would cause file uploads to fail *Jeremy Kemper*
+
+* Fixed issues with sending files on WEBrick by setting the proper binmode *Jeremy Kemper*
+
+* Added send_data as an alternative to send_file when the stream is not read off the filesystem but from a database or generated live *Jeremy Kemper*
+
+* Added a new way to include helpers that doesn't require the include hack and can go without the explicit require. *Jeremy Kemper*
+
+ Before:
+
+ module WeblogHelper
+ def self.included(controller) #:nodoc:
+ controller.ancestors.include?(ActionController::Base) ? controller.add_template_helper(self) : super
+ end
+ end
+
+ require 'weblog_helper'
+ class WeblogController < ActionController::Base
+ include WeblogHelper
+ end
+
+ After:
+
+ module WeblogHelper
+ end
+
+ class WeblogController < ActionController::Base
+ helper :weblog
+ end
+
+* Added a default content-type of "text/xml" to .rxml renders *Ryan Platte*
+
+* Fixed that when /controller/index was requested by the browser, url_for would generates wrong URLs *Ryan Platte*
+
+* Fixed a bug that would share cookies between users when using FastCGI and mod_ruby *The Robot Co-op*
+
+* Added an optional third hash parameter to the process method in functional tests that takes the session data to be used *alexey*
+
+* Added UrlHelper#mail_to to make it easier to create mailto: style ahrefs
+
+* Added better error messages for layouts declared with the .rhtml extension (which they shouldn't) *geech*
+
+* Added another case to DateHelper#distance_in_minutes to return "less than a minute" instead of "0 minutes" and "1 minute" instead of "1 minutes"
+
+* Added a hidden field to checkboxes generated with FormHelper#check_box that will make sure that the unchecked value (usually 0)
+ is sent even if the checkbox is not checked. This relieves the controller from doing custom checking if the checkbox wasn't
+ checked. BEWARE: This might conflict with your run-on-the-mill work-around code. *Tobias Lütke*
+
+* Fixed error_message_on to just use the first if more than one error had been added *Marcel Molina Jr.*
+
+* Fixed that URL rewriting with /controller/ was working but /controller was not and that you couldn't use :id on index *geech*
+
+* Fixed a bug with link_to where the :confirm option wouldn't be picked up if the link was a straight url instead of an option hash
+
+* Changed scaffolding of forms to use <label> tags instead of <b> to please W3C *evl*
+
+* Added DateHelper#distance_of_time_in_words_to_now(from_time) that works like distance_of_time_in_words,
+ but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
+
+* Added assert_flash_equal(expected, key, message), assert_session_equal(expected, key, message),
+ assert_assigned_equal(expected, key, message) to test the contents of flash, session, and template assigns.
+
+* Improved the failure report on assert_success when the action triggered a redirection *alexey*.
+
+* Added "markdown" to accompany "textilize" as a TextHelper method for converting text to HTML using the Markdown syntax.
+ BlueCloth must be installed in order for this method to become available.
+
+* Made sure that an active session exists before we attempt to delete it *Samuel*
+
+* Changed link_to with Javascript confirmation to use onclick instead of onClick for XHTML validity *Scott Barron*
+
+
+## 0.9.0 (43) ##
+
+* Added support for Builder-based templates for files with the .rxml extension. These new templates are an alternative to ERb that
+ are especially useful for generating XML content, such as this RSS example from Basecamp:
+
+ xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
+ xml.channel do
+ xml.title(@feed_title)
+ xml.link(@url)
+ xml.description "Basecamp: Recent items"
+ xml.language "en-us"
+ xml.ttl "40"
+
+ for item in @recent_items
+ xml.item do
+ xml.title(item_title(item))
+ xml.description(item_description(item)) if item_description(item)
+ xml.pubDate(item_pubDate(item))
+ xml.guid(@person.firm.account.url + @recent_items.url(item))
+ xml.link(@person.firm.account.url + @recent_items.url(item))
+
+ xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
+ end
+ end
+ end
+ end
+
+ ...which will generate something like:
+
+ <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <channel>
+ <title>Web Site Redesign</title>
+ <link>http://www.basecamphq.com/clients/travelcenter/1/</link>
+ <description>Basecamp: Recent items</description>
+ <language>en-us</language>
+ <ttl>40</ttl>
+ <item>
+ <title>Post: don't you know</title>
+ <description>&amp;lt;p&amp;gt;deeper and down&amp;lt;/p&amp;gt;</description>
+ <pubDate>Fri, 20 Aug 2004 21:13:50 CEST</pubDate>
+ <guid>http://www.basecamphq.com/clients/travelcenter/1/msg/assets/96976/comments</guid>
+ <link>http://www.basecamphq.com/clients/travelcenter/1/msg/assets/96976/comments</link>
+ <dc:creator>David H. Heinemeier</dc:creator>
+ </item>
+ <item>
+ <title>Milestone completed: Design Comp 2</title>
+ <pubDate>Mon, 9 Aug 2004 14:42:06 CEST</pubDate>
+ <guid>http://www.basecamphq.com/clients/travelcenter/1/milestones/#49</guid>
+ <link>http://www.basecamphq.com/clients/travelcenter/1/milestones/#49</link>
+ </item>
+ </channel>
+ </rss>
+
+ The "xml" local variable is automatically available in .rxml templates. You construct the template by calling a method with the name
+ of the tag you want. Options for the tag can be specified as a hash parameter to that method.
+
+ Builder-based templates can be mixed and matched with the regular ERb ones. The only thing that differentiates them is the extension.
+ No new methods have been added to the public interface to handle them.
+
+ Action Pack ships with a version of Builder, but it will use the RubyGems version if you have one installed.
+
+ Read more about Builder on: http://onestepback.org/index.cgi/Tech/Ruby/StayingSimple.rdoc
+
+ *Builder is created by Jim Weirich*
+
+* Added much improved support for functional testing *what-a-day*.
+
+ # Old style
+ def test_failing_authenticate
+ @request.request_uri = "/login/authenticate"
+ @request.action = "authenticate"
+ @request.request_parameters["user_name"] = "nop"
+ @request.request_parameters["password"] = ""
+
+ response = LoginController.process_test(@request)
+
+ assert_equal "The username and/or password you entered is invalid.", response.session["flash"]["alert"]
+ assert_equal "http://37signals.basecamp.com/login/", response.headers["location"]
+ end
+
+ # New style
+ def test_failing_authenticate
+ process :authenticate, "user_name" => "nop", "password" => ""
+ assert_flash_has 'alert'
+ assert_redirected_to :action => "index"
+ end
+
+ See a full example on http://codepaste.org/view/paste/334
+
+* Increased performance by up to 100% with a revised cookie class that fixes the performance problems with the
+ default one that ships with 1.8.1 and below. It replaces the inheritance on SimpleDelegator with DelegateClass(Array)
+ following the suggestion from Matz on:
+ http://groups.google.com/groups?th=e3a4e68ba042f842&seekm=c3sioe%241qvm%241%40news.cybercity.dk#link14
+
+* Added caching for compiled ERb templates. On Basecamp, it gave between 8.5% and 71% increase in performance *Andreas Schwarz*.
+
+* Added implicit counter variable to render_collection_of_partials [Marcel Molina Jr.]. From the docs:
+
+ <%= render_collection_of_partials "ad", @advertisements %>
+
+ This will render "advertiser/_ad.rhtml" and pass the local variable +ad+ to the template for display. An iteration counter
+ will automatically be made available to the template with a name of the form +partial_name_counter+. In the case of the
+ example above, the template would be fed +ad_counter+.
+
+* Fixed problems with two sessions being maintained on reset_session that would particularly screw up ActiveRecordStore.
+
+* Fixed reset_session to start an entirely new session instead of merely deleting the old. So you can now safely access @session
+ after calling reset_ression and expect it to work.
+
+* Added @request.get?, @request.post?, @request.put?, @request.delete? as convenience query methods for @request.method *geech*
+
+* Added @request.method that'll return a symbol representing the HTTP method, such as :get, :post, :put, :delete *geech*
+
+* Changed @request.remote_ip and @request.host to work properly even when a proxy is in front of the application *geech*
+
+* Added JavaScript confirm feature to link_to. Documentation:
+
+ The html_options have a special feature for creating javascript confirm alerts where if you pass
+ :confirm => 'Are you sure?', the link will be guarded with a JS popup asking that question.
+ If the user accepts, the link is processed, otherwise not.
+
+* Added link_to_unless_current as a UrlHelper method *Sam Stephenson*. Documentation:
+
+ Creates a link tag of the given +name+ using an URL created by the set of +options+, unless the current
+ controller, action, and id are the same as the link's, in which case only the name is returned (or the
+ given block is yielded, if one exists). This is useful for creating link bars where you don't want to link
+ to the page currently being viewed.
+
+* Fixed that UrlRewriter (the driver for url_for, link_to, etc) would blow up when the anchor was an integer *alexey*
+
+* Added that layouts defined with no directory defaults to layouts. So layout "weblog/standard" will use
+ weblog/standard (as always), but layout "standard" will use layouts/standard.
+
+* Fixed that partials (or any template starting with an underscore) was publically viewable *Marten*
+
+* Added HTML escaping to text_area helper.
+
+* Added :overwrite_params to url_for and friends to keep the parameters as they were passed to the current action and only overwrite a subset.
+ The regular :params will clear the slate so you need to manually add in existing parameters if you want to reuse them. *raphinou*
+
+* Fixed scaffolding problem with composite named objects *Moo Jester*
+
+* Added the possibility for shared partials. Example:
+
+ <%= render_partial "advertisement/ad", ad %>
+
+ This will render the partial "advertisement/_ad.rhtml" regardless of which controller this is being called from.
+
+ *Jacob Fugal*
+
+* Fixed crash when encountering forms that have empty-named fields *James Prudente*
+
+* Added check_box form helper method now accepts true/false as well as 1/0 *what-a-day*
+
+* Fixed the lacking creation of all directories with install.rb *Dave Steinberg*
+
+* Fixed that date_select returns valid XHTML selected options *Andreas Schwarz*
+
+* Fixed referencing an action with the same name as a controller in url_for *what-a-day*
+
+* Fixed the destructive nature of Base#attributes= on the argument *Kevin Watt*
+
+* Changed ActionControllerError to decent from StandardError instead of Exception. It can now be caught by a generic rescue.
+
+* Added SessionRestoreError that is raised when a session being restored holds objects where there is no class available.
+
+* Added block as option for inline filters. So what used to be written as:
+
+ before_filter Proc { |controller| return false if controller.params["stop_action"] }
+
+ ...can now be as:
+
+ before_filter { |controller| return false if controller.params["stop_action"] }
+
+ *Jeremy Kemper*
+
+* Made the following methods public (was protected): url_for, controller_class_name, controller_name, action_name
+ This makes it easier to write filters without cheating around the encapsulation with send.
+
+* ActionController::Base#reset_session now sticks even if you access @session afterwards *Kent Sibilev*
+
+* Improved the exception logging so the log file gets almost as much as in-browser debugging.
+
+* Changed base class setup from AbstractTemplate/ERbTemplate to ActionView::Base. This change should be harmless unless you were
+ accessing Action View directly in which case you now need to reference the Base class.\
+
+* Added that render_collection_of_partials returns nil if the collection is empty. This makes showing a “no items” message easier.
+ For example: <%= render_collection_of_partials("message", @messages) || "No messages found." %> *Sam Stephenson*
+
+* Added :month_before_year as an option to date_select to get the month select before the year. Especially useful for credit card forms.
+
+* Added :add_month_numbers to select_month to get options like "3 - March".
+
+* Removed Base.has_active_layout? as it couldn't answer the question without the instance. Use Base#active_layout instead.
+
+* Removed redundant call to update on ActionController::Base#close_session *Andreas Schwarz*
+
+* Fixed that DRb Store accidently started its own server (instead of just client) *Andreas*
+
+* Fixed strip_links so it now works across multiple lines *Chad Fowler*
+
+* Fixed the TemplateError exception to show the proper trace on to_s (useful for unit test debugging)
+
+* Implemented class inheritable attributes without eval *Caio Chassot*
+
+* Made TextHelper#concat accept binding as it would otherwise not work
+
+* The FormOptionsHelper will now call to_s on the keys and values used to generate options
+
+
+## 0.8.5 ##
+
+* Introduced passing of locally scoped variables between templates:
+
+ You can pass local variables to sub templates by using a hash of with the variable
+ names as keys and the objects as values:
+
+ <%= render "shared/header", { "headline" => "Welcome", "person" => person } %>
+
+ These can now be accessed in shared/header with:
+
+ Headline: <%= headline %>
+ First name: <%= person.first_name %>
+
+* Introduced the concept of partials as a certain type of sub templates:
+
+ There's also a convenience method for rendering sub templates within the current
+ controller that depends on a single object (we call this kind of sub templates for
+ partials). It relies on the fact that partials should follow the naming convention
+ of being prefixed with an underscore -- as to separate them from regular templates
+ that could be rendered on their own. In the template for Advertiser#buy, we could have:
+
+ <% for ad in @advertisements %>
+ <%= render_partial "ad", ad %>
+ <% end %>
+
+ This would render "advertiser/_ad.rhtml" and pass the local variable +ad+
+ for the template to display.
+
+ == Rendering a collection of partials
+
+ The example of partial use describes a familar pattern where a template needs
+ to iterate over an array and render a sub template for each of the elements.
+ This pattern has been implemented as a single method that accepts an array and
+ renders a partial by the same name of as the elements contained within. So the
+ three-lined example in "Using partials" can be rewritten with a single line:
+
+ <%= render_collection_of_partials "ad", @advertisements %>
+
+ So this will render "advertiser/_ad.rhtml" and pass the local variable +ad+ for
+ the template to display.
+
+* Improved send_file by allowing a wide range of options to be applied *Jeremy Kemper*:
+
+ Sends the file by streaming it 4096 bytes at a time. This way the
+ whole file doesn't need to be read into memory at once. This makes
+ it feasible to send even large files.
+
+ Be careful to sanitize the path parameter if it coming from a web
+ page. send_file(@params['path'] allows a malicious user to
+ download any file on your server.
+
+ Options:
+ * <tt>:filename</tt> - specifies the filename the browser will see.
+ Defaults to File.basename(path).
+ * <tt>:type</tt> - specifies an HTTP content type.
+ Defaults to 'application/octet-stream'.
+ * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ Valid values are 'inline' and 'attachment' (default).
+ * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream
+ the file. Defaults to 4096.
+
+ The default Content-Type and Content-Disposition headers are
+ set to download arbitrary binary files in as many browsers as
+ possible. IE versions 4, 5, 5.5, and 6 are all known to have
+ a variety of quirks (especially when downloading over SSL).
+
+ Simple download:
+ send_file '/path/to.zip'
+
+ Show a JPEG in browser:
+ send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
+
+ Read about the other Content-* HTTP headers if you'd like to
+ provide the user with more information (such as Content-Description).
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
+
+ Also be aware that the document may be cached by proxies and browsers.
+ The Pragma and Cache-Control headers declare how the file may be cached
+ by intermediaries. They default to require clients to validate with
+ the server before releasing cached responses. See
+ http://www.mnot.net/cache_docs/ for an overview of web caching and
+ http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ for the Cache-Control header spec.
+
+* Added pluralize method to the TextHelper that makes it easy to get strings like "1 message", "3 messages"
+
+* Added proper escaping for the rescues *Andreas Schwarz*
+
+* Added proper escaping for the option and collection tags *Andreas Schwarz*
+
+* Fixed NaN errors on benchmarking *Jim Weirich*
+
+* Fixed query string parsing for URLs that use the escaped versions of & or ; as part of a key or value
+
+* Fixed bug with custom Content-Type headers being in addition to rather than instead of the default header.
+ (This bug didn't matter with neither CGI or mod_ruby, but FCGI exploded on it) *With help from Ara T. Howard*
+
+
+## 0.8.0 ##
+
+* Added select, collection_select, and country_select to make it easier for Active Records to set attributes through
+ drop-down lists of options. Example:
+
+ <%= select "person", "gender", %w( Male Female ) %>
+
+ ...would give the following:
+
+ <select name="person[gender]" id="person_gender"><option>Male</option><option>Female</option></select>
+
+* Added an option for getting multiple values on a single form name into an array instead of having the last one overwrite.
+ This is especially useful for groups of checkboxes, which can now be written as:
+
+ <input type="checkbox" name="rights[]" value="CREATE" />
+ <input type="checkbox" name="rights[]" value="UPDATE" />
+ <input type="checkbox" name="rights[]" value="DELETE" />
+
+ ...and retrieved in the controller action with:
+
+ @params["rights"] # => [ "CREATE", "UPDATE", "DELETE" ]
+
+ The old behavior (where the last one wins, "DELETE" in the example) is still available. Just don't add "[]" to the
+ end of the name. *Scott Baron*
+
+* Added send_file which uses the new render_text block acceptance to make it feasible to send large files.
+ The files is sent with a bunch of voodoo HTTP headers required to get arbitrary files to download as
+ expected in as many browsers as possible (eg, IE hacks). Example:
+
+ def play_movie
+ send_file "/movies/that_movie.avi"
+ end
+
+ *Jeremy Kemper*
+
+* render_text now accepts a block for deferred rendering. Useful for streaming large files, displaying
+ a “please wait” message during a complex search, etc. Streaming example:
+
+ render_text do |response|
+ File.open(path, 'rb') do |file|
+ while buf = file.read(1024)
+ print buf
+ end
+ end
+ end
+
+ *Jeremy Kemper*
+
+* Added a new Tag Helper that can generate generic tags programmatically insted of through HTML. Example:
+
+ tag("br", "clear" => "all") => <br clear="all" />
+
+ ...that's usually not terribly interesting (unless you have a lot of options already in a hash), but it
+ gives way for more specific tags, like the new form tag:
+
+ form_tag({ :controller => "weblog", :action => "update" }, { :multipart => "true", "style" => "width: 200px"}) =>
+ <form action="/weblog/update" enctype="multipart/formdata" style="width: 200px">
+
+ There's even a "pretty" version for people who don't like to open tags in code and close them in HTML:
+
+ <%= start_form_tag :action => "update" %>
+ # all the input fields
+ <%= end_form_tag %>
+
+ (end_form_tag just returns "</form>")
+
+* The selected parameter in options_for_select may now also an array of values to be selected when
+ using a multiple select. Example:
+
+ options_for_select([ "VISA", "Mastercard", "Discover" ], ["VISA", "Discover"]) =>
+ <option selected>VISA</option>\n<option>Mastercard</option>\n<option selected>Discover</option>
+
+ *Scott Baron*
+
+* Changed the URL rewriter so controller_prefix and action_prefix can be used in isolation. You can now do:
+
+ url_for(:controller_prefix => "clients")
+
+ ...or:
+
+ url_for(:action_prefix => "category/messages")
+
+ Neither would have worked in isolation before (:controller_prefix required a :controller and :action_prefix required an :action)
+
+* Started process of a cleaner separation between Action Controller and ERb-based Action Views by introducing an
+ abstract base class for views. And Amita adapter could be fitted in more easily now.
+
+* The date helper methods date_select and datetime_select now also use the field error wrapping
+ (div with class fieldWithErrors by default).
+
+* The date helper methods date_select and datetime_select can now discard selects
+
+* Added option on AbstractTemplate to specify a different field error wrapping. Example:
+
+ ActionView::AbstractTemplate.field_error_proc = Proc.new do |html, instance|
+ "<p>#{instance.method_name + instance.error_message}</p><div style='background-color: red'>#{html}</div>"
+ end
+
+ ...would give the following on a Post#title (text field) error:
+
+ <p>Title can't be empty</p>
+ <div style='background-color: red'>
+ <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
+ </div>
+
+* The UrlHelper methods url_for and link_to will now by default only return paths, not complete URIs.
+ That should make it easier to fit a Rails application behind a proxy or load-balancer.
+ You can overwrite this by passing :only_path => false as part of the options. *Suggested by U235*
+
+* Fixed bug with having your own layout for use with scaffolding *Kevin Radloff*
+
+* Fixed bug where redirect_to_path didn't append the port on non-standard ports *dhawkins*
+
+* Scaffolding plays nicely with single-table inheritance (LoadErrors are caught) *Jeremy Kemper*
+
+* Scaffolding plays nice with plural models like Category/categories *Jeremy Kemper*
+
+* Fixed missing suffix appending in scaffolding *Kevin Radloff*
+
+
+## 0.7.9 ##
+
+* The "form" method now present boolean fields from PostgreSQL as drop-down menu. *Scott*
+
+* Scaffolding now automatically attempts to require the class that's being scaffolded.
+
+* Scaffolding will use the current active layout, instead of its own, if one has been specified. Example:
+
+ class WeblogController < ActionController::Base
+ layout "layouts/weblog"
+ scaffold :post
+ end
+
+ *Suggested by Scott*
+
+* Changed url_for (and all the that drives, like redirect_to, link_to, link_for) so you can pass it a symbol instead of a hash.
+ This symbol is a method reference which is then called to calculate the url. Example:
+
+ class WeblogController < ActionController::Base
+ def update
+ # do some update
+ redirect_to :dashboard_url
+ end
+
+ protected
+ def dashboard_url
+ if @project.active?
+ url_for :controller => "project", :action => "dashboard"
+ else
+ url_for :controller => "account", :action => "dashboard"
+ end
+ end
+ end
+
+* Added default_url_options to specialize behavior for all url_for (and friends) calls:
+
+ Overwrite to implement a number of default options that all url_for-based methods will use.
+ The default options should come in form of a hash, just like the one you would use for
+ url_for directly. Example:
+
+ def default_url_options(options)
+ { :controller_prefix => @project.active? ? "projects/" : "accounts/" }
+ end
+
+ As you can infer from the example, this is mostly useful for situations where you want to
+ centralize dynamic dissions about the urls as they stem from the business domain. Please note
+ that any individual url_for call can always override the defaults set by this method.
+
+
+* Changed url_for so that an "id" passed in the :params is not treated special. You need to use the dedicated :id to get
+ the special auto path-params treatment. Considering the url http://localhost:81/friends/list
+
+ url_for(:action => "show", :params => { "id" => 5 })
+ ...used to give http://localhost:81/friends/show/5
+ ......now gives http://localhost:81/friends/show?id=5
+
+ If you want the automated id behavior, do:
+
+ url_for(:action => "show", :id => 5 )
+ ....which gives http://localhost:81/friends/show/5
+
+
+* Fixed problem with anchor being inserted before path parameters with url_for (and friends)
+
+
+## 0.7.8 ##
+
+* Fixed session bug where you couldn't store any objects that didn't exist in the standard library
+ (such as Active Record objects).
+
+* Added reset_session method for Action Controller objects to clear out all objects in the session.
+
+* Fixed that exceptions raised during filters are now also caught by the default rescues
+
+* Added new around_filter for doing before and after filtering with a single object *Florian Weber*:
+
+ class WeblogController < ActionController::Base
+ around_filter BenchmarkingFilter.new
+
+ # Before this action is performed, BenchmarkingFilter#before(controller) is executed
+ def index
+ end
+ # After this action has been performed, BenchmarkingFilter#after(controller) is executed
+ end
+
+ class BenchmarkingFilter
+ def initialize
+ @runtime
+ end
+
+ def before
+ start_timer
+ end
+
+ def after
+ stop_timer
+ report_result
+ end
+ end
+
+* Added the options for specifying a different name and id for the form helper methods than what is guessed *Florian Weber*:
+
+ text_field "post", "title"
+ ...just gives: <input id="post_title" name="post[title]" size="30" type="text" value="" />
+
+ text_field "post", "title", "id" => "title_for_post", "name" => "first_post_title"
+ ...can now give: <input id="title_for_post" name="first_post_title" size="30" type="text" value="" />
+
+* Added DebugHelper with a single "debug" method for doing pretty dumps of objects in the view
+ (now used in the default rescues to better present the contents of session and template variables)
+
+* Added note to log about the templates rendered within layouts (before just the layout was shown)
+
+* Fixed redirects on https setups *Andreas*
+
+* Fixed scaffolding problem on the edit action when using :suffix => true *Scott*
+
+* Fixed scaffolding problem where implementing list.rhtml wouldn't work for the index action
+
+* URLs generated now uses &amp; instead of just & so pages using it can validate with W3C *Spotted by Andreas*
+
+
+## 0.7.7 ##
+
+* Fixed bug in CGI extension that prevented multipart forms from working
+
+
+## 0.7.6 ##
+
+* Included ERB::Util so all templates can easily escape HTML content with <%=h @person.content %>
+
+* All requests are now considered local by default, so everyone will be exposed to detailed debugging screens on errors.
+ When the application is ready to go public, set ActionController::Base.consider_all_requests_local to false,
+ and implement the protected method local_request? in the controller to determine when debugging screens should be shown.
+
+* Fixed three bugs with the url_for/redirect_to/link_to handling. Considering the url http://localhost:81/friends/show/1
+
+ url_for(:action => "list")
+ ...used to give http://localhost:81/friends/list/1
+ ......now gives http://localhost:81/friends/list
+
+ url_for(:controller => "friends", :action => "destroy", :id => 5)
+ ...used to give http://localhost:81/friends/destroy
+ ......now gives http://localhost:81/friends/destroy/5
+
+ Considering the url http://localhost:81/teachers/show/t
+
+ url_for(:action => "list", :id => 5)
+ ...used to give http://localhost:81/5eachers/list/t
+ ......now gives http://localhost:81/teachers/list/5
+
+ *Reported by David Morton & Radsaq*
+
+* Logs exception to logfile in addition to showing them for local requests
+
+* Protects the eruby load behind a begin/rescue block. eRuby is not required to run ActionController.
+
+* Fixed install.rb to also install clean_logger and the templates
+
+* Added ActiveRecordStore as a session option. Read more in lib/action_controller/session/active_record_store.rb *Tim Bates*
+
+* Change license to MIT License (and included license file in package)
+
+* Application error page now returns status code 500 instead of 200
+
+* Fixed using Procs as layout handlers *Florian Weber*
+
+* Fixed bug with using redirects ports other than 80
+
+* Added index method that calls list on scaffolding
+
+
+## 0.7.5 ##
+
+* First public release
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index 7ad1051066..810daf856c 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2011 David Heinemeier Hansson
+Copyright (c) 2004-2012 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc
index 95301c21ee..1fdc57e14d 100644
--- a/actionpack/README.rdoc
+++ b/actionpack/README.rdoc
@@ -206,7 +206,7 @@ A short rundown of some of the major features:
You specify a logger through a class method, such as:
- ActionController::Base.logger = Logger.new("Application Log")
+ ActionController::Base.logger = ActiveSupport::Logger.new("Application Log")
ActionController::Base.logger = Log4r::Logger.new("Application Log")
@@ -218,7 +218,7 @@ A short rundown of some of the major features:
def show
# the output of the method will be cached as
- # ActionController::Base.page_cache_directory + "/weblog/show/n.html"
+ # ActionController::Base.page_cache_directory + "/weblog/show.html"
# and the web server will pick it up without even hitting Rails
end
@@ -327,7 +327,9 @@ Source code can be downloaded as part of the Rails project on GitHub
== License
-Action Pack is released under the MIT license.
+Action Pack is released under the MIT license:
+
+* http://www.opensource.org/licenses/MIT
== Support
diff --git a/actionpack/RUNNING_UNIT_TESTS b/actionpack/RUNNING_UNIT_TESTS
index 1e3ba7abe7..1b29abd2d1 100644
--- a/actionpack/RUNNING_UNIT_TESTS
+++ b/actionpack/RUNNING_UNIT_TESTS
@@ -18,7 +18,7 @@ which can be further narrowed down to one test:
== Dependency on Active Record and database setup
-Test cases in the test/controller/active_record/ directory depend on having
+Test cases in the test/active_record/ directory depend on having
activerecord and sqlite installed. If Active Record is not in
actionpack/../activerecord directory, or the sqlite rubygem is not installed,
these tests are skipped.
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index d9e3e56fcc..50e3bb0d48 100755..100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rubygems/package_task'
@@ -16,7 +15,7 @@ Rake::TestTask.new(:test_action_pack) do |t|
# make sure we include the tests in alphabetical order as on some systems
# this will not happen automatically and the tests (as a whole) will error
- t.test_files = Dir.glob('test/{abstract,controller,dispatch,template}/**/*_test.rb').sort
+ t.test_files = Dir.glob('test/{abstract,controller,dispatch,template,assertions}/**/*_test.rb').sort
t.warning = true
t.verbose = true
@@ -24,6 +23,7 @@ end
namespace :test do
Rake::TestTask.new(:isolated) do |t|
+ t.libs << 'test'
t.pattern = 'test/ts_isolated.rb'
end
@@ -55,15 +55,15 @@ end
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
- for file_name in FileList["lib/**/*.rb"]
+ FileList["lib/**/*.rb"].each do |file_name|
next if file_name =~ /vendor/
- f = File.open(file_name)
-
- while line = f.gets
- lines += 1
- next if line =~ /^\s*$/
- next if line =~ /^\s*#/
- codelines += 1
+ File.open(file_name, 'r') do |f|
+ while line = f.gets
+ lines += 1
+ next if line =~ /^\s*$/
+ next if line =~ /^\s*#/
+ codelines += 1
+ end
end
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 78b9a4d5a8..589a67dc02 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -6,25 +6,23 @@ Gem::Specification.new do |s|
s.version = version
s.summary = 'Web-flow and rendering framework putting the VC in MVC (part of Rails).'
s.description = 'Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server.'
- s.required_ruby_version = '>= 1.8.7'
+ s.required_ruby_version = '>= 1.9.3'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
+ s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
s.requirements << 'none'
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('rack-cache', '~> 1.1')
+ s.add_dependency('rack-cache', '~> 1.2')
s.add_dependency('builder', '~> 3.0.0')
- s.add_dependency('i18n', '~> 0.6')
- s.add_dependency('rack', '~> 1.3.5')
+ s.add_dependency('rack', '~> 1.4.1')
s.add_dependency('rack-test', '~> 0.6.1')
- s.add_dependency('journey', '~> 1.0.0')
- s.add_dependency('sprockets', '~> 2.0.2')
+ s.add_dependency('journey', '~> 1.0.1')
s.add_dependency('erubis', '~> 2.7.0')
s.add_development_dependency('tzinfo', '~> 0.3.29')
diff --git a/actionpack/examples/performance.rb b/actionpack/examples/performance.rb
new file mode 100644
index 0000000000..994e745bb0
--- /dev/null
+++ b/actionpack/examples/performance.rb
@@ -0,0 +1,185 @@
+ENV['RAILS_ENV'] ||= 'production'
+
+require File.expand_path('../../../load_paths', __FILE__)
+require 'action_pack'
+require 'action_controller'
+require 'action_view'
+require 'active_model'
+require 'benchmark'
+
+MyHash = Class.new(Hash)
+
+Hash.class_eval do
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+end
+
+class Runner
+ def initialize(app, output)
+ @app, @output = app, output
+ end
+
+ def puts(*)
+ super if @output
+ end
+
+ def call(env)
+ env['n'].to_i.times { @app.call(env) }
+ @app.call(env).tap { |response| report(env, response) }
+ end
+
+ def report(env, response)
+ return unless ENV["DEBUG"]
+ out = env['rack.errors']
+ out.puts response[0], response[1].to_yaml, '---'
+ response[2].each { |part| out.puts part }
+ out.puts '---'
+ end
+
+ def self.puts(*)
+ super if @output
+ end
+
+ def self.print(*)
+ super if @output
+ end
+
+ def self.app_and_env_for(action, n)
+ env = Rack::MockRequest.env_for("/")
+ env.merge!('n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout)
+ app = lambda { |env| BasePostController.action(action).call(env) }
+ return app, env
+ end
+
+ $ran = []
+
+ def self.run(action, n, output = true)
+ print "."
+ STDOUT.flush
+ @output = output
+ label = action.to_s
+ app, env = app_and_env_for(action, n)
+ t = Benchmark.realtime { new(app, output).call(env) }
+ $ran << [label, (t * 1000).to_i.to_s] if output
+ end
+
+ def self.done
+ puts
+ header, content = "", ""
+ $ran.each do |k,v|
+ size = [k.size, v.size].max + 1
+ header << format("%#{size}s", k)
+ content << format("%#{size}s", v)
+ end
+ puts header
+ puts content
+ end
+end
+
+ActionController::Base.logger = nil
+ActionController::Base.config.compile_methods!
+ActionView::Resolver.caching = ENV["RAILS_ENV"] == "production"
+
+class BasePostController < ActionController::Base
+ append_view_path "#{File.dirname(__FILE__)}/views"
+
+ def overhead
+ self.response_body = ''
+ end
+
+ def index
+ render :text => ''
+ end
+
+ $OBJECT = {:name => "Hello my name is omg", :address => "333 omg"}
+
+ def partial
+ render :partial => "/collection", :object => $OBJECT
+ end
+
+ def partial_10
+ render :partial => "/ten_partials"
+ end
+
+ def partial_100
+ render :partial => "/hundred_partials"
+ end
+
+ $COLLECTION1 = []
+ 10.times do |i|
+ $COLLECTION1 << { :name => "Hello my name is omg", :address => "333 omg" }
+ end
+
+ def coll_10
+ render :partial => "/collection", :collection => $COLLECTION1
+ end
+
+ $COLLECTION2 = []
+ 100.times do |i|
+ $COLLECTION2 << { :name => "Hello my name is omg", :address => "333 omg" }
+ end
+
+ def coll_100
+ render :partial => "/collection", :collection => $COLLECTION2
+ end
+
+ def uniq_100
+ render :partial => $COLLECTION2
+ end
+
+ $COLLECTION3 = []
+ 50.times do |i|
+ $COLLECTION3 << {:name => "Hello my name is omg", :address => "333 omg"}
+ $COLLECTION3 << MyHash.new(:name => "Hello my name is omg", :address => "333 omg")
+ end
+
+ def diff_100
+ render :partial => $COLLECTION3
+ end
+
+ def template_1
+ render :template => "template"
+ end
+
+ module Foo
+ def omg
+ "omg"
+ end
+ end
+
+ helper Foo
+end
+
+N = (ENV['N'] || 1000).to_i
+# ActionController::Base.use_accept_header = false
+
+def run_all!(times, verbose)
+ Runner.run(:overhead, times, verbose)
+ Runner.run(:index, times, verbose)
+ Runner.run(:template_1, times, verbose)
+ Runner.run(:partial, times, verbose)
+ Runner.run(:partial_10, times, verbose)
+ Runner.run(:coll_10, times, verbose)
+ Runner.run(:partial_100, times, verbose)
+ Runner.run(:coll_100, times, verbose)
+ Runner.run(:uniq_100, times, verbose)
+ Runner.run(:diff_100, times, verbose)
+end
+
+unless ENV["PROFILE"]
+ run_all!(1, false)
+
+ (ENV["M"] || 1).to_i.times do
+ $ran = []
+ run_all!(N, true)
+ Runner.done
+ end
+else
+ Runner.run(ENV["PROFILE"].to_sym, 1, false)
+ require "ruby-prof"
+ RubyProf.start
+ Runner.run(ENV["PROFILE"].to_sym, N, true)
+ result = RubyProf.stop
+ printer = RubyProf::CallStackPrinter.new(result)
+ printer.print(File.open("output.html", "w"))
+end \ No newline at end of file
diff --git a/actionpack/examples/views/_collection.erb b/actionpack/examples/views/_collection.erb
new file mode 100644
index 0000000000..cee3fe64c0
--- /dev/null
+++ b/actionpack/examples/views/_collection.erb
@@ -0,0 +1,3 @@
+<%= collection[:name] %>
+<%= collection[:address] %>
+<%= omg %> \ No newline at end of file
diff --git a/actionpack/examples/views/_hello.erb b/actionpack/examples/views/_hello.erb
new file mode 100644
index 0000000000..5ab2f8a432
--- /dev/null
+++ b/actionpack/examples/views/_hello.erb
@@ -0,0 +1 @@
+Hello \ No newline at end of file
diff --git a/actionpack/examples/views/_hundred_partials.erb b/actionpack/examples/views/_hundred_partials.erb
new file mode 100644
index 0000000000..35c2a6c9d3
--- /dev/null
+++ b/actionpack/examples/views/_hundred_partials.erb
@@ -0,0 +1,3 @@
+<% 100.times do %>
+ <%= render :partial => "/collection", :object => $OBJECT %>
+<% end %> \ No newline at end of file
diff --git a/actionpack/examples/views/_partial.erb b/actionpack/examples/views/_partial.erb
new file mode 100644
index 0000000000..3ca8e80b52
--- /dev/null
+++ b/actionpack/examples/views/_partial.erb
@@ -0,0 +1,10 @@
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
diff --git a/actionpack/examples/views/_ten_partials.erb b/actionpack/examples/views/_ten_partials.erb
new file mode 100644
index 0000000000..fd02991e22
--- /dev/null
+++ b/actionpack/examples/views/_ten_partials.erb
@@ -0,0 +1,10 @@
+<%= render :partial => '/collection', :object => $OBJECT %>
+<%= render :partial => '/collection', :object => $OBJECT %>
+<%= render :partial => '/collection', :object => $OBJECT %>
+<%= render :partial => '/collection', :object => $OBJECT %>
+<%= render :partial => '/collection', :object => $OBJECT %>
+<%= render :partial => '/collection', :object => $OBJECT %>
+<%= render :partial => '/collection', :object => $OBJECT %>
+<%= render :partial => '/collection', :object => $OBJECT %>
+<%= render :partial => '/collection', :object => $OBJECT %>
+<%= render :partial => '/collection', :object => $OBJECT %> \ No newline at end of file
diff --git a/actionpack/examples/views/hashes/_hash.erb b/actionpack/examples/views/hashes/_hash.erb
new file mode 100644
index 0000000000..c100a290bd
--- /dev/null
+++ b/actionpack/examples/views/hashes/_hash.erb
@@ -0,0 +1,3 @@
+<%= hash[:name] %>
+<%= hash[:address] %>
+<%= omg %> \ No newline at end of file
diff --git a/actionpack/examples/views/my_hashes/_my_hash.erb b/actionpack/examples/views/my_hashes/_my_hash.erb
new file mode 100644
index 0000000000..e25d84101a
--- /dev/null
+++ b/actionpack/examples/views/my_hashes/_my_hash.erb
@@ -0,0 +1,3 @@
+<%= my_hash[:name] %>
+<%= my_hash[:address] %>
+<%= omg %> \ No newline at end of file
diff --git a/actionpack/examples/views/template.html.erb b/actionpack/examples/views/template.html.erb
new file mode 100644
index 0000000000..5ab2f8a432
--- /dev/null
+++ b/actionpack/examples/views/template.html.erb
@@ -0,0 +1 @@
+Hello \ No newline at end of file
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb
index cc5878c88e..b95ea5f0b2 100644
--- a/actionpack/lib/abstract_controller.rb
+++ b/actionpack/lib/abstract_controller.rb
@@ -1,9 +1,5 @@
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
require 'action_pack'
require 'active_support/concern'
-require 'active_support/ruby/shim'
require 'active_support/dependencies/autoload'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/module/attr_internal'
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index c2a6809f58..822254b1a4 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -1,10 +1,10 @@
module AbstractController
- module AssetPaths
+ module AssetPaths #:nodoc:
extend ActiveSupport::Concern
included do
config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir,
- :stylesheets_dir, :default_asset_host_protocol
+ :stylesheets_dir, :default_asset_host_protocol, :relative_url_root
end
end
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index fd6a46fbec..97a9eec144 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,11 +1,15 @@
require 'erubis'
+require 'set'
require 'active_support/configurable'
require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
module AbstractController
- class Error < StandardError; end
- class ActionNotFound < StandardError; end
+ class Error < StandardError #:nodoc:
+ end
+
+ class ActionNotFound < StandardError #:nodoc:
+ end
# <tt>AbstractController::Base</tt> is a low-level API. Nobody should be
# using it directly, and subclasses (like ActionController::Base) are
@@ -42,8 +46,8 @@ module AbstractController
controller.public_instance_methods(true)
end
- # The list of hidden actions to an empty array. Defaults to an
- # empty array. This can be modified by other modules or subclasses
+ # The list of hidden actions. Defaults to an empty array.
+ # This can be modified by other modules or subclasses
# to specify particular actions as hidden.
#
# ==== Returns
@@ -59,7 +63,7 @@ module AbstractController
# itself. Finally, #hidden_actions are removed.
#
# ==== Returns
- # * <tt>array</tt> - A list of all methods that should be considered actions.
+ # * <tt>set</tt> - A set of all methods that should be considered actions.
def action_methods
@action_methods ||= begin
# All public instance methods of this class, including ancestors
@@ -72,7 +76,7 @@ module AbstractController
hidden_actions.to_a
# Clear out AS callback method pollution
- methods.reject { |method| method =~ /_one_time_conditions/ }
+ Set.new(methods.reject { |method| method =~ /_one_time_conditions/ })
end
end
@@ -85,7 +89,7 @@ module AbstractController
# Returns the full controller name, underscored, without the ending Controller.
# For instance, MyApp::MyPostsController would return "my_app/my_posts" for
- # controller_name.
+ # controller_path.
#
# ==== Returns
# * <tt>string</tt>
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 7004e607a1..5705ab590c 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -8,24 +8,22 @@ module AbstractController
include ActiveSupport::Callbacks
included do
- define_callbacks :process_action, :terminator => "response_body"
+ define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true
end
# Override AbstractController::Base's process_action to run the
# process_action callbacks around the normal behavior.
def process_action(*args)
- run_callbacks(:process_action, action_name) do
+ run_callbacks(:process_action) do
super
end
end
module ClassMethods
# If :only or :except are used, convert the options into the
- # primitive form (:per_key) used by ActiveSupport::Callbacks.
+ # :unless and :if options of ActiveSupport::Callbacks.
# The basic idea is that :only => :index gets converted to
- # :if => proc {|c| c.action_name == "index" }, but that the
- # proc is only evaluated once per action for the lifetime of
- # a Rails process.
+ # :if => proc {|c| c.action_name == "index" }.
#
# ==== Options
# * <tt>only</tt> - The callback should be run only for this action
@@ -33,11 +31,11 @@ module AbstractController
def _normalize_callback_options(options)
if only = options[:only]
only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ")
- options[:per_key] = {:if => only}
+ options[:if] = Array(options[:if]) << only
end
if except = options[:except]
except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ")
- options[:per_key] = {:unless => except}
+ options[:unless] = Array(options[:unless]) << except
end
end
@@ -48,7 +46,7 @@ module AbstractController
# callbacks. Note that skipping uses Ruby equality, so it's
# impossible to skip a callback defined using an anonymous proc
# using #skip_filter
- def skip_filter(*names, &blk)
+ def skip_filter(*names)
skip_before_filter(*names)
skip_after_filter(*names)
skip_around_filter(*names)
@@ -66,7 +64,7 @@ module AbstractController
# ==== Block Parameters
# * <tt>name</tt> - The callback to be added
# * <tt>options</tt> - A hash of options to be used when adding the callback
- def _insert_callbacks(callbacks, block)
+ def _insert_callbacks(callbacks, block = nil)
options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
_normalize_callback_options(options)
callbacks.push(block) if block
@@ -92,7 +90,7 @@ module AbstractController
##
# :method: skip_before_filter
#
- # :call-seq: skip_before_filter(names, block)
+ # :call-seq: skip_before_filter(names)
#
# Skip a before filter. See _insert_callbacks for parameter details.
@@ -120,7 +118,7 @@ module AbstractController
##
# :method: skip_after_filter
#
- # :call-seq: skip_after_filter(names, block)
+ # :call-seq: skip_after_filter(names)
#
# Skip an after filter. See _insert_callbacks for parameter details.
@@ -148,7 +146,7 @@ module AbstractController
##
# :method: skip_around_filter
#
- # :call-seq: skip_around_filter(names, block)
+ # :call-seq: skip_around_filter(names)
#
# Skip an around filter. See _insert_callbacks for parameter details.
@@ -165,29 +163,27 @@ module AbstractController
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
# Append a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array.wrap(options[:if]) << "!halted") if false
- set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
- end # end
- end # end
+ def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk)
+ _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
+ set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options)
+ end # end
+ end # end
# Prepend a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk)
_insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array.wrap(options[:if]) << "!halted") if false
set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true))
end # end
end # end
# Skip a before, after or around filter. See _insert_callbacks
# for details on the allowed parameters.
- def skip_#{filter}_filter(*names, &blk) # def skip_before_filter(*names, &blk)
- _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options|
- skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options)
- end # end
- end # end
+ def skip_#{filter}_filter(*names) # def skip_before_filter(*names)
+ _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options|
+ skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options)
+ end # end
+ end # end
# *_filter is the same as append_*_filter
alias_method :append_#{filter}_filter, :#{filter}_filter # alias_method :append_before_filter, :before_filter
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 77cc4c07d9..4e0672d590 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -49,9 +49,9 @@ module AbstractController
meths.each do |meth|
_helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
- def #{meth}(*args, &blk)
- controller.send(%(#{meth}), *args, &blk)
- end
+ def #{meth}(*args, &blk) # def current_user(*args, &blk)
+ controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk)
+ end # end
ruby_eval
end
end
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index 10aa34c76b..bc9f6fc3e8 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -66,27 +66,40 @@ module AbstractController
# == Inheritance Examples
#
# class BankController < ActionController::Base
- # layout "bank_standard"
+ # # bank.html.erb exists
+ #
+ # class ExchangeController < BankController
+ # # exchange.html.erb exists
+ #
+ # class CurrencyController < BankController
#
# class InformationController < BankController
+ # layout "information"
#
- # class TellerController < BankController
+ # class TellerController < InformationController
# # teller.html.erb exists
#
- # class TillController < TellerController
+ # class EmployeeController < InformationController
+ # # employee.html.erb exists
+ # layout nil
#
# class VaultController < BankController
# layout :access_level_layout
#
- # class EmployeeController < BankController
- # layout nil
+ # class TillController < BankController
+ # layout false
#
- # In these examples:
- # * The InformationController uses the "bank_standard" layout, inherited from BankController.
- # * The TellerController follows convention and uses +app/views/layouts/teller.html.erb+.
- # * The TillController inherits the layout from TellerController and uses +teller.html.erb+ as well.
+ # In these examples, we have three implicit lookup scenarios:
+ # * The BankController uses the "bank" layout.
+ # * The ExchangeController uses the "exchange" layout.
+ # * The CurrencyController inherits the layout from BankController.
+ #
+ # However, when a layout is explicitly set, the explicitly set layout wins:
+ # * The InformationController uses the "information" layout, explicitly set.
+ # * The TellerController also uses the "information" layout, because the parent explicitly set it.
+ # * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration.
# * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
- # * The EmployeeController does not use a layout at all.
+ # * The TillController does not use a layout at all.
#
# == Types of layouts
#
@@ -107,6 +120,7 @@ module AbstractController
# def writers_and_readers
# logged_in? ? "writer_layout" : "reader_layout"
# end
+ # end
#
# Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
# is logged in or not.
@@ -114,7 +128,14 @@ module AbstractController
# If you want to use an inline method, such as a proc, do something like this:
#
# class WeblogController < ActionController::Base
- # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ # layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
+ # end
+ #
+ # If an argument isn't given to the proc, it's evaluated in the context of
+ # the current controller anyway.
+ #
+ # class WeblogController < ActionController::Base
+ # layout proc { logged_in? ? "writer_layout" : "reader_layout" }
# end
#
# Of course, the most common way of specifying a layout is still just as a plain template name:
@@ -123,8 +144,24 @@ module AbstractController
# layout "weblog_standard"
# end
#
- # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
- # Otherwise, it will be looked up relative to the template root.
+ # The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point
+ # <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>.
+ #
+ # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
+ # Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent:
+ #
+ # class ApplicationController < ActionController::Base
+ # layout "application"
+ # end
+ #
+ # class PostsController < ApplicationController
+ # # Will use "application" layout
+ # end
+ #
+ # class CommentsController < ApplicationController
+ # # Will search for "comments" layout and fallback "application" layout
+ # layout nil
+ # end
#
# == Conditional layouts
#
@@ -166,13 +203,15 @@ module AbstractController
include Rendering
included do
- class_attribute :_layout_conditions
- remove_possible_method :_layout_conditions
- delegate :_layout_conditions, :to => :'self.class'
+ class_attribute :_layout, :_layout_conditions,
+ :instance_reader => false, :instance_writer => false
+ self._layout = nil
self._layout_conditions = {}
_write_layout_method
end
+ delegate :_layout_conditions, :to => "self.class"
+
module ClassMethods
def inherited(klass)
super
@@ -188,7 +227,7 @@ module AbstractController
#
# ==== Returns
# * <tt> Boolean</tt> - True if the action has a layout, false otherwise.
- def action_has_layout?
+ def conditional_layout?
return unless super
conditions = _layout_conditions
@@ -207,13 +246,13 @@ module AbstractController
#
# If the specified layout is a:
# String:: the String is the template name
- # Symbol:: call the method specified by the symbol, which will return
- # the template name
+ # Symbol:: call the method specified by the symbol, which will return the template name
# false:: There is no layout
# true:: raise an ArgumentError
+ # nil:: Force default layout behavior with inheritance
#
# ==== Parameters
- # * <tt>String, Symbol, false</tt> - The layout to use.
+ # * <tt>layout</tt> - The layout to use.
#
# ==== Options (conditions)
# * :only - A list of actions to apply this layout to.
@@ -224,7 +263,7 @@ module AbstractController
conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
self._layout_conditions = conditions
- @_layout = layout || false # Converts nil to false
+ self._layout = layout
_write_layout_method
end
@@ -244,43 +283,50 @@ module AbstractController
def _write_layout_method
remove_possible_method(:_layout)
- case defined?(@_layout) ? @_layout : nil
- when String
- self.class_eval %{def _layout; #{@_layout.inspect} end}, __FILE__, __LINE__
- when Symbol
- self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
- def _layout
- #{@_layout}.tap do |layout|
+ prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
+ name_clause = if name
+ <<-RUBY
+ lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super
+ RUBY
+ else
+ <<-RUBY
+ super
+ RUBY
+ end
+
+ layout_definition = case _layout
+ when String
+ _layout.inspect
+ when Symbol
+ <<-RUBY
+ #{_layout}.tap do |layout|
unless layout.is_a?(String) || !layout
- raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \
+ raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \
"should have returned a String, false, or nil"
end
end
- end
- ruby_eval
- when Proc
- define_method :_layout_from_proc, &@_layout
- self.class_eval %{def _layout; _layout_from_proc(self) end}, __FILE__, __LINE__
- when false
- self.class_eval %{def _layout; end}, __FILE__, __LINE__
- when true
- raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
- when nil
- if name
- _prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
-
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def _layout
- if template_exists?("#{_implied_layout_name}", #{_prefixes.inspect})
- "#{_implied_layout_name}"
- else
- super
- end
- end
RUBY
- end
+ when Proc
+ define_method :_layout_from_proc, &_layout
+ _layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)"
+ when false
+ nil
+ when true
+ raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
+ when nil
+ name_clause
end
- self.class_eval { private :_layout }
+
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def _layout
+ if conditional_layout?
+ #{layout_definition}
+ else
+ #{name_clause}
+ end
+ end
+ private :_layout
+ RUBY
end
end
@@ -288,9 +334,8 @@ module AbstractController
super
if _include_layout?(options)
- layout = options.key?(:layout) ? options.delete(:layout) : :default
- value = _layout_for_option(layout)
- options[:layout] = (value =~ /\blayouts/ ? value : "layouts/#{value}") if value
+ layout = options.delete(:layout) { :default }
+ options[:layout] = _layout_for_option(layout)
end
end
@@ -305,56 +350,58 @@ module AbstractController
@_action_has_layout
end
+ def conditional_layout?
+ true
+ end
+
private
# This will be overwritten by _write_layout_method
def _layout; end
- # Determine the layout for a given name and details, taking into account
- # the name type.
+ # Determine the layout for a given name, taking into account the name type.
#
# ==== Parameters
# * <tt>name</tt> - The name of the template
- # * <tt>details</tt> - A list of details to restrict
- # the lookup to. By default, layout lookup is limited to the
- # formats specified for the current request.
def _layout_for_option(name)
case name
- when String then name
- when true then _default_layout(true)
- when :default then _default_layout(false)
+ when String then _normalize_layout(name)
+ when Proc then name
+ when true then Proc.new { _default_layout(true) }
+ when :default then Proc.new { _default_layout(false) }
when false, nil then nil
else
raise ArgumentError,
- "String, true, or false, expected for `layout'; you passed #{name.inspect}"
+ "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}"
end
end
- # Returns the default layout for this controller and a given set of details.
+ def _normalize_layout(value)
+ value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value
+ end
+
+ # Returns the default layout for this controller.
# Optionally raises an exception if the layout could not be found.
#
# ==== 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_layout</tt> - If this is true, raise an ArgumentError
- # with details about the fact that the exception could not be
- # found (defaults to false)
+ # * <tt>require_layout</tt> - If set to true and layout is not found,
+ # an ArgumentError exception is raised (defaults to false)
#
# ==== Returns
# * <tt>template</tt> - The template object for the default layout (or nil)
def _default_layout(require_layout = false)
begin
- layout_name = _layout if action_has_layout?
+ value = _layout if action_has_layout?
rescue NameError => e
raise e, "Could not render layout: #{e.message}"
end
- if require_layout && action_has_layout? && !layout_name
+ if require_layout && action_has_layout? && !value
raise ArgumentError,
"There was no default layout for #{self.class} in #{view_paths.inspect}"
end
- layout_name
+ _normalize_layout(value)
end
def _include_layout?(options)
diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index 0b196119f4..c31ea6c5b5 100644
--- a/actionpack/lib/abstract_controller/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
@@ -1,13 +1,12 @@
-require "active_support/core_ext/logger"
require "active_support/benchmarkable"
module AbstractController
- module Logger
+ module Logger #:nodoc:
extend ActiveSupport::Concern
included do
config_accessor :logger
- extend ActiveSupport::Benchmarkable
+ include ActiveSupport::Benchmarkable
end
end
end
diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
index dec1e9d6d9..6684f46f64 100644
--- a/actionpack/lib/abstract_controller/railties/routes_helpers.rb
+++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb
@@ -5,8 +5,8 @@ module AbstractController
Module.new do
define_method(:inherited) do |klass|
super(klass)
- if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) }
- klass.send(:include, namespace._railtie.routes.url_helpers)
+ if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) }
+ klass.send(:include, namespace.railtie_routes_url_helpers)
else
klass.send(:include, routes.url_helpers)
end
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index 41fdc11196..7d73c6af8d 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -1,6 +1,5 @@
require "abstract_controller/base"
require "action_view"
-require "active_support/core_ext/object/instance_variables"
module AbstractController
class DoubleRenderError < Error
@@ -35,7 +34,7 @@ module AbstractController
include AbstractController::ViewPaths
included do
- config_accessor :protected_instance_variables, :instance_reader => false
+ class_attribute :protected_instance_variables
self.protected_instance_variables = []
end
@@ -59,19 +58,8 @@ module AbstractController
attr_internal_writer :view_context_class
- # Explicitly define protected_instance_variables so it can be
- # inherited and overwritten by other modules if needed.
- def protected_instance_variables
- config.protected_instance_variables
- end
-
def view_context_class
- @_view_context_class || self.class.view_context_class
- end
-
- def initialize(*)
- @_view_context_class = nil
- super
+ @_view_context_class ||= self.class.view_context_class
end
# An instance of a view class. The default view class is ActionView::Base
@@ -117,23 +105,24 @@ module AbstractController
# Find and renders a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
+ lookup_context.rendered_format = nil if options[:formats]
view_renderer.render(view_context, options)
end
- DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w(
- @_action_name @_response_body @_formats @_prefixes @_config
- @_view_context_class @_view_renderer @_lookup_context
- )
+ DEFAULT_PROTECTED_INSTANCE_VARIABLES = [
+ :@_action_name, :@_response_body, :@_formats, :@_prefixes, :@_config,
+ :@_view_context_class, :@_view_renderer, :@_lookup_context
+ ]
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
# :api: public
def view_assigns
hash = {}
- variables = instance_variable_names
+ variables = instance_variables
variables -= protected_instance_variables
variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES
- variables.each { |name| hash[name.to_s[1, name.length]] = instance_variable_get(name) }
+ variables.each { |name| hash[name[1..-1]] = instance_variable_get(name) }
hash
end
diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb
index e4261068d8..4a95e1f276 100644
--- a/actionpack/lib/abstract_controller/url_for.rb
+++ b/actionpack/lib/abstract_controller/url_for.rb
@@ -1,10 +1,10 @@
-# Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class
-# has to provide a +RouteSet+ by implementing the <tt>_routes</tt> methods. Otherwise, an
-# exception will be raised.
-#
-# Note that this module is completely decoupled from HTTP - the only requirement is a valid
-# <tt>_routes</tt> implementation.
module AbstractController
+ # Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class
+ # has to provide a +RouteSet+ by implementing the <tt>_routes</tt> methods. Otherwise, an
+ # exception will be raised.
+ #
+ # Note that this module is completely decoupled from HTTP - the only requirement is a valid
+ # <tt>_routes</tt> implementation.
module UrlFor
extend ActiveSupport::Concern
include ActionDispatch::Routing::UrlFor
diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb
index e8394447a7..c08b3a0e2a 100644
--- a/actionpack/lib/abstract_controller/view_paths.rb
+++ b/actionpack/lib/abstract_controller/view_paths.rb
@@ -10,7 +10,7 @@ module AbstractController
self._view_paths.freeze
end
- delegate :find_template, :template_exists?, :view_paths, :formats, :formats=,
+ delegate :template_exists?, :view_paths, :formats, :formats=,
:locale, :locale=, :to => :lookup_context
module ClassMethods
@@ -89,7 +89,7 @@ module AbstractController
# * <tt>paths</tt> - If a PathSet is provided, use that;
# otherwise, process the parameter into a PathSet.
def view_paths=(paths)
- self._view_paths = ActionView::PathSet.new(Array.wrap(paths))
+ self._view_paths = ActionView::PathSet.new(Array(paths))
end
end
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index f4eaa2fd1b..7c10fcbb8a 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -31,7 +31,6 @@ module ActionController
autoload :RequestForgeryProtection
autoload :Rescue
autoload :Responder
- autoload :SessionManagement
autoload :Streaming
autoload :Testing
autoload :UrlFor
@@ -40,7 +39,6 @@ module ActionController
autoload :Integration, 'action_controller/deprecated/integration_test'
autoload :IntegrationTest, 'action_controller/deprecated/integration_test'
autoload :PerformanceTest, 'action_controller/deprecated/performance_test'
- autoload :UrlWriter, 'action_controller/deprecated'
autoload :Routing, 'action_controller/deprecated'
autoload :TestCase, 'action_controller/test_case'
autoload :TemplateAssertions, 'action_controller/test_case'
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 98bfe72fef..71425cd542 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -116,12 +116,12 @@ module ActionController
#
# Title: <%= @post.title %>
#
- # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
+ # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates
# will use the manual rendering methods:
#
# def search
# @results = Search.find(params[:query])
- # case @results
+ # case @results.count
# when 0 then render :action => "no_results"
# when 1 then render :action => "show"
# when 2..10 then render :action => "show_many"
@@ -133,7 +133,7 @@ module ActionController
# == Redirects
#
# Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the
- # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
+ # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're
# going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
#
# def create
@@ -171,6 +171,16 @@ module ActionController
class Base < Metal
abstract!
+ # Shortcut helper that returns all the ActionController::Base modules except the ones passed in the argument:
+ #
+ # class MetalController
+ # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
+ # include left
+ # end
+ # end
+ #
+ # This gives better control over what you want to exclude and makes it easier
+ # to create a bare controller class, instead of listing the modules required manually.
def self.without_modules(*modules)
modules = modules.map do |m|
m.is_a?(Symbol) ? ActionController.const_get(m) : m
@@ -192,7 +202,6 @@ module ActionController
Renderers::All,
ConditionalGet,
RackDelegation,
- SessionManagement,
Caching,
MimeResponds,
ImplicitRender,
@@ -228,8 +237,11 @@ module ActionController
include mod
end
- # Rails 2.x compatibility
- include ActionController::Compatibility
+ # Define some internal variables that should not be propagated to the view.
+ self.protected_instance_variables = [
+ :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
+ :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout
+ ]
ActiveSupport.run_load_hooks(:action_controller, self)
end
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 14137f2886..112573a38d 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -24,7 +24,6 @@ module ActionController #:nodoc:
#
# config.action_controller.cache_store = :memory_store
# config.action_controller.cache_store = :file_store, "/path/to/cache/directory"
- # config.action_controller.cache_store = :drb_store, "druby://localhost:9192"
# config.action_controller.cache_store = :mem_cache_store, "localhost"
# config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new("localhost:11211")
# config.action_controller.cache_store = MyOwnStore.new("parameter")
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 0031d2701f..80901b8bf3 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -40,14 +40,15 @@ module ActionController #:nodoc:
#
# You can modify the default action cache path by passing a
# <tt>:cache_path</tt> option. This will be passed directly to
- # <tt>ActionCachePath.path_for</tt>. This is handy for actions with
+ # <tt>ActionCachePath.new</tt>. This is handy for actions with
# multiple possible routes that should be cached differently. If a
# block is given, it is called with the current controller instance.
#
# And you can also use <tt>:if</tt> (or <tt>:unless</tt>) to pass a
# proc that specifies when the action should be cached.
#
- # Finally, if you are using memcached, you can also pass <tt>:expires_in</tt>.
+ # As of Rails 3.0, you can also pass <tt>:expires_in</tt> with a time
+ # interval (in seconds) to schedule expiration of the cached item.
#
# The following example depicts some of the points made above:
#
@@ -56,14 +57,14 @@ module ActionController #:nodoc:
#
# caches_page :public
#
- # caches_action :index, :if => proc do
+ # caches_action :index, :if => Proc.new do
# !request.format.json? # cache if is not a JSON request
# end
#
# caches_action :show, :cache_path => { :project => 1 },
# :expires_in => 1.hour
#
- # caches_action :feed, :cache_path => proc do
+ # caches_action :feed, :cache_path => Proc.new do
# if params[:user_id]
# user_list_url(params[:user_id, params[:id])
# else
@@ -102,8 +103,10 @@ module ActionController #:nodoc:
end
def _save_fragment(name, options)
- content = response_body
- content = content.join if content.is_a?(Array)
+ content = ""
+ response_body.each do |parts|
+ content << parts
+ end
if caching_allowed?
write_fragment(name, content, options)
@@ -116,9 +119,8 @@ module ActionController #:nodoc:
def expire_action(options = {})
return unless cache_configured?
- actions = options[:action]
- if actions.is_a?(Array)
- actions.each {|action| expire_action(options.merge(:action => action)) }
+ if options.is_a?(Hash) && options[:action].is_a?(Array)
+ options[:action].each {|action| expire_action(options.merge(:action => action)) }
else
expire_fragment(ActionCachePath.new(self, options, false).path)
end
@@ -131,6 +133,8 @@ module ActionController #:nodoc:
end
def filter(controller)
+ cache_layout = @cache_layout.respond_to?(:call) ? @cache_layout.call(controller) : @cache_layout
+
path_options = if @cache_path.respond_to?(:call)
controller.instance_exec(controller, &@cache_path)
else
@@ -142,13 +146,13 @@ module ActionController #:nodoc:
body = controller.read_fragment(cache_path.path, @store_options)
unless body
- controller.action_has_layout = false unless @cache_layout
+ controller.action_has_layout = false unless cache_layout
yield
controller.action_has_layout = true
body = controller._save_fragment(cache_path.path, @store_options)
end
- body = controller.render_to_string(:text => body, :layout => true) unless @cache_layout
+ body = controller.render_to_string(:text => body, :layout => true) unless cache_layout
controller.response_body = body
controller.content_type = Mime[cache_path.extension || :html]
@@ -168,14 +172,14 @@ module ActionController #:nodoc:
options.reverse_merge!(:format => @extension) if options.is_a?(Hash)
end
- path = controller.url_for(options).split(%r{://}).last
+ path = controller.url_for(options).split('://', 2).last
@path = normalize!(path)
end
private
def normalize!(path)
path << 'index' if path[-1] == ?/
- path << ".#{extension}" if extension and !path.ends_with?(extension)
+ path << ".#{extension}" if extension and !path.split('?', 2).first.ends_with?(".#{extension}")
URI.parser.unescape(path)
end
end
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index 957bb7de6b..dd4eddbe9a 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -16,7 +16,7 @@ module ActionController #:nodoc:
# caches_page :show, :new
# end
#
- # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used
+ # This will generate cache files such as <tt>weblog/show/5.html</tt> and <tt>weblog/new.html</tt>, which match the URLs used
# that would normally trigger dynamic page generation. Page caching works by configuring a web server to first check for the
# existence of files on disk, and to serve them directly when found, without passing the request through to Action Pack.
# This is much faster than handling the full dynamic request in the usual way.
@@ -38,27 +38,30 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
included do
- ##
- # :singleton-method:
# The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
# For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>Rails.root + "/public"</tt>). Changing
# this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
# web server to look in the new location for cached files.
- config_accessor :page_cache_directory
+ class_attribute :page_cache_directory
self.page_cache_directory ||= ''
- ##
- # :singleton-method:
# Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
# order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
# If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
# extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
- config_accessor :page_cache_extension
+ class_attribute :page_cache_extension
self.page_cache_extension ||= '.html'
+
+ # The compression used for gzip. If false (default), the page is not compressed.
+ # If can be a symbol showing the ZLib compression method, for example, :best_compression
+ # or :best_speed or an integer configuring the compression level.
+ class_attribute :page_cache_compression
+ self.page_cache_compression ||= false
end
module ClassMethods
- # Expires the page that was cached with the +path+ as a key. Example:
+ # Expires the page that was cached with the +path+ as a key.
+ #
# expire_page "/lists/show"
def expire_page(path)
return unless perform_caching
@@ -66,35 +69,59 @@ module ActionController #:nodoc:
instrument_page_cache :expire_page, path do
File.delete(path) if File.exist?(path)
+ File.delete(path + '.gz') if File.exist?(path + '.gz')
end
end
- # Manually cache the +content+ in the key determined by +path+. Example:
+ # Manually cache the +content+ in the key determined by +path+.
+ #
# cache_page "I'm the cached content", "/lists/show"
- def cache_page(content, path, extension = nil)
+ def cache_page(content, path, extension = nil, gzip = Zlib::BEST_COMPRESSION)
return unless perform_caching
path = page_cache_path(path, extension)
instrument_page_cache :write_page, path do
FileUtils.makedirs(File.dirname(path))
File.open(path, "wb+") { |f| f.write(content) }
+ if gzip
+ Zlib::GzipWriter.open(path + '.gz', gzip) { |f| f.write(content) }
+ end
end
end
- # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
+ # Caches the +actions+ using the page-caching approach that'll store
+ # the cache in a path within the page_cache_directory that
# matches the triggering url.
#
- # Usage:
+ # You can also pass a :gzip option to override the class configuration one.
#
# # cache the index action
# caches_page :index
#
# # cache the index action except for JSON requests
- # caches_page :index, :if => Proc.new { |c| !c.request.format.json? }
+ # caches_page :index, :if => Proc.new { !request.format.json? }
+ #
+ # # don't gzip images
+ # caches_page :image, :gzip => false
def caches_page(*actions)
return unless perform_caching
options = actions.extract_options!
- after_filter({:only => actions}.merge(options)) { |c| c.cache_page }
+
+ gzip_level = options.fetch(:gzip, page_cache_compression)
+ gzip_level = case gzip_level
+ when Symbol
+ Zlib.const_get(gzip_level.to_s.upcase)
+ when Fixnum
+ gzip_level
+ when false
+ nil
+ else
+ Zlib::BEST_COMPRESSION
+ end
+
+ after_filter({:only => actions}.merge(options)) do |c|
+ c.cache_page(nil, nil, gzip_level)
+ end
end
private
@@ -115,7 +142,8 @@ module ActionController #:nodoc:
end
end
- # Expires the page that was cached with the +options+ as a key. Example:
+ # Expires the page that was cached with the +options+ as a key.
+ #
# expire_page :controller => "lists", :action => "show"
def expire_page(options = {})
return unless self.class.perform_caching
@@ -134,9 +162,10 @@ module ActionController #:nodoc:
end
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used.
- # If no options are provided, the url of the current request being handled is used. Example:
+ # If no options are provided, the url of the current request being handled is used.
+ #
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
- def cache_page(content = nil, options = nil)
+ def cache_page(content = nil, options = nil, gzip = Zlib::BEST_COMPRESSION)
return unless self.class.perform_caching && caching_allowed?
path = case options
@@ -152,7 +181,7 @@ module ActionController #:nodoc:
extension = ".#{type_symbol}"
end
- self.class.cache_page(content || response.body, path, extension)
+ self.class.cache_page(content || response.body, path, extension, gzip)
end
end
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
index 49cf70ec21..cc1fa23935 100644
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ b/actionpack/lib/action_controller/caching/sweeping.rb
@@ -54,6 +54,11 @@ module ActionController #:nodoc:
class Sweeper < ActiveRecord::Observer #:nodoc:
attr_accessor :controller
+ def initialize(*args)
+ super
+ @controller = nil
+ end
+
def before(controller)
self.controller = controller
callback(:before) if controller.perform_caching
@@ -88,7 +93,7 @@ module ActionController #:nodoc:
end
def method_missing(method, *arguments, &block)
- return unless @controller
+ return super unless @controller
@controller.__send__(method, *arguments, &block)
end
end
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
index aa0cfc9395..2405bebb97 100644
--- a/actionpack/lib/action_controller/deprecated.rb
+++ b/actionpack/lib/action_controller/deprecated.rb
@@ -1,3 +1,7 @@
ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
-ActionController::Routing = ActionDispatch::Routing \ No newline at end of file
+ActionController::Routing = ActionDispatch::Routing
+
+ActiveSupport::Deprecation.warn 'ActionController::AbstractRequest and ActionController::Request are deprecated and will be removed, use ActionDispatch::Request instead.'
+ActiveSupport::Deprecation.warn 'ActionController::AbstractResponse and ActionController::Response are deprecated and will be removed, use ActionDispatch::Response instead.'
+ActiveSupport::Deprecation.warn 'ActionController::Routing is deprecated and will be removed, use ActionDispatch::Routing instead.' \ No newline at end of file
diff --git a/actionpack/lib/action_controller/deprecated/integration_test.rb b/actionpack/lib/action_controller/deprecated/integration_test.rb
index 86336b6bc4..54eae48f47 100644
--- a/actionpack/lib/action_controller/deprecated/integration_test.rb
+++ b/actionpack/lib/action_controller/deprecated/integration_test.rb
@@ -1,2 +1,5 @@
ActionController::Integration = ActionDispatch::Integration
ActionController::IntegrationTest = ActionDispatch::IntegrationTest
+
+ActiveSupport::Deprecation.warn 'ActionController::Integration is deprecated and will be removed, use ActionDispatch::Integration instead.'
+ActiveSupport::Deprecation.warn 'ActionController::IntegrationTest is deprecated and will be removed, use ActionDispatch::IntegrationTest instead.'
diff --git a/actionpack/lib/action_controller/deprecated/performance_test.rb b/actionpack/lib/action_controller/deprecated/performance_test.rb
index fcf47d31a7..c7ba5a2fe7 100644
--- a/actionpack/lib/action_controller/deprecated/performance_test.rb
+++ b/actionpack/lib/action_controller/deprecated/performance_test.rb
@@ -1 +1,3 @@
ActionController::PerformanceTest = ActionDispatch::PerformanceTest
+
+ActiveSupport::Deprecation.warn 'ActionController::PerformanceTest is deprecated and will be removed, use ActionDispatch::PerformanceTest instead.'
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 35e29398e6..11aa393bf9 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -20,15 +20,18 @@ module ActionController
status = payload[:status]
if status.nil? && payload[:exception].present?
- status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil
+ status = ActionDispatch::ExceptionWrapper.new({}, payload[:exception]).status_code
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration
message << " (#{additions.join(" | ")})" unless additions.blank?
- message << "\n"
info(message)
end
+ def halted_callback(event)
+ info "Filter chain halted as #{event.payload[:filter]} rendered or redirected"
+ end
+
def send_file(event)
message = "Sent file %s"
message << " (%.1fms)"
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 0133b2ecbc..92433ab462 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -181,9 +181,13 @@ module ActionController
@_status = Rack::Utils.status_code(status)
end
- def response_body=(val)
- body = val.nil? ? nil : (val.respond_to?(:each) ? val : [val])
- super body
+ def response_body=(body)
+ body = [body] unless body.nil? || body.respond_to?(:each)
+ super
+ end
+
+ def performed?
+ !!response_body
end
def dispatch(name, request) #:nodoc:
diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
deleted file mode 100644
index 05dca445a4..0000000000
--- a/actionpack/lib/action_controller/metal/compatibility.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-module ActionController
- module Compatibility
- extend ActiveSupport::Concern
-
- class ::ActionController::ActionControllerError < StandardError #:nodoc:
- end
-
- # Temporary hax
- included do
- ::ActionController::UnknownAction = ::AbstractController::ActionNotFound
- ::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError
-
- # ROUTES TODO: This should be handled by a middleware and route generation
- # should be able to handle SCRIPT_NAME
- self.config.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
-
- class << self
- delegate :default_charset=, :to => "ActionDispatch::Response"
- end
-
- self.protected_instance_variables = %w(
- @_status @_headers @_params @_env @_response @_request
- @_view_runtime @_stream @_url_options @_action_has_layout
- )
-
- def rescue_action(env)
- raise env["action_dispatch.rescue.exception"]
- end
- end
-
- # For old tests
- def initialize_template_class(*) end
- def assign_shortcuts(*) end
-
- def _normalize_options(options)
- options[:text] = nil if options.delete(:nothing) == true
- options[:text] = " " if options.key?(:text) && options[:text].nil?
- super
- end
-
- def render_to_body(options)
- options[:template].sub!(/^\//, '') if options.key?(:template)
- super || " "
- end
-
- def _handle_method_missing
- method_missing(@_action_name.to_sym)
- end
-
- def method_for_action(action_name)
- super || (respond_to?(:method_missing) && "_handle_method_missing")
- end
-
- def performed?
- response_body
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index a5e37172c9..2193dde667 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -23,8 +23,27 @@ module ActionController
# This will render the show template if the request isn't sending a matching etag or
# If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match.
#
- def fresh_when(options)
- options.assert_valid_keys(:etag, :last_modified, :public)
+ # You can also just pass a record where last_modified will be set by calling updated_at and the etag by passing the object itself. Example:
+ #
+ # def show
+ # @article = Article.find(params[:id])
+ # fresh_when(@article)
+ # end
+ #
+ # When passing a record, you can still set whether the public header:
+ #
+ # def show
+ # @article = Article.find(params[:id])
+ # fresh_when(@article, :public => true)
+ # end
+ def fresh_when(record_or_options, additional_options = {})
+ if record_or_options.is_a? Hash
+ options = record_or_options
+ options.assert_valid_keys(:etag, :last_modified, :public)
+ else
+ record = record_or_options
+ options = { :etag => record, :last_modified => record.try(:updated_at) }.merge(additional_options)
+ end
response.etag = options[:etag] if options[:etag]
response.last_modified = options[:last_modified] if options[:last_modified]
@@ -55,26 +74,58 @@ module ActionController
# end
# end
# end
- def stale?(options)
- fresh_when(options)
+ #
+ # You can also just pass a record where last_modified will be set by calling updated_at and the etag by passing the object itself. Example:
+ #
+ # def show
+ # @article = Article.find(params[:id])
+ #
+ # if stale?(@article)
+ # @statistics = @article.really_expensive_call
+ # respond_to do |format|
+ # # all the supported formats
+ # end
+ # end
+ # end
+ #
+ # When passing a record, you can still set whether the public header:
+ #
+ # def show
+ # @article = Article.find(params[:id])
+ #
+ # if stale?(@article, :public => true)
+ # @statistics = @article.really_expensive_call
+ # respond_to do |format|
+ # # all the supported formats
+ # end
+ # end
+ # end
+ def stale?(record_or_options, additional_options = {})
+ fresh_when(record_or_options, additional_options)
!request.fresh?(response)
end
# Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a <tt>private</tt> instruction, so that
# intermediate caches must not cache the response.
#
- # Examples:
# expires_in 20.minutes
# expires_in 3.hours, :public => true
- # expires_in 3.hours, 'max-stale' => 5.hours, :public => true
+ # expires_in 3.hours, :public => true, :must_revalidate => true
#
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
+ #
+ # The method will also ensure a HTTP Date header for client compatibility.
def expires_in(seconds, options = {}) #:doc:
- response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public))
+ response.cache_control.merge!(
+ :max_age => seconds,
+ :public => options.delete(:public),
+ :must_revalidate => options.delete(:must_revalidate)
+ )
options.delete(:private)
response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
+ response.date = Time.now unless response.date?
end
# Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should occur by the browser or
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 0670a58d97..379ff97048 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/file/path'
require 'action_controller/metal/exceptions'
module ActionController #:nodoc:
@@ -9,15 +8,13 @@ module ActionController #:nodoc:
include ActionController::Rendering
- DEFAULT_SEND_FILE_OPTIONS = {
- :type => 'application/octet-stream'.freeze,
- :disposition => 'attachment'.freeze,
- }.freeze
+ DEFAULT_SEND_FILE_TYPE = 'application/octet-stream'.freeze #:nodoc:
+ DEFAULT_SEND_FILE_DISPOSITION = 'attachment'.freeze #:nodoc:
protected
# Sends the file. This uses a server-appropriate method (such as X-Sendfile)
# via the Rack::Sendfile middleware. The header to use is set via
- # config.action_dispatch.x_sendfile_header.
+ # +config.action_dispatch.x_sendfile_header+.
# Your server can also configure this for you by setting the X-Sendfile-Type header.
#
# Be careful to sanitize the path parameter if it is coming from a web
@@ -75,7 +72,27 @@ module ActionController #:nodoc:
self.status = options[:status] || 200
self.content_type = options[:content_type] if options.key?(:content_type)
- self.response_body = File.open(path, "rb")
+ self.response_body = FileBody.new(path)
+ end
+
+ # Avoid having to pass an open file handle as the response body.
+ # Rack::Sendfile will usually intercepts the response and just uses
+ # the path directly, so no reason to open the file.
+ class FileBody #:nodoc:
+ attr_reader :to_path
+
+ def initialize(path)
+ @to_path = path
+ end
+
+ # Stream the file's contents if Rack::Sendfile isn't present.
+ def each
+ File.open(to_path, 'rb') do |file|
+ while chunk = file.read(16384)
+ yield chunk
+ end
+ end
+ end
end
# Sends the given binary data to the browser. This method is similar to
@@ -108,23 +125,16 @@ module ActionController #:nodoc:
#
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
- send_file_headers! options.dup
+ send_file_headers! options
render options.slice(:status, :content_type).merge(:text => data)
end
private
def send_file_headers!(options)
type_provided = options.has_key?(:type)
-
- options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
- [:type, :disposition].each do |arg|
- raise ArgumentError, ":#{arg} option required" if options[arg].nil?
- end
- disposition = options[:disposition]
- disposition += %(; filename="#{options[:filename]}") if options[:filename]
-
- content_type = options[:type]
+ content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE)
+ raise ArgumentError, ":type option required" if content_type.nil?
if content_type.is_a?(Symbol)
extension = Mime[content_type]
@@ -133,15 +143,18 @@ module ActionController #:nodoc:
else
if !type_provided && options[:filename]
# If type wasn't provided, try guessing from file extension.
- content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.tr('.','')) || content_type
+ content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete('.')) || content_type
end
self.content_type = content_type
end
- headers.merge!(
- 'Content-Disposition' => disposition,
- 'Content-Transfer-Encoding' => 'binary'
- )
+ disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
+ unless disposition.nil?
+ disposition += %(; filename="#{options[:filename]}") if options[:filename]
+ headers['Content-Disposition'] = disposition
+ end
+
+ headers['Content-Transfer-Encoding'] = 'binary'
response.sending_file = true
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 07024d0a9a..90648c37ad 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -14,8 +14,6 @@ module ActionController
end
class MethodNotAllowed < ActionControllerError #:nodoc:
- attr_reader :allowed_methods
-
def initialize(*allowed_methods)
super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.")
end
@@ -30,9 +28,6 @@ module ActionController
class MissingFile < ActionControllerError #:nodoc:
end
- class RenderError < ActionControllerError #:nodoc:
- end
-
class SessionOverflowError < ActionControllerError #:nodoc:
DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
@@ -43,4 +38,7 @@ module ActionController
class UnknownHttpMethod < ActionControllerError #:nodoc:
end
-end \ No newline at end of file
+
+ class UnknownFormat < ActionControllerError #:nodoc:
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index 0fd42f9d8a..ac12cbb625 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -18,17 +18,33 @@ module ActionController
# Force the request to this particular controller or specified actions to be
# under HTTPS protocol.
#
- # Note that this method will not be effective on development environment.
+ # If you need to disable this for any reason (e.g. development) then you can use
+ # an +:if+ or +:unless+ condition.
+ #
+ # class AccountsController < ApplicationController
+ # force_ssl :if => :ssl_configured?
+ #
+ # def ssl_configured?
+ # !Rails.env.development?
+ # end
+ # end
#
# ==== Options
+ # * <tt>host</tt> - Redirect to a different host name
# * <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>if</tt> - A symbol naming an instance method or a proc; the callback
+ # will be called only when it returns a true value.
+ # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
+ # will be called only when it returns a false value.
def force_ssl(options = {})
host = options.delete(:host)
before_filter(options) do
- if !request.ssl? && !Rails.env.development?
+ unless request.ssl?
redirect_options = {:protocol => 'https://', :status => :moved_permanently}
redirect_options.merge!(:host => host) if host
+ redirect_options.merge!(:params => request.query_parameters)
+ flash.keep if respond_to?(:flash)
redirect_to redirect_options
end
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index a618533d09..2fcd933d32 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -20,6 +20,7 @@ module ActionController
options, status = status, nil if status.is_a?(Hash)
status ||= options.delete(:status) || :ok
location = options.delete(:location)
+ content_type = options.delete(:content_type)
options.each do |key, value|
headers[key.to_s.dasherize.split('-').each { |v| v[0] = v[0].chr.upcase }.join('-')] = value.to_s
@@ -27,8 +28,28 @@ module ActionController
self.status = status
self.location = url_for(location) if location
- self.content_type = Mime[formats.first] if formats
+
+ if include_content_headers?(self.status)
+ self.content_type = content_type || (Mime[formats.first] if formats)
+ else
+ headers.delete('Content-Type')
+ headers.delete('Content-Length')
+ end
+
self.response_body = " "
end
+
+ private
+ # :nodoc:
+ def include_content_headers?(status)
+ case status
+ when 100..199
+ false
+ when 204, 205, 304
+ false
+ else
+ true
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index bd515bba82..86d061e3b7 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
module ActionController
@@ -17,7 +16,6 @@ module ActionController
# Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
# controller which inherits from it.
#
- # ==== Examples
# The +to_s+ method from the \Time class can be wrapped in a helper method to display a custom message if
# a \Time object is blank:
#
@@ -53,10 +51,11 @@ module ActionController
module Helpers
extend ActiveSupport::Concern
+ class << self; attr_accessor :helpers_path; end
include AbstractController::Helpers
included do
- config_accessor :helpers_path, :include_all_helpers
+ class_attribute :helpers_path, :include_all_helpers
self.helpers_path ||= []
self.include_all_helpers = true
end
@@ -94,7 +93,7 @@ module ActionController
def all_helpers_from_path(path)
helpers = []
- Array.wrap(path).each do |_path|
+ Array(path).each do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 264806cd36..57bb0e2a32 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -1,9 +1,10 @@
-require 'active_support/base64'
+require 'base64'
require 'active_support/core_ext/object/blank'
module ActionController
+ # Makes it dead easy to do HTTP Basic, Digest and Token authentication.
module HttpAuthentication
- # Makes it dead easy to do HTTP \Basic and \Digest authentication.
+ # Makes it dead easy to do HTTP \Basic authentication.
#
# === Simple \Basic example
#
@@ -60,47 +61,6 @@ module ActionController
#
# assert_equal 200, status
# end
- #
- # === Simple \Digest example
- #
- # require 'digest/md5'
- # class PostsController < ApplicationController
- # REALM = "SuperSecret"
- # USERS = {"dhh" => "secret", #plain text password
- # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password
- #
- # before_filter :authenticate, :except => [:index]
- #
- # def index
- # render :text => "Everyone can see me!"
- # end
- #
- # def edit
- # render :text => "I'm only accessible if you know the password"
- # end
- #
- # private
- # def authenticate
- # authenticate_or_request_with_http_digest(REALM) do |username|
- # USERS[username]
- # end
- # end
- # end
- #
- # === Notes
- #
- # The +authenticate_or_request_with_http_digest+ block must return the user's password
- # or the ha1 digest hash so the framework can appropriately hash to check the user's
- # credentials. Returning +nil+ will cause authentication to fail.
- #
- # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
- # the password file or database is compromised, the attacker would be able to use the ha1 hash to
- # authenticate as the user at this +realm+, but would not have the user's password to try using at
- # other sites.
- #
- # In rare instances, web servers or front proxies strip authorization headers before
- # they reach your application. You can debug this situation by logging all environment
- # variables, and check for HTTP_AUTHORIZATION, amongst others.
module Basic
extend self
@@ -141,11 +101,11 @@ module ActionController
end
def decode_credentials(request)
- ActiveSupport::Base64.decode64(request.authorization.split(' ', 2).last || '')
+ ::Base64.decode64(request.authorization.split(' ', 2).last || '')
end
def encode_credentials(user_name, password)
- "Basic #{ActiveSupport::Base64.encode64s("#{user_name}:#{password}")}"
+ "Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}"
end
def authentication_request(controller, realm)
@@ -155,6 +115,48 @@ module ActionController
end
end
+ # Makes it dead easy to do HTTP \Digest authentication.
+ #
+ # === Simple \Digest example
+ #
+ # require 'digest/md5'
+ # class PostsController < ApplicationController
+ # REALM = "SuperSecret"
+ # USERS = {"dhh" => "secret", #plain text password
+ # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
+ #
+ # before_filter :authenticate, :except => [:index]
+ #
+ # def index
+ # render :text => "Everyone can see me!"
+ # end
+ #
+ # def edit
+ # render :text => "I'm only accessible if you know the password"
+ # end
+ #
+ # private
+ # def authenticate
+ # authenticate_or_request_with_http_digest(REALM) do |username|
+ # USERS[username]
+ # end
+ # end
+ # end
+ #
+ # === Notes
+ #
+ # The +authenticate_or_request_with_http_digest+ block must return the user's password
+ # or the ha1 digest hash so the framework can appropriately hash to check the user's
+ # credentials. Returning +nil+ will cause authentication to fail.
+ #
+ # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If
+ # the password file or database is compromised, the attacker would be able to use the ha1 hash to
+ # authenticate as the user at this +realm+, but would not have the user's password to try using at
+ # other sites.
+ #
+ # In rare instances, web servers or front proxies strip authorization headers before
+ # they reach your application. You can debug this situation by logging all environment
+ # variables, and check for HTTP_AUTHORIZATION, amongst others.
module Digest
extend self
@@ -192,12 +194,15 @@ module ActionController
return false unless password
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
- uri = credentials[:uri][0,1] == '/' ? request.fullpath : request.url
+ uri = credentials[:uri][0,1] == '/' ? request.original_fullpath : request.original_url
- [true, false].any? do |password_is_ha1|
- expected = expected_response(method, uri, credentials, password, password_is_ha1)
- expected == credentials[:response]
- end
+ [true, false].any? do |trailing_question_mark|
+ [true, false].any? do |password_is_ha1|
+ _uri = trailing_question_mark ? uri + "?" : uri
+ expected = expected_response(method, _uri, credentials, password, password_is_ha1)
+ expected == credentials[:response]
+ end
+ end
end
end
@@ -226,7 +231,7 @@ module ActionController
def decode_credentials(header)
Hash[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair|
key, value = pair.split('=', 2)
- [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').gsub(/'/, '')]
+ [key.strip.to_sym, value.to_s.gsub(/^"|"$/,'').delete('\'')]
end]
end
@@ -260,7 +265,7 @@ module ActionController
# The quality of the implementation depends on a good choice.
# A nonce might, for example, be constructed as the base 64 encoding of
#
- # => time-stamp H(time-stamp ":" ETag ":" private-key)
+ # time-stamp H(time-stamp ":" ETag ":" private-key)
#
# where time-stamp is a server-generated time or other non-repeating value,
# ETag is the value of the HTTP ETag header associated with the requested entity,
@@ -276,7 +281,7 @@ module ActionController
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
- # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
+ # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
# The nonce is opaque to the client. Composed of Time, and hash of Time with secret
@@ -286,16 +291,16 @@ module ActionController
t = time.to_i
hashed = [t, secret_key]
digest = ::Digest::MD5.hexdigest(hashed.join(":"))
- ActiveSupport::Base64.encode64("#{t}:#{digest}").gsub("\n", '')
+ ::Base64.strict_encode64("#{t}:#{digest}")
end
# Might want a shorter timeout depending on whether the request
- # is a PUT or POST, and if client is browser or web service.
+ # is a PATCH, PUT, or POST, and if client is browser or web service.
# Can be much shorter if the Stale directive is implemented. This would
# allow a user to use new nonce without prompting user again for their
# username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
- t = ActiveSupport::Base64.decode64(value).split(":").first.to_i
+ t = ::Base64.decode64(value).split(":").first.to_i
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index e8e465d3ba..ae04b53825 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -2,7 +2,7 @@ module ActionController
module ImplicitRender
def send_action(method, *args)
ret = super
- default_render unless response_body
+ default_render unless performed?
ret
end
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index 777a0ab343..640ebf5f00 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -64,7 +64,12 @@ module ActionController
end
end
- protected
+ private
+
+ # A hook invoked everytime a before callback is halted.
+ def halted_callback_hook(filter)
+ ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter)
+ end
# A hook which allows you to clean up any time taken into account in
# views wrongly, like database querying time.
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 00bd1706e7..0b800c3c62 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -6,8 +6,6 @@ module ActionController #:nodoc:
module MimeResponds
extend ActiveSupport::Concern
- include ActionController::ImplicitRender
-
included do
class_attribute :responder, :mimes_for_respond_to
self.responder = ActionController::Responder
@@ -18,8 +16,6 @@ module ActionController #:nodoc:
# Defines mime types that are rendered by default when invoking
# <tt>respond_with</tt>.
#
- # Examples:
- #
# respond_to :html, :xml, :json
#
# Specifies that all actions in the controller respond to requests
@@ -58,7 +54,7 @@ module ActionController #:nodoc:
# Clear all mime types in <tt>respond_to</tt>.
#
def clear_respond_to
- self.mimes_for_respond_to = ActiveSupport::OrderedHash.new.freeze
+ self.mimes_for_respond_to = Hash.new.freeze
end
end
@@ -76,7 +72,7 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
- # format.xml { render :xml => @people.to_xml }
+ # format.xml { render :xml => @people }
# end
# end
#
@@ -182,34 +178,120 @@ module ActionController #:nodoc:
#
# def index
# @people = Person.all
- # respond_with(@person)
+ # respond_with(@people)
# end
# end
#
# Be sure to check respond_with and respond_to documentation for more examples.
- #
def respond_to(*mimes, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
- if response = retrieve_response_from_mimes(mimes, &block)
- response.call(nil)
+ if collector = retrieve_collector_from_mimes(mimes, &block)
+ response = collector.response
+ response ? response.call : render({})
end
end
- # respond_with wraps a resource around a responder for default representation.
- # First it invokes respond_to, if a response cannot be found (ie. no block
- # for the request was given and template was not available), it instantiates
- # an ActionController::Responder with the controller and resource.
+ # For a given controller action, respond_with generates an appropriate
+ # response based on the mime-type requested by the client.
#
- # ==== Example
+ # If the method is called with just a resource, as in this example -
#
- # def index
- # @users = User.all
- # respond_with(@users)
+ # class PeopleController < ApplicationController
+ # respond_to :html, :xml, :json
+ #
+ # def index
+ # @people = Person.all
+ # respond_with @people
+ # end
+ # end
+ #
+ # then the mime-type of the response is typically selected based on the
+ # request's Accept header and the set of available formats declared
+ # by previous calls to the controller's class method +respond_to+. Alternatively
+ # the mime-type can be selected by explicitly setting <tt>request.format</tt> in
+ # the controller.
+ #
+ # If an acceptable format is not identified, the application returns a
+ # '406 - not acceptable' status. Otherwise, the default response is to render
+ # a template named after the current action and the selected format,
+ # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior
+ # depends on the selected format:
+ #
+ # * for an html response - if the request method is +get+, an exception
+ # is raised but for other requests such as +post+ the response
+ # depends on whether the resource has any validation errors (i.e.
+ # assuming that an attempt has been made to save the resource,
+ # e.g. by a +create+ action) -
+ # 1. If there are no errors, i.e. the resource
+ # was saved successfully, the response +redirect+'s to the resource
+ # i.e. its +show+ action.
+ # 2. If there are validation errors, the response
+ # renders a default action, which is <tt>:new</tt> for a
+ # +post+ request or <tt>:edit</tt> for +put+.
+ # Thus an example like this -
+ #
+ # respond_to :html, :xml
+ #
+ # def create
+ # @user = User.new(params[:user])
+ # flash[:notice] = 'User was successfully created.' if @user.save
+ # respond_with(@user)
+ # end
+ #
+ # is equivalent, in the absence of <tt>create.html.erb</tt>, to -
+ #
+ # def create
+ # @user = User.new(params[:user])
+ # respond_to do |format|
+ # if @user.save
+ # flash[:notice] = 'User was successfully created.'
+ # format.html { redirect_to(@user) }
+ # format.xml { render :xml => @user }
+ # else
+ # format.html { render :action => "new" }
+ # format.xml { render :xml => @user }
+ # end
+ # end
+ # end
+ #
+ # * for a javascript request - if the template isn't found, an exception is
+ # raised.
+ # * for other requests - i.e. data formats such as xml, json, csv etc, if
+ # the resource passed to +respond_with+ responds to <code>to_<format></code>,
+ # the method attempts to render the resource in the requested format
+ # directly, e.g. for an xml request, the response is equivalent to calling
+ # <code>render :xml => resource</code>.
+ #
+ # === Nested resources
+ #
+ # As outlined above, the +resources+ argument passed to +respond_with+
+ # can play two roles. It can be used to generate the redirect url
+ # for successful html requests (e.g. for +create+ actions when
+ # no template exists), while for formats other than html and javascript
+ # it is the object that gets rendered, by being converted directly to the
+ # required format (again assuming no template exists).
+ #
+ # For redirecting successful html requests, +respond_with+ also supports
+ # the use of nested resources, which are supplied in the same way as
+ # in <code>form_for</code> and <code>polymorphic_url</code>. For example -
+ #
+ # def create
+ # @project = Project.find(params[:project_id])
+ # @task = @project.comments.build(params[:task])
+ # flash[:notice] = 'Task was successfully created.' if @task.save
+ # respond_with(@project, @task)
# end
#
- # It also accepts a block to be given. It's used to overwrite a default
- # response:
+ # This would cause +respond_with+ to redirect to <code>project_task_url</code>
+ # instead of <code>task_url</code>. For request formats other than html or
+ # javascript, if multiple resources are passed in this way, it is the last
+ # one specified that is rendered.
+ #
+ # === Customizing response behavior
+ #
+ # Like +respond_to+, +respond_with+ may also be called with a block that
+ # can be used to overwrite any of the default responses, e.g. -
#
# def create
# @user = User.new(params[:user])
@@ -220,21 +302,31 @@ module ActionController #:nodoc:
# end
# end
#
- # All options given to respond_with are sent to the underlying responder,
- # except for the option :responder itself. Since the responder interface
- # 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>.
- #
+ # The argument passed to the block is an ActionController::MimeResponds::Collector
+ # object which stores the responses for the formats defined within the
+ # block. Note that formats with responses defined explicitly in this way
+ # do not have to first be declared using the class method +respond_to+.
+ #
+ # Also, a hash passed to +respond_with+ immediately after the specified
+ # resource(s) is interpreted as a set of options relevant to all
+ # formats. Any option accepted by +render+ can be used, e.g.
+ # respond_with @people, :status => 200
+ # However, note that these options are ignored after an unsuccessful attempt
+ # to save a resource, e.g. when automatically rendering <tt>:new</tt>
+ # after a post request.
+ #
+ # Two additional options are relevant specifically to +respond_with+ -
+ # 1. <tt>:location</tt> - overwrites the default redirect location used after
+ # a successful html +post+ request.
+ # 2. <tt>:action</tt> - overwrites the default render action used after an
+ # unsuccessful html +post+ request.
def respond_with(*resources, &block)
raise "In order to use respond_with, first you need to declare the formats your " <<
"controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
- if response = retrieve_response_from_mimes(&block)
+ if collector = retrieve_collector_from_mimes(&block)
options = resources.size == 1 ? {} : resources.extract_options!
- options.merge!(:default_response => response)
+ options[:default_response] = collector.response
(options.delete(:responder) || self.class.responder).call(self, resources, options)
end
end
@@ -243,7 +335,6 @@ module ActionController #:nodoc:
# Collect mimes declared in the class method respond_to valid for the
# current action.
- #
def collect_mimes_from_class_level #:nodoc:
action = action_name.to_s
@@ -260,30 +351,56 @@ module ActionController #:nodoc:
end
end
- # Collects mimes and return the response for the negotiated format. Returns
- # nil if :not_acceptable was sent to the client.
+ # Returns a Collector object containing the appropriate mime-type response
+ # for the current request, based on the available responses defined by a block.
+ # In typical usage this is the block passed to +respond_with+ or +respond_to+.
#
- def retrieve_response_from_mimes(mimes=nil, &block) #:nodoc:
+ # Sends :not_acceptable to the client and returns nil if no suitable format
+ # is available.
+ def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
- collector = Collector.new(mimes) { |options| default_render(options || {}) }
+ collector = Collector.new(mimes)
block.call(collector) if block_given?
+ format = collector.negotiate_format(request)
- if format = request.negotiate_mime(collector.order)
+ if format
self.content_type ||= format.to_s
- lookup_context.freeze_formats([format.to_sym])
- collector.response_for(format)
+ lookup_context.formats = [format.to_sym]
+ lookup_context.rendered_format = lookup_context.formats.first
+ collector
else
- head :not_acceptable
- nil
+ raise ActionController::UnknownFormat
end
end
- class Collector #:nodoc:
+ # A container for responses available from the current controller for
+ # requests for different mime-types sent to a particular action.
+ #
+ # The public controller methods +respond_with+ and +respond_to+ may be called
+ # with a block that is used to define responses to different mime-types, e.g.
+ # for +respond_to+ :
+ #
+ # respond_to do |format|
+ # format.html
+ # format.xml { render :xml => @people }
+ # end
+ #
+ # In this usage, the argument passed to the block (+format+ above) is an
+ # instance of the ActionController::MimeResponds::Collector class. This
+ # object serves as a container in which available responses can be stored by
+ # calling any of the dynamically generated, mime-type-specific methods such
+ # as +html+, +xml+ etc on the Collector. Each response is represented by a
+ # corresponding block if present.
+ #
+ # A subsequent call to #negotiate_format(request) will enable the Collector
+ # to determine which specific mime-type it should respond with for the current
+ # request, with this response then being accessible by calling #response.
+ class Collector
include AbstractController::Collector
- attr_accessor :order
+ attr_accessor :order, :format
- def initialize(mimes, &block)
- @order, @responses, @default_response = [], {}, block
+ def initialize(mimes)
+ @order, @responses = [], {}
mimes.each { |mime| send(mime) }
end
@@ -302,8 +419,12 @@ module ActionController #:nodoc:
@responses[mime_type] ||= block
end
- def response_for(mime)
- @responses[mime] || @responses[Mime::ALL] || @default_response
+ def response
+ @responses[format] || @responses[Mime::ALL]
+ end
+
+ def negotiate_format(request)
+ @format = request.negotiate_mime(order)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index e0d8e1c992..1f52c164de 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -1,7 +1,6 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/module/anonymous'
require 'action_dispatch/http/mime_types'
@@ -43,8 +42,13 @@ module ActionController
# wrap_parameters :person, :include => [:username, :password]
# end
#
+ # On ActiveRecord models with no +:include+ or +:exclude+ option set,
+ # if attr_accessible is set on that model, it will only wrap the accessible
+ # parameters, else it will only wrap the parameters returned by the class
+ # method attribute_names.
+ #
# If you're going to pass the parameters to an +ActiveModel+ object (such as
- # +User.new(params[:user])+), you might consider passing the model class to
+ # <tt>User.new(params[:user])</tt>), you might consider passing the model class to
# the method instead. The +ParamsWrapper+ will actually try to determine the
# list of attribute names from the model and only wrap those attributes:
#
@@ -62,7 +66,7 @@ module ActionController
# class Admin::UsersController < ApplicationController
# end
#
- # will try to check if +Admin::User+ or +User+ model exists, and use it to
+ # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
# determine the wrapper key respectively. If both models don't exist,
# it will then fallback to use +user+ as the key.
module ParamsWrapper
@@ -141,7 +145,7 @@ module ActionController
# try to find Foo::Bar::User, Foo::User and finally User.
def _default_wrap_model #:nodoc:
return nil if self.anonymous?
- model_name = self.name.sub(/Controller$/, '').singularize
+ model_name = self.name.sub(/Controller$/, '').classify
begin
if model_klass = model_name.safe_constantize
@@ -162,7 +166,10 @@ module ActionController
unless options[:include] || options[:exclude]
model ||= _default_wrap_model
- if model.respond_to?(:attribute_names) && model.attribute_names.present?
+ role = options.fetch(:as, :default)
+ if model.respond_to?(:accessible_attributes) && model.accessible_attributes(role).present?
+ options[:include] = model.accessible_attributes(role).to_a
+ elsif model.respond_to?(:attribute_names) && model.attribute_names.present?
options[:include] = model.attribute_names
end
end
@@ -173,9 +180,9 @@ module ActionController
controller_name.singularize
end
- options[:include] = Array.wrap(options[:include]).collect(&:to_s) if options[:include]
- options[:exclude] = Array.wrap(options[:exclude]).collect(&:to_s) if options[:exclude]
- options[:format] = Array.wrap(options[:format])
+ options[:include] = Array(options[:include]).collect(&:to_s) if options[:include]
+ options[:exclude] = Array(options[:exclude]).collect(&:to_s) if options[:exclude]
+ options[:format] = Array(options[:format])
self._wrapper_options = options
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index f2dfb3833b..ee0e69d87c 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -18,13 +18,12 @@ module ActionController
#
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
# * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record.
- # * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) - Is passed straight through as the target for redirection.
+ # * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) or a protocol relative reference (like <tt>//</tt>) - Is passed straight through as the target for redirection.
# * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string.
# * <tt>Proc</tt> - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+.
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
#
- # Examples:
# redirect_to :action => "show", :id => 5
# redirect_to post
# redirect_to "http://www.rubyonrails.org"
@@ -35,7 +34,6 @@ module ActionController
#
# The redirection happens as a "302 Moved" header unless otherwise specified.
#
- # Examples:
# redirect_to post_url(@post), :status => :found
# redirect_to :action=>'atom', :status => :moved_permanently
# redirect_to post_url(@post), :status => 301
@@ -45,20 +43,29 @@ module ActionController
# integer, or a symbol representing the downcased, underscored and symbolized description.
# Note that the status code must be a 3xx HTTP code, or redirection will not occur.
#
+ # If you are using XHR requests other than GET or POST and redirecting after the
+ # request then some browsers will follow the redirect using the original request
+ # method. This may lead to undesirable behavior such as a double DELETE. To work
+ # around this you can return a <tt>303 See Other</tt> status code which will be
+ # followed using a GET request.
+ #
+ # redirect_to posts_url, :status => :see_other
+ # redirect_to :action => 'index', :status => 303
+ #
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
#
- # Examples:
# redirect_to post_url(@post), :alert => "Watch it, mister!"
# redirect_to post_url(@post), :status=> :found, :notice => "Pay attention to the road"
# redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id }
# redirect_to { :action=>'atom' }, :alert => "Something serious happened"
#
- # When using <tt>redirect_to :back</tt>, if there is no referrer, RedirectBackError will be raised. You may specify some fallback
- # behavior for this case by rescuing RedirectBackError.
+ # When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
+ # behavior for this case by rescuing ActionController::RedirectBackError.
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
+ logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
@@ -81,7 +88,8 @@ module ActionController
# The scheme name consist of a letter followed by any combination of
# letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
# characters; and is terminated by a colon (":").
- when %r{^\w[\w+.-]*:.*}
+ # The protocol relative scheme starts with a double slash "//"
+ when %r{^(\w[\w+.-]*:|//).*}
options
when String
request.protocol + request.host_with_port + options
@@ -92,7 +100,7 @@ module ActionController
_compute_redirect_to_location options.call
else
url_for(options)
- end.gsub(/[\r\n]/, '')
+ end.delete("\0\r\n")
end
end
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 0ad9dbeda9..1927c8bdc7 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/blank'
+require 'set'
module ActionController
# See <tt>Renderers.add</tt>
@@ -12,16 +13,13 @@ module ActionController
included do
class_attribute :_renderers
- self._renderers = {}.freeze
+ self._renderers = Set.new.freeze
end
module ClassMethods
def use_renderers(*args)
- new = _renderers.dup
- args.each do |key|
- new[key] = RENDERERS[key]
- end
- self._renderers = new.freeze
+ renderers = _renderers + args
+ self._renderers = renderers.freeze
end
alias use_renderer use_renderers
end
@@ -31,10 +29,10 @@ module ActionController
end
def _handle_render_options(options)
- _renderers.each do |name, value|
- if options.key?(name.to_sym)
+ _renderers.each do |name|
+ if options.key?(name)
_process_options(options)
- return send("_render_option_#{name}", options.delete(name.to_sym), options)
+ return send("_render_option_#{name}", options.delete(name), options)
end
end
nil
@@ -42,7 +40,7 @@ module ActionController
# Hash of available renderers, mapping a renderer name to its proc.
# Default keys are :json, :js, :xml.
- RENDERERS = {}
+ RENDERERS = Set.new
# Adds a new renderer to call within controller actions.
# A renderer is invoked by passing its name as an option to
@@ -51,7 +49,6 @@ module ActionController
# is the value paired with its key and the second is the remaining
# hash of options passed to +render+.
#
- # === Example
# Create a csv renderer:
#
# ActionController::Renderers.add :csv do |obj, options|
@@ -79,7 +76,7 @@ module ActionController
# <tt>ActionController::MimeResponds#respond_with</tt>
def self.add(key, &block)
define_method("_render_option_#{key}", &block)
- RENDERERS[key] = block
+ RENDERERS << key.to_sym
end
module All
@@ -93,9 +90,14 @@ module ActionController
add :json do |json, options|
json = json.to_json(options) unless json.kind_of?(String)
- json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
- self.content_type ||= Mime::JSON
- json
+
+ if options[:callback].present?
+ self.content_type ||= Mime::JS
+ "#{options[:callback]}(#{json})"
+ else
+ self.content_type ||= Mime::JSON
+ json
+ end
end
add :js do |js, options|
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 70fd79bb8b..c5e7d4e357 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -14,7 +14,7 @@ module ActionController
def render(*args) #:nodoc:
raise ::AbstractController::DoubleRenderError if response_body
super
- self.content_type ||= Mime[formats.first].to_s
+ self.content_type ||= Mime[lookup_context.rendered_format].to_s
response_body
end
@@ -29,6 +29,10 @@ module ActionController
self.response_body = nil
end
+ def render_to_body(*)
+ super || " "
+ end
+
private
# Normalize arguments by catching blocks and setting them on :update.
@@ -44,6 +48,10 @@ module ActionController
options[:text] = options[:text].to_text
end
+ if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
+ options[:text] = " "
+ end
+
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index bc22e39efb..95b0e99ed5 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -17,7 +17,6 @@ module ActionController #:nodoc:
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
# which checks the token and resets the session if it doesn't match what was expected.
# A call to this method is generated for new \Rails applications by default.
- # You can customize the error message by editing public/422.html.
#
# The token parameter is named <tt>authenticity_token</tt> by default. The name and
# value of this token must be added to every layout that renders forms by including
@@ -37,6 +36,10 @@ module ActionController #:nodoc:
config_accessor :request_forgery_protection_token
self.request_forgery_protection_token ||= :authenticity_token
+ # Controls how unverified request will be handled
+ config_accessor :request_forgery_protection_method
+ self.request_forgery_protection_method ||= :reset_session
+
# Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
config_accessor :allow_forgery_protection
self.allow_forgery_protection = true if allow_forgery_protection.nil?
@@ -48,8 +51,6 @@ module ActionController #:nodoc:
module ClassMethods
# Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
#
- # Example:
- #
# class FooController < ApplicationController
# protect_from_forgery :except => :index
#
@@ -64,8 +65,10 @@ module ActionController #:nodoc:
# Valid Options:
#
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
+ # * <tt>:with</tt> - Set the method to handle unverified request. Valid values: <tt>:exception</tt> and <tt>:reset_session</tt> (default).
def protect_from_forgery(options = {})
self.request_forgery_protection_token ||= :authenticity_token
+ self.request_forgery_protection_method = options.delete(:with) if options.key?(:with)
prepend_before_filter :verify_authenticity_token, options
end
end
@@ -74,15 +77,25 @@ module ActionController #:nodoc:
# The actual before_filter that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
unless verified_request?
- logger.warn "WARNING: Can't verify CSRF token authenticity" if logger
+ logger.warn "Can't verify CSRF token authenticity" if logger
handle_unverified_request
end
end
# This is the method that defines the application behavior when a request is found to be unverified.
- # By default, \Rails resets the session when it finds an unverified request.
+ # By default, \Rails uses <tt>request_forgery_protection_method</tt> when it finds an unverified request:
+ #
+ # * <tt>:reset_session</tt> - Resets the session.
+ # * <tt>:exception</tt>: - Raises ActionController::InvalidAuthenticityToken exception.
def handle_unverified_request
- reset_session
+ case request_forgery_protection_method
+ when :exception
+ raise ActionController::InvalidAuthenticityToken
+ when :reset_session
+ reset_session
+ else
+ raise ArgumentError, 'Invalid request forgery protection method, use :exception or :reset_session'
+ end
end
# Returns true or false if a request is verified. Checks:
diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb
index eb037aa1b0..68cc9a9c9b 100644
--- a/actionpack/lib/action_controller/metal/rescue.rb
+++ b/actionpack/lib/action_controller/metal/rescue.rb
@@ -1,4 +1,7 @@
module ActionController #:nodoc:
+ # This module is responsible to provide `rescue_from` helpers
+ # to controllers and configure when detailed exceptions must be
+ # shown.
module Rescue
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
@@ -12,10 +15,20 @@ module ActionController #:nodoc:
super(exception)
end
+ # Override this method if you want to customize when detailed
+ # exceptions must be shown. This method is only called when
+ # consider_all_requests_local is false. By default, it returns
+ # false, but someone may set it to `request.local?` so local
+ # requests in production still shows the detailed exception pages.
+ def show_detailed_exceptions?
+ false
+ end
+
private
def process_action(*args)
super
rescue Exception => exception
+ request.env['action_dispatch.show_detailed_exceptions'] ||= show_detailed_exceptions?
rescue_with_handler(exception) || raise(exception)
end
end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index c7827309dd..83407846dc 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -53,7 +53,7 @@ module ActionController #:nodoc:
# end
# end
#
- # The same happens for PUT and DELETE requests.
+ # The same happens for PATCH/PUT and DELETE requests.
#
# === Nested resources
#
@@ -63,7 +63,7 @@ module ActionController #:nodoc:
#
# def create
# @project = Project.find(params[:project_id])
- # @task = @project.comments.build(params[:task])
+ # @task = @project.tasks.build(params[:task])
# flash[:notice] = 'Task was successfully created.' if @task.save
# respond_with(@project, @task)
# end
@@ -84,8 +84,8 @@ module ActionController #:nodoc:
#
# === Custom options
#
- # <code>respond_with</code> also allow you to pass options that are forwarded
- # to the underlying render call. Those options are only applied success
+ # <code>respond_with</code> also allows you to pass options that are forwarded
+ # to the underlying render call. Those options are only applied for success
# scenarios. For instance, you can do the following in the create method above:
#
# def create
@@ -95,7 +95,7 @@ module ActionController #:nodoc:
# respond_with(@project, @task, :status => 201)
# end
#
- # This will return status 201 if the task was saved with success. If not,
+ # This will return status 201 if the task was saved successfully. If not,
# it will simply ignore the given options and return status 422 and the
# resource errors. To customize the failure scenario, you can pass a
# a block to <code>respond_with</code>:
@@ -116,8 +116,9 @@ module ActionController #:nodoc:
class Responder
attr_reader :controller, :request, :format, :resource, :resources, :options
- ACTIONS_FOR_VERBS = {
+ DEFAULT_ACTIONS_FOR_VERBS = {
:post => :new,
+ :patch => :edit,
:put => :edit
}
@@ -133,7 +134,7 @@ module ActionController #:nodoc:
end
delegate :head, :render, :redirect_to, :to => :controller
- delegate :get?, :post?, :put?, :delete?, :to => :request
+ delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request
# Undefine :to_json and :to_yaml since it's defined on Object
undef_method(:to_json) if method_defined?(:to_json)
@@ -172,7 +173,7 @@ module ActionController #:nodoc:
# responds to :to_format and display it.
#
def to_format
- if get? || !has_errors?
+ if get? || !has_errors? || response_overridden?
default_render
else
display_errors
@@ -202,10 +203,8 @@ module ActionController #:nodoc:
display resource
elsif post?
display resource, :status => :created, :location => api_location
- elsif has_empty_resource_definition?
- display empty_resource, :status => :ok
else
- head :ok
+ head :no_content
end
end
@@ -224,11 +223,15 @@ module ActionController #:nodoc:
alias :navigation_location :resource_location
alias :api_location :resource_location
- # If a given response block was given, use it, otherwise call render on
+ # If a response block was given, use it, otherwise call render on
# controller.
#
def default_render
- @default_response.call(options)
+ if @default_response
+ @default_response.call(options)
+ else
+ controller.default_render(options)
+ end
end
# Display is just a shortcut to render a resource with the current format.
@@ -262,37 +265,23 @@ module ActionController #:nodoc:
resource.respond_to?(:errors) && !resource.errors.empty?
end
- # By default, render the <code>:edit</code> action for HTML requests with failure, unless
- # the verb is POST.
+ # By default, render the <code>:edit</code> action for HTML requests with errors, unless
+ # the verb was POST.
#
def default_action
- @action ||= ACTIONS_FOR_VERBS[request.request_method_symbol]
- end
-
- # Check whether resource needs a specific definition of empty resource to be valid
- #
- def has_empty_resource_definition?
- respond_to?("empty_#{format}_resource")
- end
-
- # Delegate to proper empty resource method
- #
- def empty_resource
- send("empty_#{format}_resource")
- end
-
- # Return a valid empty JSON resource
- #
- def empty_json_resource
- "{}"
+ @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol]
end
def resource_errors
- respond_to?("#{format}_resource_errors") ? send("#{format}_resource_errors") : resource.errors
+ respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors
end
def json_resource_errors
{:errors => resource.errors}
end
+
+ def response_overridden?
+ @default_response.present?
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb
deleted file mode 100644
index 91d89ff9a4..0000000000
--- a/actionpack/lib/action_controller/metal/session_management.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module ActionController #:nodoc:
- module SessionManagement #:nodoc:
- extend ActiveSupport::Concern
-
- module ClassMethods
-
- end
- end
-end
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 5fe5334458..eeb37db2e7 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/file/path'
require 'rack/chunked'
module ActionController #:nodoc:
@@ -140,17 +139,17 @@ module ActionController #:nodoc:
# session or flash after the template starts rendering will not propagate
# to the client.
#
- # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+
+ # If you try to modify cookies, session or flash, an <tt>ActionDispatch::ClosedError</tt>
# will be raised, showing those objects are closed for modification.
#
# == Middlewares
#
# Middlewares that need to manipulate the body won't work with streaming.
# You should disable those middlewares whenever streaming in development
- # or production. For instance, +Rack::Bug+ won't work when streaming as it
+ # or production. For instance, <tt>Rack::Bug</tt> won't work when streaming as it
# needs to inject contents in the HTML body.
#
- # Also +Rack::Cache+ won't work with streaming as it does not support
+ # Also <tt>Rack::Cache</tt> won't work with streaming as it does not support
# streaming bodies yet. Whenever streaming Cache-Control is automatically
# set to "no-cache".
#
@@ -163,7 +162,7 @@ module ActionController #:nodoc:
# Currently, when an exception happens in development or production, Rails
# will automatically stream to the client:
#
- # "><script type="text/javascript">window.location = "/500.html"</script></html>
+ # "><script>window.location = "/500.html"</script></html>
#
# The first two characters (">) are required in case the exception happens
# while rendering attributes for a given tag. You can check the real cause
@@ -195,7 +194,7 @@ module ActionController #:nodoc:
# ==== Passenger
#
# To be described.
- #
+ #
module Streaming
extend ActiveSupport::Concern
@@ -217,7 +216,7 @@ module ActionController #:nodoc:
end
end
- # Call render_to_body if we are streaming instead of usual +render+.
+ # Call render_body if we are streaming instead of usual +render+.
def _render_template(options) #:nodoc:
if options.delete(:stream)
Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 0b40b1fc4c..e28c05cc2d 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -1,25 +1,22 @@
-# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
-# the <tt>_routes</tt> method. Otherwise, an exception will be raised.
-#
-# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
-# url options like the +host+. In order to do so, this module requires the host class
-# to implement +env+ and +request+, which need to be a Rack-compatible.
-#
-# Example:
-#
-# class RootUrl
-# include ActionController::UrlFor
-# include Rails.application.routes.url_helpers
-#
-# delegate :env, :request, :to => :controller
-#
-# def initialize(controller)
-# @controller = controller
-# @url = root_path # named route from the application.
-# end
-# end
-#
module ActionController
+ # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
+ # the <tt>_routes</tt> method. Otherwise, an exception will be raised.
+ #
+ # In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
+ # url options like the +host+. In order to do so, this module requires the host class
+ # to implement +env+ and +request+, which need to be a Rack-compatible.
+ #
+ # class RootUrl
+ # include ActionController::UrlFor
+ # include Rails.application.routes.url_helpers
+ #
+ # delegate :env, :request, :to => :controller
+ #
+ # def initialize(controller)
+ # @controller = controller
+ # @url = root_path # named route from the application.
+ # end
+ # end
module UrlFor
extend ActiveSupport::Concern
@@ -42,6 +39,5 @@ module ActionController
@_url_options
end
end
-
end
end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index f0c29825ba..851a2c4aee 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -3,38 +3,49 @@ require "action_controller"
require "action_dispatch/railtie"
require "action_view/railtie"
require "abstract_controller/railties/routes_helpers"
-require "action_controller/railties/paths"
+require "action_controller/railties/helpers"
module ActionController
- class Railtie < Rails::Railtie
+ class Railtie < Rails::Railtie #:nodoc:
config.action_controller = ActiveSupport::OrderedOptions.new
- initializer "action_controller.logger" do
- ActiveSupport.on_load(:action_controller) { self.logger ||= Rails.logger }
+ initializer "action_controller.assets_config", :group => :all do |app|
+ app.config.action_controller.assets_dir ||= app.config.paths["public"].first
end
- initializer "action_controller.initialize_framework_caches" do
- ActiveSupport.on_load(:action_controller) { self.cache_store ||= RAILS_CACHE }
+ initializer "action_controller.set_helpers_path" do |app|
+ ActionController::Helpers.helpers_path = app.helpers_paths
end
initializer "action_controller.set_configs" do |app|
paths = app.config.paths
options = app.config.action_controller
- options.assets_dir ||= paths["public"].first
+ options.logger ||= Rails.logger
+ options.cache_store ||= Rails.cache
+
options.javascripts_dir ||= paths["public/javascripts"].first
options.stylesheets_dir ||= paths["public/stylesheets"].first
options.page_cache_directory ||= paths["public"].first
- # make sure readers methods get compiled
+ # Ensure readers methods get compiled
options.asset_path ||= app.config.asset_path
options.asset_host ||= app.config.asset_host
+ options.relative_url_root ||= app.config.relative_url_root
ActiveSupport.on_load(:action_controller) do
include app.routes.mounted_helpers
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
- extend ::ActionController::Railties::Paths.with(app)
- options.each { |k,v| send("#{k}=", v) }
+ extend ::ActionController::Railties::Helpers
+
+ options.each do |k,v|
+ k = "#{k}="
+ if respond_to?(k)
+ send(k, v)
+ elsif !Base.respond_to?(k)
+ raise "Invalid option key: #{k}"
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/railties/helpers.rb b/actionpack/lib/action_controller/railties/helpers.rb
new file mode 100644
index 0000000000..3985c6b273
--- /dev/null
+++ b/actionpack/lib/action_controller/railties/helpers.rb
@@ -0,0 +1,22 @@
+module ActionController
+ module Railties
+ module Helpers
+ def inherited(klass)
+ super
+ return unless klass.respond_to?(:helpers_path=)
+
+ if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) }
+ paths = namespace.railtie_helpers_paths
+ else
+ paths = ActionController::Helpers.helpers_path
+ end
+
+ klass.helpers_path = paths
+
+ if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
+ klass.helper :all
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb
deleted file mode 100644
index 699c44c62c..0000000000
--- a/actionpack/lib/action_controller/railties/paths.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module ActionController
- module Railties
- module Paths
- def self.with(app)
- Module.new do
- define_method(:inherited) do |klass|
- super(klass)
-
- if namespace = klass.parents.detect {|m| m.respond_to?(:_railtie) }
- paths = namespace._railtie.paths["app/helpers"].existent
- else
- paths = app.config.helpers_paths
- end
-
- klass.helpers_path = paths
- if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
- klass.helper :all
- end
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 2036442cfe..16a5decc62 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -2,8 +2,8 @@ require 'active_support/core_ext/module'
module ActionController
# The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
- # Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
- # the view actions to a higher logical level. Example:
+ # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to
+ # a higher logical level.
#
# # routes
# resources :posts
@@ -14,9 +14,9 @@ module ActionController
# <% end %> </div>
#
# # controller
- # def destroy
+ # def update
# post = Post.find(params[:id])
- # post.destroy
+ # post.update_attributes(params[:post])
#
# redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
# end
@@ -30,7 +30,7 @@ module ActionController
JOIN = '_'.freeze
NEW = 'new'.freeze
- # The DOM class convention is to use the singular form of an object or class. Examples:
+ # The DOM class convention is to use the singular form of an object or class.
#
# dom_class(post) # => "post"
# dom_class(Person) # => "person"
@@ -45,7 +45,7 @@ module ActionController
end
# The DOM id convention is to use the singular form of an object or class with the id following an underscore.
- # If no id is found, prefix with "new_" instead. Examples:
+ # If no id is found, prefix with "new_" instead.
#
# dom_id(Post.find(45)) # => "post_45"
# dom_id(Post.new) # => "new_post"
@@ -53,6 +53,7 @@ module ActionController
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
#
# dom_id(Post.find(45), :edit) # => "edit_post_45"
+ # dom_id(Post.new, :custom) # => "custom_post"
def dom_id(record, prefix = nil)
if record_id = record_key_for_dom_id(record)
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
@@ -74,12 +75,7 @@ module ActionController
def record_key_for_dom_id(record)
record = record.to_model if record.respond_to?(:to_model)
key = record.to_key
- key ? sanitize_dom_id(key.join('_')) : key
- end
-
- # Replaces characters that are invalid in HTML DOM ids with valid ones.
- def sanitize_dom_id(candidate_id)
- candidate_id # TODO implement conversion to valid DOM id values
+ key ? key.join('_') : key
end
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 6913c1ef4a..028a8d3fba 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -20,20 +20,25 @@ module ActionController
ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
path = payload[:layout]
- @layouts[path] += 1
+ if path
+ @layouts[path] += 1
+ if path =~ /^layouts\/(.*)/
+ @layouts[$1] += 1
+ end
+ end
end
ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
path = payload[:virtual_path]
next unless path
partial = path =~ /^.*\/_[^\/]*$/
+
if partial
@partials[path] += 1
@partials[path.split("/").last] += 1
- @templates[path] += 1
- else
- @templates[path] += 1
end
+
+ @templates[path] += 1
end
end
@@ -51,11 +56,21 @@ module ActionController
# Asserts that the request was rendered with the appropriate template file or partials.
#
- # ==== Examples
- #
# # assert that the "new" view template was rendered
# assert_template "new"
#
+ # # assert that the exact template "admin/posts/new" was rendered
+ # assert_template %r{\Aadmin/posts/new\Z}
+ #
+ # # assert that the layout 'admin' was rendered
+ # assert_template :layout => 'admin'
+ # assert_template :layout => 'layouts/admin'
+ # assert_template :layout => :admin
+ #
+ # # assert that no layout was rendered
+ # assert_template :layout => nil
+ # assert_template :layout => false
+ #
# # assert that the "_customer" partial was rendered twice
# assert_template :partial => '_customer', :count => 2
#
@@ -67,25 +82,40 @@ module ActionController
#
# # assert that the "_customer" partial was rendered with a specific object
# assert_template :partial => '_customer', :locals => { :customer => @customer }
- #
def assert_template(options = {}, message = nil)
- validate_request!
+ # Force body to be read in case the
+ # template is being streamed
+ response.body
case options
- when NilClass, String, Symbol
+ when NilClass, String, Symbol, Regexp
options = options.to_s if Symbol === options
rendered = @templates
- msg = build_message(message,
- "expecting <?> but rendering with <?>",
- options, rendered.keys.join(', '))
- assert_block(msg) do
+ msg = message || sprintf("expecting <%s> but rendering with <%s>",
+ options.inspect, rendered.keys)
+ matches_template =
if options
rendered.any? { |t,num| t.match(options) }
else
@templates.blank?
end
- end
+ assert matches_template, msg
when Hash
+ if options.key?(:layout)
+ expected_layout = options[:layout]
+ msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
+ expected_layout, @layouts.keys)
+
+ case expected_layout
+ when String, Symbol
+ assert_includes @layouts.keys, expected_layout.to_s, msg
+ when Regexp
+ assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg)
+ when nil, false
+ assert(@layouts.empty?, msg)
+ end
+ end
+
if expected_partial = options[:partial]
if expected_locals = options[:locals]
actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')]
@@ -94,33 +124,20 @@ module ActionController
end
elsif expected_count = options[:count]
actual_count = @partials[expected_partial]
- msg = build_message(message,
- "expecting ? to be rendered ? time(s) but rendered ? time(s)",
+ msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
expected_partial, expected_count, actual_count)
assert(actual_count == expected_count.to_i, msg)
- elsif options.key?(:layout)
- msg = build_message(message,
- "expecting layout <?> but action rendered <?>",
- expected_layout, @layouts.keys)
-
- case layout = options[:layout]
- when String
- assert(@layouts.include?(expected_layout), msg)
- when Regexp
- assert(@layouts.any? {|l| l =~ layout }, msg)
- when nil
- assert(@layouts.empty?, msg)
- end
else
- msg = build_message(message,
- "expecting partial <?> but action rendered <?>",
+ msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
options[:partial], @partials.keys)
- assert(@partials.include?(expected_partial), msg)
+ assert_includes @partials, expected_partial, msg
end
- else
+ elsif options.key?(:partial)
assert @partials.empty?,
"Expected no partials to be rendered"
end
+ else
+ raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
end
end
end
@@ -135,9 +152,6 @@ module ActionController
class Result < ::Array #:nodoc:
def to_s() join '/' end
- def self.new_escaped(strings)
- new strings.collect {|str| uri_parser.unescape str}
- end
end
def assign_parameters(routes, controller_path, action, parameters = {})
@@ -145,17 +159,23 @@ module ActionController
extra_keys = routes.extra_keys(parameters)
non_path_parameters = get? ? query_parameters : request_parameters
parameters.each do |key, value|
- if value.is_a? Fixnum
- value = value.to_s
- elsif value.is_a? Array
- value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v })
- elsif value.is_a? String
+ if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
+ value = value.map{ |v| v.duplicable? ? v.dup : v }
+ elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
+ value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
+ elsif value.frozen? && value.duplicable?
value = value.dup
end
if extra_keys.include?(key.to_sym)
non_path_parameters[key] = value
else
+ if value.is_a?(Array)
+ value = Result.new(value.map(&:to_param))
+ else
+ value = value.to_param
+ end
+
path_parameters[key.to_s] = value
end
end
@@ -229,7 +249,7 @@ module ActionController
# == Basic example
#
# Functional tests are written as follows:
- # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate
+ # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
# an HTTP request.
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
# the controller's HTTP response, the database contents, etc.
@@ -250,6 +270,13 @@ module ActionController
# end
# end
#
+ # You can also send a real document in the simulated HTTP request.
+ #
+ # def test_create
+ # json = {:book => { :title => "Love Hina" }}.to_json
+ # post :create, json
+ # end
+ #
# == Special instance variables
#
# ActionController::TestCase will also automatically provide the following instance
@@ -296,11 +323,11 @@ module ActionController
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
# assert flash.empty? # makes sure that there's nothing in the flash
#
- # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
+ # For historic reasons, the assigns hash uses string-based keys. So <tt>assigns[:person]</tt> won't work, but <tt>assigns["person"]</tt> will. To
# appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
- # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
+ # So <tt>assigns(:person)</tt> will work just like <tt>assigns["person"]</tt>, but again, <tt>assigns[:person]</tt> will not work.
#
- # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
+ # On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
#
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
# action call which can then be asserted against.
@@ -320,10 +347,15 @@ module ActionController
# == \Testing named routes
#
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
- # Example:
#
# assert_redirected_to page_url(:title => 'foo')
class TestCase < ActiveSupport::TestCase
+
+ # Use AS::TestCase for the base class when describing a model
+ register_spec_type(self) do |desc|
+ desc < ActionController::Base
+ end
+
module Behavior
extend ActiveSupport::Concern
include ActionDispatch::TestProcess
@@ -333,16 +365,15 @@ module ActionController
module ClassMethods
# Sets the controller class name. Useful if the name can't be inferred from test class.
- # Normalizes +controller_class+ before using. Examples:
+ # Normalizes +controller_class+ before using.
#
# tests WidgetController
# tests :widget
# tests 'widget'
- #
def tests(controller_class)
case controller_class
when String, Symbol
- self.controller_class = "#{controller_class.to_s.underscore}_controller".camelize.constantize
+ self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
when Class
self.controller_class = controller_class
else
@@ -374,28 +405,33 @@ module ActionController
end
# Executes a request simulating GET HTTP method and set/volley the response
- def get(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "GET")
+ def get(action, *args)
+ process(action, "GET", *args)
end
# Executes a request simulating POST HTTP method and set/volley the response
- def post(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "POST")
+ def post(action, *args)
+ process(action, "POST", *args)
+ end
+
+ # Executes a request simulating PATCH HTTP method and set/volley the response
+ def patch(action, *args)
+ process(action, "PATCH", *args)
end
# Executes a request simulating PUT HTTP method and set/volley the response
- def put(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "PUT")
+ def put(action, *args)
+ process(action, "PUT", *args)
end
# Executes a request simulating DELETE HTTP method and set/volley the response
- def delete(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "DELETE")
+ def delete(action, *args)
+ process(action, "DELETE", *args)
end
# Executes a request simulating HEAD HTTP method and set/volley the response
def head(action, parameters = nil, session = nil, flash = nil)
- process(action, parameters, session, flash, "HEAD")
+ process(action, "HEAD", parameters, session, flash)
end
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@@ -421,19 +457,20 @@ module ActionController
end
end
- def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
- # Ensure that numbers and symbols passed as params are converted to
- # proper params, as is the case when engaging rack.
- parameters = paramify_values(parameters)
+ def process(action, http_method = 'GET', *args)
+ check_required_ivars
+ http_method, args = handle_old_process_api(http_method, args)
- # Sanity check for required instance variables so we can give an
- # understandable error message.
- %w(@routes @controller @request @response).each do |iv_name|
- if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
- raise "#{iv_name} is nil: make sure you set it in your test's setup method."
- end
+ if args.first.is_a?(String) && http_method != 'HEAD'
+ @request.env['RAW_POST_DATA'] = args.shift
end
+ parameters, session, flash = args
+
+ # Ensure that numbers and symbols passed as params are converted to
+ # proper params, as is the case when engaging rack.
+ parameters = paramify_values(parameters) if html_format?(parameters)
+
@request.recycle!
@response.recycle!
@controller.response_body = nil
@@ -445,17 +482,15 @@ module ActionController
parameters ||= {}
controller_class_name = @controller.class.anonymous? ?
- "anonymous_controller" :
+ "anonymous" :
@controller.class.name.underscore.sub(/_controller$/, '')
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
- @request.session = ActionController::TestSession.new(session) if session
+ @request.session.update(session) if session
@request.session["flash"] = @request.flash.update(flash || {})
- @request.session["flash"].sweep
@controller.request = @request
- @controller.params.merge!(parameters)
build_request_uri(action, parameters)
@controller.class.class_eval { include Testing }
@controller.recycle!
@@ -481,11 +516,6 @@ module ActionController
end
end
- # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
- def rescue_action_in_public!
- @request.remote_addr = '208.77.188.166' # example.com
- end
-
included do
include ActionController::TemplateAssertions
include ActionDispatch::Assertions
@@ -494,6 +524,26 @@ module ActionController
end
private
+ def check_required_ivars
+ # Sanity check for required instance variables so we can give an
+ # understandable error message.
+ [:@routes, :@controller, :@request, :@response].each do |iv_name|
+ if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
+ raise "#{iv_name} is nil: make sure you set it in your test's setup method."
+ end
+ end
+ end
+
+ def handle_old_process_api(http_method, args)
+ # 4.0: Remove this method.
+ if http_method.is_a?(Hash)
+ ActiveSupport::Deprecation.warn("TestCase#process now expects the HTTP method as second argument: process(action, http_method, params, session, flash)")
+ args.unshift(http_method)
+ http_method = args.last.is_a?(String) ? args.last : "GET"
+ end
+
+ [http_method, args]
+ end
def build_request_uri(action, parameters)
unless @request.env["PATH_INFO"]
@@ -511,6 +561,12 @@ module ActionController
@request.env["QUERY_STRING"] = query_string || ""
end
end
+
+ def html_format?(parameters)
+ return true unless parameters.is_a?(Hash)
+ format = Mime[parameters[:format]]
+ format.nil? || format.html?
+ end
end
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
index 7fa3aead82..386820300a 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
@@ -4,7 +4,7 @@ require 'html/selector'
require 'html/sanitizer'
module HTML #:nodoc:
- # A top-level HTMl document. You give it a body of text, and it will parse that
+ # A top-level HTML document. You give it a body of text, and it will parse that
# text into a tree of nodes.
class Document #:nodoc:
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
index af06bffa16..6b269e7a31 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb
@@ -1,10 +1,12 @@
require 'set'
require 'cgi'
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/class/attribute_accessors'
module HTML
class Sanitizer
def sanitize(text, options = {})
+ validate_options(options)
return text unless sanitizeable?(text)
tokenize(text, options).join
end
@@ -27,6 +29,16 @@ module HTML
def process_node(node, result, options)
result << node.to_s
end
+
+ def validate_options(options)
+ if options[:tags] && !options[:tags].is_a?(Enumerable)
+ raise ArgumentError, "You should pass :tags as an Enumerable"
+ end
+
+ if options[:attributes] && !options[:attributes].is_a?(Enumerable)
+ raise ArgumentError, "You should pass :attributes as an Enumerable"
+ end
+ end
end
class FullSanitizer < Sanitizer
@@ -88,7 +100,7 @@ module HTML
self.allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto
feed svn urn aim rsync tag ssh sftp rtsp afs))
- # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept.
+ # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept.
self.allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse
border-color border-left-color border-right-color border-top-color clear color cursor direction display
elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height
@@ -171,7 +183,7 @@ module HTML
def contains_bad_protocols?(attr_name, value)
uri_attributes.include?(attr_name) &&
- (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase))
+ (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
end
end
end
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
index c252e01cf5..8ac8d34430 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
@@ -23,7 +23,7 @@ module HTML #:nodoc:
# Create a new Tokenizer for the given text.
def initialize(text)
- text.encode! if text.encoding_aware?
+ text.encode!
@scanner = StringScanner.new(text)
@position = 0
@line = 0
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 7f972fc281..1e4ac70f3d 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2011 David Heinemeier Hansson
+# Copyright (c) 2004-2012 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -21,14 +21,9 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-
require 'active_support'
require 'active_support/dependencies/autoload'
+require 'active_support/core_ext/module/attribute_accessors'
require 'action_pack'
require 'active_model'
@@ -47,20 +42,23 @@ module ActionDispatch
end
autoload_under 'middleware' do
+ autoload :RequestId
autoload :BestStandardsSupport
autoload :Callbacks
autoload :Cookies
+ autoload :DebugExceptions
+ autoload :ExceptionWrapper
autoload :Flash
autoload :Head
autoload :ParamsParser
+ autoload :PublicExceptions
autoload :Reloader
autoload :RemoteIp
- autoload :Rescue
autoload :ShowExceptions
+ autoload :SSL
autoload :Static
end
- autoload :ClosedError, 'action_dispatch/middleware/closed_error'
autoload :MiddlewareStack, 'action_dispatch/middleware/stack'
autoload :Routing
@@ -82,8 +80,11 @@ module ActionDispatch
autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
+ mattr_accessor :test_app
+
autoload_under 'testing' do
autoload :Assertions
autoload :Integration
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index aaed0d750f..5ee4c044ea 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -4,14 +4,18 @@ module ActionDispatch
module Http
module Cache
module Request
+
+ HTTP_IF_MODIFIED_SINCE = 'HTTP_IF_MODIFIED_SINCE'.freeze
+ HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze
+
def if_modified_since
- if since = env['HTTP_IF_MODIFIED_SINCE']
+ if since = env[HTTP_IF_MODIFIED_SINCE]
Time.rfc2822(since) rescue nil
end
end
def if_none_match
- env['HTTP_IF_NONE_MATCH']
+ env[HTTP_IF_NONE_MATCH]
end
def not_modified?(modified_at)
@@ -43,31 +47,49 @@ module ActionDispatch
alias :etag? :etag
def last_modified
- if last = headers['Last-Modified']
+ if last = headers[LAST_MODIFIED]
Time.httpdate(last)
end
end
def last_modified?
- headers.include?('Last-Modified')
+ headers.include?(LAST_MODIFIED)
end
def last_modified=(utc_time)
- headers['Last-Modified'] = utc_time.httpdate
+ headers[LAST_MODIFIED] = utc_time.httpdate
+ end
+
+ def date
+ if date_header = headers['Date']
+ Time.httpdate(date_header)
+ end
+ end
+
+ def date?
+ headers.include?('Date')
+ end
+
+ def date=(utc_time)
+ headers['Date'] = utc_time.httpdate
end
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
- @etag = self["ETag"] = %("#{Digest::MD5.hexdigest(key)}")
+ @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
end
private
+ LAST_MODIFIED = "Last-Modified".freeze
+ ETAG = "ETag".freeze
+ CACHE_CONTROL = "Cache-Control".freeze
+
def prepare_cache_control!
@cache_control = {}
- @etag = self["ETag"]
+ @etag = self[ETAG]
- if cache_control = self["Cache-Control"]
+ if cache_control = self[CACHE_CONTROL]
cache_control.split(/,\s*/).each do |segment|
first, last = segment.split("=")
@cache_control[first.to_sym] = last || true
@@ -81,28 +103,32 @@ module ActionDispatch
end
end
- DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
+ DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze
+ NO_CACHE = "no-cache".freeze
+ PUBLIC = "public".freeze
+ PRIVATE = "private".freeze
+ MUST_REVALIDATE = "must-revalidate".freeze
def set_conditional_cache_control!
- return if self["Cache-Control"].present?
+ return if self[CACHE_CONTROL].present?
control = @cache_control
if control.empty?
- headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
+ headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
elsif control[:no_cache]
- headers["Cache-Control"] = "no-cache"
+ headers[CACHE_CONTROL] = NO_CACHE
else
extras = control[:extras]
max_age = control[:max_age]
options = []
options << "max-age=#{max_age.to_i}" if max_age
- options << (control[:public] ? "public" : "private")
- options << "must-revalidate" if control[:must_revalidate]
+ options << (control[:public] ? PUBLIC : PRIVATE)
+ options << MUST_REVALIDATE if control[:must_revalidate]
options.concat(extras) if extras
- headers["Cache-Control"] = options.join(", ")
+ headers[CACHE_CONTROL] = options.join(", ")
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 8dd1af7f3d..6413929be3 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -10,8 +10,6 @@ module ActionDispatch
# value of the params hash and all subhashes is passed to it, the value
# or key can be replaced using String#replace or similar method.
#
- # Examples:
- #
# env["action_dispatch.parameter_filter"] = [:password]
# => replaces the value to all keys matching /password/i with "[FILTERED]"
#
@@ -22,7 +20,6 @@ module ActionDispatch
# v.reverse! if k =~ /secret/i
# end
# => reverses the value to all keys matching /secret/i
- #
module FilterParameters
extend ActiveSupport::Concern
@@ -50,7 +47,7 @@ module ActionDispatch
end
def env_filter
- parameter_filter_for(Array.wrap(@env["action_dispatch.parameter_filter"]) << /RAW_POST_DATA/)
+ parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) + [/RAW_POST_DATA/, "rack.request.form_vars"])
end
def parameter_filter_for(filters)
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 505d5560b1..a3bb25f75a 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -1,5 +1,3 @@
-require 'active_support/memoizable'
-
module ActionDispatch
module Http
class Headers < ::Hash
@@ -16,17 +14,18 @@ module ActionDispatch
end
def [](header_name)
- if include?(header_name)
- super
- else
- super(env_name(header_name))
- end
+ super env_name(header_name)
+ end
+
+ def fetch(header_name, default=nil, &block)
+ super env_name(header_name), default, &block
end
private
- # Converts a HTTP header name to an environment variable name.
+ # Converts a HTTP header name to an environment variable name if it is
+ # not contained within the headers hash.
def env_name(header_name)
- @@env_cache[header_name]
+ include?(header_name) ? header_name : @@env_cache[header_name]
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 5c48a60469..e31f3b823d 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/attribute_accessors'
+
module ActionDispatch
module Http
module MimeNegotiation
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 8a9f9c4315..0eaae80461 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -38,7 +38,7 @@ module Mime
# respond_to do |format|
# format.html
# format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"] }
- # format.xml { render :xml => @people.to_xml }
+ # format.xml { render :xml => @people }
# end
# end
# end
@@ -82,6 +82,7 @@ module Mime
class << self
TRAILING_STAR_REGEXP = /(text|application)\/\*/
+ PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
def lookup(string)
LOOKUP[string]
@@ -103,11 +104,12 @@ module Mime
SET << Mime.const_get(symbol.to_s.upcase)
([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
- ([symbol.to_s] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext] = SET.last }
+ ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
end
def parse(accept_header)
if accept_header !~ /,/
+ accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
if accept_header =~ TRAILING_STAR_REGEXP
parse_data_with_trailing_star($1)
else
@@ -117,7 +119,7 @@ module Mime
# keep track of creation order to keep the subsequent sort stable
list, index = [], 0
accept_header.split(/,/).each do |header|
- params, q = header.split(/;\s*q=/)
+ params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
if params.present?
params.strip!
@@ -177,11 +179,11 @@ module Mime
end
end
- # input: 'text'
- # returned value: [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]
+ # For an input of <tt>'text'</tt>, returns <tt>[Mime::JSON, Mime::XML, Mime::ICS,
+ # Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]</tt>.
#
- # input: 'application'
- # returned value: [Mime::HTML, Mime::JS, Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]
+ # For an input of <tt>'application'</tt>, returns <tt>[Mime::HTML, Mime::JS,
+ # Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]</tt>.
def parse_data_with_trailing_star(input)
Mime::SET.select { |m| m =~ input }
end
@@ -190,7 +192,7 @@ module Mime
#
# Usage:
#
- # Mime::Type.unregister(:mobile)
+ # Mime::Type.unregister(:mobile)
def unregister(symbol)
symbol = symbol.to_s.upcase
mime = Mime.const_get(symbol)
diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb
index 3da4f91051..a6b3aee5e7 100644
--- a/actionpack/lib/action_dispatch/http/mime_types.rb
+++ b/actionpack/lib/action_dispatch/http/mime_types.rb
@@ -9,7 +9,7 @@ Mime::Type.register "text/calendar", :ics
Mime::Type.register "text/csv", :csv
Mime::Type.register "image/png", :png, [], %w(png)
-Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe)
+Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg)
Mime::Type.register "image/gif", :gif, [], %w(gif)
Mime::Type.register "image/bmp", :bmp, [], %w(bmp)
Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff)
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
index 1480e8f77c..490b46c990 100644
--- a/actionpack/lib/action_dispatch/http/parameter_filter.rb
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -20,6 +20,8 @@ module ActionDispatch
@filters.present?
end
+ FILTERED = '[FILTERED]'.freeze
+
def compiled_filter
@compiled_filter ||= begin
regexps, blocks = compile_filter
@@ -29,7 +31,7 @@ module ActionDispatch
original_params.each do |key, value|
if regexps.find { |r| key =~ r }
- value = '[FILTERED]'
+ value = FILTERED
elsif value.is_a?(Hash)
value = filter(value)
elsif value.is_a?(Array)
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index ef5d207b26..bcfd0b0d00 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -35,14 +35,16 @@ module ActionDispatch
@env["action_dispatch.request.path_parameters"] ||= {}
end
+ def reset_parameters #:nodoc:
+ @env.delete("action_dispatch.request.parameters")
+ end
+
private
# TODO: Validate that the characters are UTF-8. If they aren't,
# you'll get a weird error down the road, but our form handling
# should really prevent that from happening
def encode_params(params)
- return params unless "ruby".encoding_aware?
-
if params.is_a?(String)
return params.force_encoding("UTF-8").encode!
elsif !params.is_a?(Hash)
diff --git a/actionpack/lib/action_dispatch/http/rack_cache.rb b/actionpack/lib/action_dispatch/http/rack_cache.rb
index cc8edee300..003ae4029d 100644
--- a/actionpack/lib/action_dispatch/http/rack_cache.rb
+++ b/actionpack/lib/action_dispatch/http/rack_cache.rb
@@ -8,8 +8,7 @@ module ActionDispatch
new
end
- # TODO: Finally deal with the RAILS_CACHE global
- def initialize(store = RAILS_CACHE)
+ def initialize(store = Rails.cache)
@store = store
end
@@ -33,7 +32,7 @@ module ActionDispatch
new
end
- def initialize(store = RAILS_CACHE)
+ def initialize(store = Rails.cache)
@store = store
end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 37d0a3e0b8..56908b5794 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -17,7 +17,10 @@ module ActionDispatch
include ActionDispatch::Http::Upload
include ActionDispatch::Http::URL
- LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
+ autoload :Session, 'action_dispatch/request/session'
+
+ LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
+
ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
@@ -35,14 +38,6 @@ module ActionDispatch
METHOD
end
- def self.new(env)
- if request = env["action_dispatch.request"] && request.instance_of?(self)
- return request
- end
-
- super
- end
-
def key?(key)
@env.key?(key)
end
@@ -94,31 +89,37 @@ module ActionDispatch
end
# Is this a GET (or HEAD) request?
- # Equivalent to <tt>request.request_method == :get</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :get</tt>.
def get?
HTTP_METHOD_LOOKUP[request_method] == :get
end
# Is this a POST request?
- # Equivalent to <tt>request.request_method == :post</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :post</tt>.
def post?
HTTP_METHOD_LOOKUP[request_method] == :post
end
+ # Is this a PATCH request?
+ # Equivalent to <tt>request.request_method == :patch</tt>.
+ def patch?
+ HTTP_METHOD_LOOKUP[request_method] == :patch
+ end
+
# Is this a PUT request?
- # Equivalent to <tt>request.request_method == :put</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :put</tt>.
def put?
HTTP_METHOD_LOOKUP[request_method] == :put
end
# Is this a DELETE request?
- # Equivalent to <tt>request.request_method == :delete</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :delete</tt>.
def delete?
HTTP_METHOD_LOOKUP[request_method] == :delete
end
# Is this a HEAD request?
- # Equivalent to <tt>request.method == :head</tt>.
+ # Equivalent to <tt>request.method_symbol == :head</tt>.
def head?
HTTP_METHOD_LOOKUP[method] == :head
end
@@ -130,10 +131,18 @@ module ActionDispatch
Http::Headers.new(@env)
end
+ def original_fullpath
+ @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath)
+ end
+
def fullpath
@fullpath ||= super
end
+ def original_url
+ base_url + original_fullpath
+ end
+
def media_type
content_mime_type.to_s
end
@@ -155,28 +164,21 @@ module ActionDispatch
@ip ||= super
end
- # Which IP addresses are "trusted proxies" that can be stripped from
- # the right-hand-side of X-Forwarded-For.
- #
- # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces.
- TRUSTED_PROXIES = %r{
- ^127\.0\.0\.1$ | # localhost
- ^(10 | # private IP 10.x.x.x
- 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
- 192\.168 # private IP 192.168.x.x
- )\.
- }x
-
- # Determines originating IP address. REMOTE_ADDR is the standard
- # but will fail if the user is behind a proxy. HTTP_CLIENT_IP and/or
- # HTTP_X_FORWARDED_FOR are set by proxies so check for these if
- # REMOTE_ADDR is a proxy. HTTP_X_FORWARDED_FOR may be a comma-
- # delimited list in the case of multiple chained proxies; the last
- # address which is not trusted is the originating IP.
+ # Originating IP address, usually set by the RemoteIp middleware.
def remote_ip
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
end
+ # Returns the unique request id, which is based off either the X-Request-Id header that can
+ # be generated by a firewall, load balancer, or web server or by the RequestId middleware
+ # (which sets the action_dispatch.request_id environment variable).
+ #
+ # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
+ # This relies on the rack variable set by the ActionDispatch::RequestId middleware.
+ def uuid
+ @uuid ||= env["action_dispatch.request_id"]
+ end
+
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
@@ -196,7 +198,7 @@ module ActionDispatch
# variable is already set, wrap it in a StringIO.
def body
if raw_post = @env['RAW_POST_DATA']
- raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
+ raw_post.force_encoding(Encoding::BINARY)
StringIO.new(raw_post)
else
@env['rack.input']
@@ -220,11 +222,11 @@ module ActionDispatch
end
def session=(session) #:nodoc:
- @env['rack.session'] = session
+ Session.set @env, session
end
def session_options=(options)
- @env['rack.session.options'] = options
+ Session::Options.set @env, options
end
# Override Rack's GET method to support indifferent access
@@ -251,7 +253,7 @@ module ActionDispatch
# True if the request came from localhost, 127.0.0.1.
def local?
- LOCALHOST.any? { |local_ip| local_ip === remote_addr && local_ip === remote_ip }
+ LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
private
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index f1e85559a3..cc46f9983c 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -29,7 +29,7 @@ module ActionDispatch # :nodoc:
# class DemoControllerTest < ActionDispatch::IntegrationTest
# def test_print_root_path_to_console
# get('/')
- # puts @response.body
+ # puts response.body
# end
# end
class Response
@@ -51,9 +51,12 @@ module ActionDispatch # :nodoc:
# If a character set has been defined for this response (see charset=) then
# the character set information will also be included in the content type
# information.
- attr_accessor :charset, :content_type
+ attr_accessor :charset
+ attr_reader :content_type
- CONTENT_TYPE = "Content-Type"
+ CONTENT_TYPE = "Content-Type".freeze
+ SET_COOKIE = "Set-Cookie".freeze
+ LOCATION = "Location".freeze
cattr_accessor(:default_charset) { "utf-8" }
@@ -66,10 +69,10 @@ module ActionDispatch # :nodoc:
@sending_file = false
@blank = false
- if content_type = self["Content-Type"]
+ if content_type = self[CONTENT_TYPE]
type, charset = content_type.split(/;\s*charset=/)
@content_type = Mime::Type.lookup(type)
- @charset = charset || "UTF-8"
+ @charset = charset || self.class.default_charset
end
prepare_cache_control!
@@ -81,6 +84,10 @@ module ActionDispatch # :nodoc:
@status = Rack::Utils.status_code(status)
end
+ def content_type=(content_type)
+ @content_type = content_type.to_s
+ end
+
# The response code of the request
def response_code
@status
@@ -109,9 +116,9 @@ module ActionDispatch # :nodoc:
end
def body
- str = ''
- each { |part| str << part.to_s }
- str
+ strings = []
+ each { |part| strings << part.to_s }
+ strings.join
end
EMPTY = " "
@@ -119,14 +126,7 @@ module ActionDispatch # :nodoc:
def body=(body)
@blank = true if body == EMPTY
- # Explicitly check for strings. This is *wrong* theoretically
- # but if we don't check this, the performance on string bodies
- # is bad on Ruby 1.8 (because strings responds to each then).
- @body = if body.respond_to?(:to_str) || !body.respond_to?(:each)
- [body]
- else
- body
- end
+ @body = body.respond_to?(:each) ? body : [body]
end
def body_parts
@@ -142,12 +142,12 @@ module ActionDispatch # :nodoc:
end
def location
- headers['Location']
+ headers[LOCATION]
end
alias_method :redirect_url, :location
def location=(url)
- headers['Location'] = url
+ headers[LOCATION] = url
end
def close
@@ -158,10 +158,10 @@ module ActionDispatch # :nodoc:
assign_default_content_type_and_charset!
handle_conditional_get!
- @header["Set-Cookie"] = @header["Set-Cookie"].join("\n") if @header["Set-Cookie"].respond_to?(:join)
+ @header[SET_COOKIE] = @header[SET_COOKIE].join("\n") if @header[SET_COOKIE].respond_to?(:join)
if [204, 304].include?(@status)
- @header.delete "Content-Type"
+ @header.delete CONTENT_TYPE
[@status, @header, []]
else
[@status, @header, self]
@@ -175,7 +175,7 @@ module ActionDispatch # :nodoc:
# assert_equal 'AuthorOfNewPage', r.cookies['author']
def cookies
cookies = {}
- if header = self["Set-Cookie"]
+ if header = self[SET_COOKIE]
header = header.split("\n") if header.respond_to?(:to_str)
header.each do |cookie|
if pair = cookie.split(';').first
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 94fa747a79..ce8c2729e9 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -4,11 +4,12 @@ module ActionDispatch
attr_accessor :original_filename, :content_type, :tempfile, :headers
def initialize(hash)
+ @tempfile = hash[:tempfile]
+ raise(ArgumentError, ':tempfile is required') unless @tempfile
+
@original_filename = encode_filename(hash[:filename])
@content_type = hash[:type]
@headers = hash[:head]
- @tempfile = hash[:tempfile]
- raise(ArgumentError, ':tempfile is required') unless @tempfile
end
def read(*args)
@@ -16,18 +17,15 @@ module ActionDispatch
end
# Delegate these methods to the tempfile.
- [:open, :path, :rewind, :size].each do |method|
+ [:open, :path, :rewind, :size, :eof?].each do |method|
class_eval "def #{method}; @tempfile.#{method}; end"
end
-
+
private
+
def encode_filename(filename)
- # Encode the filename in the utf8 encoding, unless it is nil or we're in 1.8
- if "ruby".encoding_aware? && filename
- filename.force_encoding("UTF-8").encode!
- else
- filename
- end
+ # Encode the filename in the utf8 encoding, unless it is nil
+ filename.force_encoding("UTF-8").encode! if filename
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index c8ddd07bfa..4266ec042e 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -1,6 +1,8 @@
module ActionDispatch
module Http
module URL
+ IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
+
mattr_accessor :tld_length
self.tld_length = 1
@@ -21,38 +23,45 @@ module ActionDispatch
end
def url_for(options = {})
- unless options[:host].present? || options[:only_path].present?
+ path = ""
+ path << options.delete(:script_name).to_s.chomp("/")
+ path << options.delete(:path).to_s
+
+ params = options[:params] || {}
+ params.reject! {|k,v| v.to_param.nil? }
+
+ result = build_host_url(options)
+
+ result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ result << "?#{params.to_query}" unless params.empty?
+ result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
+ result
+ end
+
+ private
+
+ def build_host_url(options)
+ if options[:host].blank? && options[:only_path].blank?
raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
end
- rewritten_url = ""
+ result = ""
unless options[:only_path]
unless options[:protocol] == false
- rewritten_url << (options[:protocol] || "http")
- rewritten_url << ":" unless rewritten_url.match(%r{:|//})
+ result << (options[:protocol] || "http")
+ result << ":" unless result.match(%r{:|//})
end
- rewritten_url << "//" unless rewritten_url.match("//")
- rewritten_url << rewrite_authentication(options)
- rewritten_url << host_or_subdomain_and_domain(options)
- rewritten_url << ":#{options.delete(:port)}" if options[:port]
+ result << "//" unless result.match("//")
+ result << rewrite_authentication(options)
+ result << host_or_subdomain_and_domain(options)
+ result << ":#{options.delete(:port)}" if options[:port]
end
-
- path = options.delete(:path) || ''
-
- params = options[:params] || {}
- params.reject! {|k,v| v.to_param.nil? }
-
- rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
- rewritten_url << "?#{params.to_query}" unless params.empty?
- rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
- rewritten_url
+ result
end
- private
-
def named_host?(host)
- !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
+ host && IP_HOST_REGEXP !~ host
end
def rewrite_authentication(options)
@@ -64,13 +73,13 @@ module ActionDispatch
end
def host_or_subdomain_and_domain(options)
- return options[:host] if options[:subdomain].nil? && options[:domain].nil?
+ return options[:host] if !named_host?(options[:host]) || (options[:subdomain].nil? && options[:domain].nil?)
tld_length = options[:tld_length] || @@tld_length
host = ""
unless options[:subdomain] == false
- host << (options[:subdomain] || extract_subdomain(options[:host], tld_length))
+ host << (options[:subdomain] || extract_subdomain(options[:host], tld_length)).to_param
host << "."
end
host << (options[:domain] || extract_domain(options[:host], tld_length))
@@ -167,7 +176,7 @@ module ActionDispatch
# such as 2 to catch <tt>"www"</tt> instead of <tt>"www.rubyonrails"</tt>
# in "www.rubyonrails.co.uk".
def subdomain(tld_length = @@tld_length)
- subdomains(tld_length).join(".")
+ ActionDispatch::Http::URL.extract_subdomain(host, tld_length)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index 8c0f4052ec..338b116940 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -5,7 +5,7 @@ module ActionDispatch
class Callbacks
include ActiveSupport::Callbacks
- define_callbacks :call, :rescuable => true
+ define_callbacks :call
class << self
delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader"
@@ -24,9 +24,15 @@ module ActionDispatch
end
def call(env)
- run_callbacks :call do
- @app.call(env)
+ error = nil
+ result = run_callbacks :call do
+ begin
+ @app.call(env)
+ rescue => error
+ end
end
+ raise error if error
+ result
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/closed_error.rb b/actionpack/lib/action_dispatch/middleware/closed_error.rb
deleted file mode 100644
index 0a4db47f4b..0000000000
--- a/actionpack/lib/action_dispatch/middleware/closed_error.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-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 8c4615c0c1..771f075275 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/keys'
module ActionDispatch
- class Request
+ class Request < Rack::Request
def cookie_jar
env['action_dispatch.cookies'] ||= Cookies::CookieJar.build(self)
end
@@ -26,9 +26,9 @@ module ActionDispatch
# # Sets a cookie that expires in 1 hour.
# cookies[:login] = { :value => "XJ-122", :expires => 1.hour.from_now }
#
- # # Sets a signed cookie, which prevents a user from tampering with its value.
+ # # Sets a signed cookie, which prevents users from tampering with its value.
# # The cookie is signed by your app's <tt>config.secret_token</tt> value.
- # # Rails generates this value by default when you create a new Rails app.
+ # # It can be read using the signed method <tt>cookies.signed[:key]</tt>
# cookies.signed[:user_id] = current_user.id
#
# # Sets a "permanent" cookie (which expires in 20 years from now).
@@ -39,9 +39,10 @@ module ActionDispatch
#
# Examples for reading:
#
- # cookies[:user_name] # => "david"
- # cookies.size # => 2
- # cookies[:lat_lon] # => [47.68, -122.37]
+ # cookies[:user_name] # => "david"
+ # cookies.size # => 2
+ # cookies[:lat_lon] # => [47.68, -122.37]
+ # cookies.signed[:login] # => "XJ-122"
#
# Example for deleting:
#
@@ -82,7 +83,7 @@ module ActionDispatch
TOKEN_KEY = "action_dispatch.secret_token".freeze
# Raised when storing more than 4K of session data.
- class CookieOverflow < StandardError; end
+ CookieOverflow = Class.new StandardError
class CookieJar #:nodoc:
include Enumerable
@@ -117,14 +118,9 @@ module ActionDispatch
@delete_cookies = {}
@host = host
@secure = secure
- @closed = false
@cookies = {}
end
- attr_reader :closed
- alias :closed? :closed
- def close!; @closed = true end
-
def each(&block)
@cookies.each(&block)
end
@@ -158,14 +154,13 @@ module ActionDispatch
end
elsif options[:domain].is_a? Array
# if host matches one of the supplied domains without a dot in front of it
- options[:domain] = options[:domain].find {|domain| @host.include? domain[/^\.?(.*)$/, 1] }
+ options[:domain] = options[:domain].find {|domain| @host.include? domain.sub(/^\./, '') }
end
end
# 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]
@@ -174,12 +169,14 @@ module ActionDispatch
options = { :value => value }
end
- value = @cookies[key.to_s] = value
-
handle_options(options)
- @set_cookies[key.to_s] = options
- @delete_cookies.delete(key.to_s)
+ if @cookies[key.to_s] != value or options[:expires]
+ @cookies[key.to_s] = value
+ @set_cookies[key.to_s] = options
+ @delete_cookies.delete(key.to_s)
+ end
+
value
end
@@ -187,8 +184,9 @@ module ActionDispatch
# and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in
# an options hash to delete cookies with extra data such as a <tt>:path</tt>.
def delete(key, options = {})
- options.symbolize_keys!
+ return unless @cookies.has_key? key.to_s
+ options.symbolize_keys!
handle_options(options)
value = @cookies.delete(key.to_s)
@@ -196,6 +194,15 @@ module ActionDispatch
value
end
+ # Whether the given cookie is to be deleted by this CookieJar.
+ # Like <tt>[]=</tt>, you can pass in an options hash to test if a
+ # deletion applies to a specific <tt>:path</tt>, <tt>:domain</tt> etc.
+ def deleted?(key, options = {})
+ options.symbolize_keys!
+ handle_options(options)
+ @delete_cookies[key.to_s] == options
+ end
+
# Removes all cookies on the client machine by calling <tt>delete</tt> for each cookie
def clear(options = {})
@cookies.each_key{ |k| delete(k, options) }
@@ -221,7 +228,7 @@ module ActionDispatch
# cookie was tampered with by the user (or a 3rd party), an ActiveSupport::MessageVerifier::InvalidSignature exception will
# be raised.
#
- # This jar requires that you set a suitable secret for the verification on your app's config.secret_token.
+ # This jar requires that you set a suitable secret for the verification on your app's +config.secret_token+.
#
# Example:
#
@@ -243,10 +250,13 @@ module ActionDispatch
@delete_cookies.clear
end
+ mattr_accessor :always_write_cookie
+ self.always_write_cookie = false
+
private
def write_cookie?(cookie)
- @secure || !cookie[:secure] || defined?(Rails.env) && Rails.env.development?
+ @secure || !cookie[:secure] || always_write_cookie
end
end
@@ -256,7 +266,6 @@ module ActionDispatch
end
def []=(key, options)
- raise ClosedError, :cookies if closed?
if options.is_a?(Hash)
options.symbolize_keys!
else
@@ -267,10 +276,6 @@ module ActionDispatch
@parent_jar[key] = options
end
- def signed
- @signed ||= SignedCookieJar.new(self, @secret)
- end
-
def method_missing(method, *arguments, &block)
@parent_jar.send(method, *arguments, &block)
end
@@ -295,7 +300,6 @@ 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])
@@ -338,7 +342,6 @@ module ActionDispatch
end
def call(env)
- cookie_jar = nil
status, headers, body = @app.call(env)
if cookie_jar = env['action_dispatch.cookies']
@@ -349,9 +352,6 @@ 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/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
new file mode 100644
index 0000000000..b903f98761
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb
@@ -0,0 +1,82 @@
+require 'action_dispatch/http/request'
+require 'action_dispatch/middleware/exception_wrapper'
+
+module ActionDispatch
+ # This middleware is responsible for logging exceptions and
+ # showing a debugging page in case the request is local.
+ class DebugExceptions
+ RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ begin
+ response = @app.call(env)
+
+ if response[1]['X-Cascade'] == 'pass'
+ body = response[2]
+ body.close if body.respond_to?(:close)
+ raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
+ end
+ rescue Exception => exception
+ raise exception if env['action_dispatch.show_exceptions'] == false
+ end
+
+ exception ? render_exception(env, exception) : response
+ end
+
+ private
+
+ def render_exception(env, exception)
+ wrapper = ExceptionWrapper.new(env, exception)
+ log_error(env, wrapper)
+
+ if env['action_dispatch.show_detailed_exceptions']
+ template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
+ :request => Request.new(env),
+ :exception => wrapper.exception,
+ :application_trace => wrapper.application_trace,
+ :framework_trace => wrapper.framework_trace,
+ :full_trace => wrapper.full_trace
+ )
+
+ file = "rescues/#{wrapper.rescue_template}"
+ body = template.render(:template => file, :layout => 'rescues/layout')
+ render(wrapper.status_code, body)
+ else
+ raise exception
+ end
+ end
+
+ def render(status, body)
+ [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+
+ def log_error(env, wrapper)
+ logger = logger(env)
+ return unless logger
+
+ exception = wrapper.exception
+
+ trace = wrapper.application_trace
+ trace = wrapper.framework_trace if trace.empty?
+
+ ActiveSupport::Deprecation.silence do
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << trace.join("\n ")
+ logger.fatal("#{message}\n\n")
+ end
+ end
+
+ def logger(env)
+ env['action_dispatch.logger'] || stderr_logger
+ end
+
+ def stderr_logger
+ @stderr_logger ||= ActiveSupport::Logger.new($stderr)
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
new file mode 100644
index 0000000000..a8f49bd3bd
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -0,0 +1,80 @@
+require 'action_controller/metal/exceptions'
+require 'active_support/core_ext/exception'
+require 'active_support/core_ext/class/attribute_accessors'
+
+module ActionDispatch
+ class ExceptionWrapper
+ cattr_accessor :rescue_responses
+ @@rescue_responses = Hash.new(:internal_server_error)
+ @@rescue_responses.merge!(
+ 'ActionController::RoutingError' => :not_found,
+ 'AbstractController::ActionNotFound' => :not_found,
+ 'ActionController::MethodNotAllowed' => :method_not_allowed,
+ 'ActionController::NotImplemented' => :not_implemented,
+ 'ActionController::UnknownFormat' => :not_acceptable,
+ 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
+ )
+
+ cattr_accessor :rescue_templates
+ @@rescue_templates = Hash.new('diagnostics')
+ @@rescue_templates.merge!(
+ 'ActionView::MissingTemplate' => 'missing_template',
+ 'ActionController::RoutingError' => 'routing_error',
+ 'AbstractController::ActionNotFound' => 'unknown_action',
+ 'ActionView::Template::Error' => 'template_error'
+ )
+
+ attr_reader :env, :exception
+
+ def initialize(env, exception)
+ @env = env
+ @exception = original_exception(exception)
+ end
+
+ def rescue_template
+ @@rescue_templates[@exception.class.name]
+ end
+
+ def status_code
+ Rack::Utils.status_code(@@rescue_responses[@exception.class.name])
+ end
+
+ def application_trace
+ clean_backtrace(:silent)
+ end
+
+ def framework_trace
+ clean_backtrace(:noise)
+ end
+
+ def full_trace
+ clean_backtrace(:all)
+ end
+
+ private
+
+ def original_exception(exception)
+ if registered_original_exception?(exception)
+ exception.original_exception
+ else
+ exception
+ end
+ end
+
+ def registered_original_exception?(exception)
+ exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
+ end
+
+ def clean_backtrace(*args)
+ if backtrace_cleaner
+ backtrace_cleaner.clean(@exception.backtrace, *args)
+ else
+ @exception.backtrace
+ end
+ end
+
+ def backtrace_cleaner
+ @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner']
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index e59404ef68..9928b7cc3a 100644
--- a/actionpack/lib/action_dispatch/middleware/flash.rb
+++ b/actionpack/lib/action_dispatch/middleware/flash.rb
@@ -1,23 +1,23 @@
module ActionDispatch
- class Request
+ class Request < Rack::Request
# Access the contents of the flash. Use <tt>flash["notice"]</tt> to
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
# to put a new one.
def flash
- @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new)
+ @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new).tap(&:sweep)
end
end
# The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
# to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
# action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
- # then expose the flash to its template. Actually, that exposure is automatically done. Example:
+ # then expose the flash to its template. Actually, that exposure is automatically done.
#
# class PostsController < ActionController::Base
# def create
# # save post
# flash[:notice] = "Post successfully created"
- # redirect_to posts_path(@post)
+ # redirect_to @post
# end
#
# def show
@@ -78,8 +78,7 @@ module ActionDispatch
include Enumerable
def initialize #:nodoc:
- @used = Set.new
- @closed = false
+ @discard = Set.new
@flashes = {}
@now = nil
end
@@ -93,8 +92,7 @@ module ActionDispatch
end
def []=(k, v) #:nodoc:
- raise ClosedError, :flash if closed?
- keep(k)
+ @discard.delete k
@flashes[k] = v
end
@@ -103,7 +101,7 @@ module ActionDispatch
end
def update(h) #:nodoc:
- h.keys.each { |k| keep(k) }
+ @discard.subtract h.keys
@flashes.update h
self
end
@@ -117,6 +115,7 @@ module ActionDispatch
end
def delete(key)
+ @discard.delete key
@flashes.delete key
self
end
@@ -130,6 +129,7 @@ module ActionDispatch
end
def clear
+ @discard.clear
@flashes.clear
end
@@ -140,7 +140,7 @@ module ActionDispatch
alias :merge! :update
def replace(h) #:nodoc:
- @used = Set.new
+ @discard.clear
@flashes.replace h
self
end
@@ -159,16 +159,13 @@ module ActionDispatch
@now ||= FlashNow.new(self)
end
- attr_reader :closed
- alias :closed? :closed
- def close!; @closed = true; end
-
# Keeps either the entire current flash or a specific flash entry available for the next action:
#
# flash.keep # keeps the entire flash
# flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
def keep(k = nil)
- use(k, false)
+ @discard.subtract Array(k || keys)
+ k ? self[k] : self
end
# Marks the entire flash or a single flash entry to be discarded by the end of the current action:
@@ -176,24 +173,16 @@ module ActionDispatch
# flash.discard # discard the entire flash at the end of the current action
# flash.discard(:warning) # discard only the "warning" entry at the end of the current action
def discard(k = nil)
- use(k)
+ @discard.merge Array(k || keys)
+ k ? self[k] : self
end
# Mark for removal entries that were kept, and delete unkept ones.
#
# This method is called automatically by filters, so you generally don't need to care about it.
def sweep #:nodoc:
- keys.each do |k|
- unless @used.include?(k)
- @used << k
- else
- delete(k)
- @used.delete(k)
- end
- end
-
- # clean up after keys that could have been left over by calling reject! or shift on the flash
- (@used - keys).each{ |k| @used.delete(k) }
+ @discard.each { |k| @flashes.delete k }
+ @discard.replace @flashes.keys
end
# Convenience accessor for flash[:alert]
@@ -217,22 +206,9 @@ module ActionDispatch
end
protected
-
- def now_is_loaded?
- !!@now
- end
-
- # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
- # use() # marks the entire flash as used
- # use('msg') # marks the "msg" entry as used
- # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
- # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
- # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself
- # if no key is passed.
- def use(key = nil, used = true)
- Array(key || keys).each { |k| used ? @used << k : @used.delete(k) }
- return key ? self[key] : self
- end
+ def now_is_loaded?
+ @now
+ end
end
def initialize(app)
@@ -240,13 +216,9 @@ module ActionDispatch
end
def call(env)
- if (session = env['rack.session']) && (flash = session['flash'])
- flash.sweep
- end
-
@app.call(env)
ensure
- session = env['rack.session'] || {}
+ session = Request::Session.find(env) || {}
flash_hash = env[KEY]
if flash_hash
@@ -258,10 +230,10 @@ module ActionDispatch
end
env[KEY] = new_hash
- new_hash.close!
end
- if session.key?('flash') && session['flash'].empty?
+ if (!session.respond_to?(:loaded?) || session.loaded?) && # (reset_session uses {}, which doesn't implement #loaded?)
+ session.key?('flash') && session['flash'].empty?
session.delete('flash')
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb
index d4208ca96e..1cb803ffb9 100644
--- a/actionpack/lib/action_dispatch/middleware/params_parser.rb
+++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb
@@ -52,14 +52,9 @@ module ActionDispatch
false
end
rescue Exception => e # YAML, XML or Ruby code block errors
- logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
+ logger(env).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}"
- raise
- { "body" => request.raw_post,
- "content_type" => request.content_mime_type,
- "content_length" => request.content_length,
- "exception" => "#{e.message} (#{e.class})",
- "backtrace" => e.backtrace }
+ raise e
end
def content_type_from_legacy_post_data_format_header(env)
@@ -73,8 +68,8 @@ module ActionDispatch
nil
end
- def logger
- defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
+ def logger(env)
+ env['action_dispatch.logger'] || ActiveSupport::Logger.new($stderr)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
new file mode 100644
index 0000000000..85b8d178bf
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -0,0 +1,30 @@
+module ActionDispatch
+ # A simple Rack application that renders exceptions in the given public path.
+ class PublicExceptions
+ attr_accessor :public_path
+
+ def initialize(public_path)
+ @public_path = public_path
+ end
+
+ def call(env)
+ status = env["PATH_INFO"][1..-1]
+ locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
+ path = "#{public_path}/#{status}.html"
+
+ if locale_path && File.exist?(locale_path)
+ render(status, File.read(locale_path))
+ elsif File.exist?(path)
+ render(status, File.read(path))
+ else
+ [404, { "X-Cascade" => "pass" }, []]
+ end
+ end
+
+ private
+
+ def render(status, body)
+ [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb
index 29289a76b4..2f6968eb2e 100644
--- a/actionpack/lib/action_dispatch/middleware/reloader.rb
+++ b/actionpack/lib/action_dispatch/middleware/reloader.rb
@@ -18,10 +18,10 @@ module ActionDispatch
# classes before they are unloaded.
#
# By default, ActionDispatch::Reloader is included in the middleware stack
- # only in the development environment; specifically, when config.cache_classes
+ # only in the development environment; specifically, when +config.cache_classes+
# is false. Callbacks may be registered even when it is not included in the
- # middleware stack, but are executed only when +ActionDispatch::Reloader.prepare!+
- # or +ActionDispatch::Reloader.cleanup!+ are called manually.
+ # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt>
+ # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually.
#
class Reloader
include ActiveSupport::Callbacks
@@ -43,34 +43,47 @@ module ActionDispatch
# Execute all prepare callbacks.
def self.prepare!
- new(nil).run_callbacks :prepare
+ new(nil).prepare!
end
# Execute all cleanup callbacks.
def self.cleanup!
- new(nil).run_callbacks :cleanup
+ new(nil).cleanup!
end
- def initialize(app)
+ def initialize(app, condition=nil)
@app = app
- end
-
- module CleanupOnClose
- def close
- super if defined?(super)
- ensure
- ActionDispatch::Reloader.cleanup!
- end
+ @condition = condition || lambda { true }
+ @validated = true
end
def call(env)
- run_callbacks :prepare
+ @validated = @condition.call
+ prepare!
+
response = @app.call(env)
- response[2].extend(CleanupOnClose)
+ response[2] = ::Rack::BodyProxy.new(response[2]) { cleanup! }
+
response
rescue Exception
- run_callbacks :cleanup
+ cleanup!
raise
end
+
+ def prepare! #:nodoc:
+ run_callbacks :prepare if validated?
+ end
+
+ def cleanup! #:nodoc:
+ run_callbacks :cleanup if validated?
+ ensure
+ @validated = true
+ end
+
+ private
+
+ def validated? #:nodoc:
+ @validated
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
index c7d710b98e..ec15a2a715 100644
--- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb
+++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb
@@ -2,50 +2,128 @@ module ActionDispatch
class RemoteIp
class IpSpoofAttackError < StandardError ; end
- class RemoteIpGetter
- def initialize(env, check_ip_spoofing, trusted_proxies)
- @env = env
- @check_ip_spoofing = check_ip_spoofing
- @trusted_proxies = trusted_proxies
+ # IP addresses that are "trusted proxies" that can be stripped from
+ # the comma-delimited list in the X-Forwarded-For header. See also:
+ # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
+ # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses.
+ TRUSTED_PROXIES = %r{
+ ^127\.0\.0\.1$ | # localhost
+ ^::1$ |
+ ^(10 | # private IP 10.x.x.x
+ 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255
+ 192\.168 | # private IP 192.168.x.x
+ fc00:: # private IP fc00
+ )\.
+ }x
+
+ attr_reader :check_ip, :proxies
+
+ def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
+ @app = app
+ @check_ip = check_ip_spoofing
+ @proxies = case custom_proxies
+ when Regexp
+ custom_proxies
+ when nil
+ TRUSTED_PROXIES
+ else
+ Regexp.union(TRUSTED_PROXIES, custom_proxies)
+ end
+ end
+
+ def call(env)
+ env["action_dispatch.remote_ip"] = GetIp.new(env, self)
+ @app.call(env)
+ end
+
+ class GetIp
+
+ # IP v4 and v6 (with compression) validation regexp
+ # https://gist.github.com/1289635
+ VALID_IP = %r{
+ (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4
+ (^(
+ (([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}) | # ip v6 not abbreviated
+ (([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4}) | # ip v6 with double colon in the end
+ (([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4}) | # - ip addresses v6
+ (([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4}) | # - with
+ (([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4}) | # - double colon
+ (([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4}) | # - in the middle
+ (([0-9A-Fa-f]{1,4}:){6} ((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3} (\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){1,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){1}:([0-9A-Fa-f]{1,4}:){0,4}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,2}:([0-9A-Fa-f]{1,4}:){0,3}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,3}:([0-9A-Fa-f]{1,4}:){0,2}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ (::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
+ ([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
+ (::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the begining
+ (([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
+ )$)
+ }x
+
+ def initialize(env, middleware)
+ @env = env
+ @middleware = middleware
+ @calculated_ip = false
end
- def remote_addrs
- @remote_addrs ||= begin
- list = @env['REMOTE_ADDR'] ? @env['REMOTE_ADDR'].split(/[,\s]+/) : []
- list.reject { |addr| addr =~ @trusted_proxies }
+ # Determines originating IP address. REMOTE_ADDR is the standard
+ # but will be wrong if the user is behind a proxy. Proxies will set
+ # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those.
+ # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of
+ # multiple chained proxies. The first address which is in this list
+ # if it's not a known proxy will be the originating IP.
+ # Format of HTTP_X_FORWARDED_FOR:
+ # client_ip, proxy_ip1, proxy_ip2...
+ # http://en.wikipedia.org/wiki/X-Forwarded-For
+ def calculate_ip
+ client_ip = @env['HTTP_CLIENT_IP']
+ forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first
+ remote_addrs = ips_from('REMOTE_ADDR')
+
+ check_ip = client_ip && @middleware.check_ip
+ if check_ip && forwarded_ip != client_ip
+ # We don't know which came from the proxy, and which from the user
+ raise IpSpoofAttackError, "IP spoofing attack?!" \
+ "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
+ "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
+ end
+
+ client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten
+ if client_ips.present?
+ client_ips.first
+ else
+ # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR
+ remote_addrs.find { |ip| valid_ip? ip }
end
end
def to_s
- return remote_addrs.first if remote_addrs.any?
-
- forwarded_ips = @env['HTTP_X_FORWARDED_FOR'] ? @env['HTTP_X_FORWARDED_FOR'].strip.split(/[,\s]+/) : []
-
- if client_ip = @env['HTTP_CLIENT_IP']
- if @check_ip_spoofing && !forwarded_ips.include?(client_ip)
- # We don't know which came from the proxy, and which from the user
- raise IpSpoofAttackError, "IP spoofing attack?!" \
- "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \
- "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}"
- end
- return client_ip
- end
+ return @ip if @calculated_ip
+ @calculated_ip = true
+ @ip = calculate_ip
+ end
- return forwarded_ips.reject { |ip| ip =~ @trusted_proxies }.last || @env["REMOTE_ADDR"]
+ private
+
+ def ips_from(header)
+ @env[header] ? @env[header].strip.split(/[,\s]+/) : []
end
- end
- def initialize(app, check_ip_spoofing = true, trusted_proxies = nil)
- @app = app
- @check_ip_spoofing = check_ip_spoofing
- regex = '(^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.)'
- regex << "|(#{trusted_proxies})" if trusted_proxies
- @trusted_proxies = Regexp.new(regex, "i")
- end
+ def valid_ip?(ip)
+ ip =~ VALID_IP
+ end
+
+ def not_a_proxy?(ip)
+ ip !~ @middleware.proxies
+ end
+
+ def remove_proxies(ips)
+ ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) }
+ end
- def call(env)
- env["action_dispatch.remote_ip"] = RemoteIpGetter.new(env, @check_ip_spoofing, @trusted_proxies)
- @app.call(env)
end
+
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
new file mode 100644
index 0000000000..6fff94707c
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -0,0 +1,36 @@
+require 'securerandom'
+require 'active_support/core_ext/string/access'
+require 'active_support/core_ext/object/blank'
+
+module ActionDispatch
+ # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
+ # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
+ #
+ # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
+ # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
+ # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
+ #
+ # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
+ # from multiple pieces of the stack.
+ class RequestId
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
+ @app.call(env).tap { |status, headers, body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
+ end
+
+ private
+ def external_request_id(env)
+ if request_id = env["HTTP_X_REQUEST_ID"].presence
+ request_id.gsub(/[^\w\-]/, "").first(255)
+ end
+ end
+
+ def internal_request_id
+ SecureRandom.uuid
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/rescue.rb b/actionpack/lib/action_dispatch/middleware/rescue.rb
deleted file mode 100644
index aee672112c..0000000000
--- a/actionpack/lib/action_dispatch/middleware/rescue.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module ActionDispatch
- class Rescue
- def initialize(app, rescuers = {}, &block)
- @app, @rescuers = app, {}
- rescuers.each { |exception, rescuer| rescue_from(exception, rescuer) }
- instance_eval(&block) if block_given?
- end
-
- def call(env)
- @app.call(env)
- rescue Exception => exception
- if rescuer = @rescuers[exception.class.name]
- env['action_dispatch.rescue.exception'] = exception
- rescuer.call(env)
- else
- raise exception
- end
- end
-
- protected
- def rescue_from(exception, rescuer)
- exception = exception.class.name if exception.is_a?(Exception)
- @rescuers[exception.to_s] = rescuer
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 6bcf099d2c..64159fa8e7 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -2,26 +2,23 @@ require 'rack/utils'
require 'rack/request'
require 'rack/session/abstract/id'
require 'action_dispatch/middleware/cookies'
+require 'action_dispatch/request/session'
require 'active_support/core_ext/object/blank'
module ActionDispatch
module Session
class SessionRestoreError < StandardError #:nodoc:
- end
+ attr_reader :original_exception
+
+ def initialize(const_error)
+ @original_exception = const_error
- module DestroyableSession
- def destroy
- clear
- options = @env[Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY] if @env
- options ||= {}
- @by.send(:destroy_session, @env, options[:id], options) if @by
- options[:id] = nil
- @loaded = false
+ super("Session contains objects whose class definition isn't available.\n" +
+ "Remember to require the classes for all objects kept in the session.\n" +
+ "(Original exception: #{const_error.message} [#{const_error.class}])\n")
end
end
- ::Rack::Session::Abstract::SessionHash.send :include, DestroyableSession
-
module Compatibility
def initialize(app, options = {})
options[:key] ||= '_session_id'
@@ -30,7 +27,7 @@ module ActionDispatch
def generate_sid
sid = SecureRandom.hex(16)
- sid.encode!('UTF-8') if sid.respond_to?(:encode!)
+ sid.encode!('UTF-8')
sid
end
@@ -58,11 +55,8 @@ module ActionDispatch
begin
# Note that the regexp does not allow $1 to end with a ':'
$1.constantize
- rescue LoadError, NameError => const_error
- raise ActionDispatch::Session::SessionRestoreError,
- "Session contains objects whose class definition isn't available.\n" +
- "Remember to require the classes for all objects kept in the session.\n" +
- "(Original exception: #{const_error.message} [#{const_error.class}])\n"
+ rescue LoadError, NameError => e
+ raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace
end
retry
else
@@ -71,12 +65,26 @@ module ActionDispatch
end
end
+ module SessionObject # :nodoc:
+ def prepare_session(env)
+ Request::Session.create(self, env, @default_options)
+ end
+
+ def loaded_session?(session)
+ !session.is_a?(Request::Session) || session.loaded?
+ end
+ end
+
class AbstractStore < Rack::Session::Abstract::ID
include Compatibility
include StaleSessionCheck
+ include SessionObject
+
+ private
- def destroy_session(env, sid, options)
- raise '#destroy_session needs to be implemented.'
+ def set_cookie(env, session_id, cookie)
+ request = ActionDispatch::Request.new(env)
+ request.cookie_jar[key] = cookie
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
new file mode 100644
index 0000000000..1db6194271
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -0,0 +1,49 @@
+require 'action_dispatch/middleware/session/abstract_store'
+
+module ActionDispatch
+ module Session
+ # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
+ # if you don't store critical data in your sessions and you don't need them to live for extended periods
+ # of time.
+ class CacheStore < AbstractStore
+ # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is
+ # not specified, <tt>Rails.cache</tt> will be used.
+ def initialize(app, options = {})
+ @cache = options[:cache] || Rails.cache
+ options[:expire_after] ||= @cache.options[:expires_in]
+ super
+ end
+
+ # Get a session from the cache.
+ def get_session(env, sid)
+ sid ||= generate_sid
+ session = @cache.read(cache_key(sid))
+ session ||= {}
+ [sid, session]
+ end
+
+ # Set a session in the cache.
+ def set_session(env, sid, session, options)
+ key = cache_key(sid)
+ if session
+ @cache.write(key, session, :expires_in => options[:expire_after])
+ else
+ @cache.delete(key)
+ end
+ sid
+ end
+
+ # Remove a session from the cache.
+ def destroy_session(env, sid, options)
+ @cache.delete(cache_key(sid))
+ generate_sid
+ end
+
+ private
+ # Turn the session id into a cache key.
+ def cache_key(sid)
+ "_session_id:#{sid}"
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index 8ebf870b95..7efc094f98 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -27,7 +27,7 @@ module ActionDispatch
# CGI::Session instance as an argument. It's important that the secret
# is not vulnerable to a dictionary attack. Therefore, you should choose
# a secret consisting of random numbers and letters and more than 30
- # characters. Examples:
+ # characters.
#
# :secret => '449fe2e7daee471bffae2fd8dc02313d'
# :secret => Proc.new { User.current_user.secret_key }
@@ -43,6 +43,7 @@ module ActionDispatch
class CookieStore < Rack::Session::Cookie
include Compatibility
include StaleSessionCheck
+ include SessionObject
private
@@ -59,7 +60,8 @@ module ActionDispatch
end
def set_session(env, sid, session_data, options)
- session_data.merge("session_id" => sid)
+ session_data["session_id"] = sid
+ session_data
end
def set_cookie(env, session_id, cookie)
diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
index 4dd9a946c2..38a737cd2b 100644
--- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -6,6 +6,7 @@ module ActionDispatch
class MemCacheStore < Rack::Session::Memcache
include Compatibility
include StaleSessionCheck
+ include SessionObject
def initialize(app, options = {})
require 'memcache'
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 2fa68c64c5..ab740a0190 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -1,172 +1,57 @@
-require 'active_support/core_ext/exception'
-require 'action_controller/metal/exceptions'
-require 'active_support/notifications'
require 'action_dispatch/http/request'
+require 'action_dispatch/middleware/exception_wrapper'
module ActionDispatch
- # This middleware rescues any exception returned by the application and renders
- # nice exception pages if it's being rescued locally.
+ # This middleware rescues any exception returned by the application
+ # and calls an exceptions app that will wrap it in a format for the end user.
+ #
+ # The exceptions app should be passed as parameter on initialization
+ # of ShowExceptions. Everytime there is an exception, ShowExceptions will
+ # store the exception in env["action_dispatch.exception"], rewrite the
+ # PATH_INFO to the exception status code and call the rack app.
+ #
+ # If the application returns a "X-Cascade" pass response, this middleware
+ # will send an empty response as result with the correct status code.
+ # If any exception happens inside the exceptions app, this middleware
+ # catches the exceptions and returns a FAILSAFE_RESPONSE.
class ShowExceptions
- RESCUES_TEMPLATE_PATH = File.join(File.dirname(__FILE__), 'templates')
-
- cattr_accessor :rescue_responses
- @@rescue_responses = Hash.new(:internal_server_error)
- @@rescue_responses.update({
- 'ActionController::RoutingError' => :not_found,
- 'AbstractController::ActionNotFound' => :not_found,
- 'ActiveRecord::RecordNotFound' => :not_found,
- 'ActiveRecord::StaleObjectError' => :conflict,
- 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
- 'ActiveRecord::RecordNotSaved' => :unprocessable_entity,
- 'ActionController::MethodNotAllowed' => :method_not_allowed,
- 'ActionController::NotImplemented' => :not_implemented,
- 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity
- })
-
- cattr_accessor :rescue_templates
- @@rescue_templates = Hash.new('diagnostics')
- @@rescue_templates.update({
- 'ActionView::MissingTemplate' => 'missing_template',
- 'ActionController::RoutingError' => 'routing_error',
- 'AbstractController::ActionNotFound' => 'unknown_action',
- 'ActionView::Template::Error' => 'template_error'
- })
-
FAILSAFE_RESPONSE = [500, {'Content-Type' => 'text/html'},
["<html><body><h1>500 Internal Server Error</h1>" <<
"If you are the administrator of this website, then please read this web " <<
"application's log file and/or the web server's log file to find out what " <<
"went wrong.</body></html>"]]
- def initialize(app, consider_all_requests_local = false)
+ def initialize(app, exceptions_app)
@app = app
- @consider_all_requests_local = consider_all_requests_local
+ @exceptions_app = exceptions_app
end
def call(env)
begin
- status, headers, body = @app.call(env)
- exception = nil
-
- # Only this middleware cares about RoutingError. So, let's just raise
- # it here.
- if headers['X-Cascade'] == 'pass'
- raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}"
- end
+ response = @app.call(env)
rescue Exception => exception
raise exception if env['action_dispatch.show_exceptions'] == false
end
- exception ? render_exception(env, exception) : [status, headers, body]
+ response || render_exception(env, exception)
end
private
- def render_exception(env, exception)
- log_error(exception)
- exception = original_exception(exception)
-
- request = Request.new(env)
- if @consider_all_requests_local || request.local?
- rescue_action_locally(request, exception)
- else
- rescue_action_in_public(exception)
- end
- rescue Exception => failsafe_error
- $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
- FAILSAFE_RESPONSE
- end
-
- # Render detailed diagnostics for unhandled exceptions rescued from
- # a controller action.
- def rescue_action_locally(request, exception)
- template = ActionView::Base.new([RESCUES_TEMPLATE_PATH],
- :request => request,
- :exception => exception,
- :application_trace => application_trace(exception),
- :framework_trace => framework_trace(exception),
- :full_trace => full_trace(exception)
- )
- file = "rescues/#{@@rescue_templates[exception.class.name]}"
- body = template.render(:template => file, :layout => 'rescues/layout')
- render(status_code(exception), body)
- end
- # Attempts to render a static error page based on the
- # <tt>status_code</tt> thrown, or just return headers if no such file
- # exists. At first, it will try to render a localized static page.
- # For example, if a 500 error is being handled Rails and locale is :da,
- # it will first attempt to render the file at <tt>public/500.da.html</tt>
- # then attempt to render <tt>public/500.html</tt>. If none of them exist,
- # the body of the response will be left empty.
- def rescue_action_in_public(exception)
- status = status_code(exception)
- locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
- path = "#{public_path}/#{status}.html"
-
- if locale_path && File.exist?(locale_path)
- render(status, File.read(locale_path))
- elsif File.exist?(path)
- render(status, File.read(path))
- else
- render(status, '')
- end
- end
-
- def status_code(exception)
- Rack::Utils.status_code(@@rescue_responses[exception.class.name])
- end
-
- def render(status, body)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
- end
-
- def public_path
- defined?(Rails.public_path) ? Rails.public_path : 'public_path'
- end
-
- def log_error(exception)
- return unless logger
-
- ActiveSupport::Deprecation.silence do
- message = "\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
- message << " " << application_trace(exception).join("\n ")
- logger.fatal("#{message}\n\n")
- end
- end
-
- def application_trace(exception)
- clean_backtrace(exception, :silent)
- end
-
- def framework_trace(exception)
- clean_backtrace(exception, :noise)
- end
-
- def full_trace(exception)
- clean_backtrace(exception, :all)
- end
-
- def clean_backtrace(exception, *args)
- defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
- Rails.backtrace_cleaner.clean(exception.backtrace, *args) :
- exception.backtrace
- end
-
- def logger
- defined?(Rails.logger) ? Rails.logger : Logger.new($stderr)
- end
-
- def original_exception(exception)
- if registered_original_exception?(exception)
- exception.original_exception
- else
- exception
- end
+ def render_exception(env, exception)
+ wrapper = ExceptionWrapper.new(env, exception)
+ status = wrapper.status_code
+ env["action_dispatch.exception"] = wrapper.exception
+ env["PATH_INFO"] = "/#{status}"
+ response = @exceptions_app.call(env)
+ response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response
+ rescue Exception => failsafe_error
+ $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}"
+ FAILSAFE_RESPONSE
end
- def registered_original_exception?(exception)
- exception.respond_to?(:original_exception) && @@rescue_responses.has_key?(exception.original_exception.class.name)
+ def pass_response(status)
+ [status, {"Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0"}, []]
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb
new file mode 100644
index 0000000000..9098f4e170
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/ssl.rb
@@ -0,0 +1,70 @@
+module ActionDispatch
+ class SSL
+ YEAR = 31536000
+
+ def self.default_hsts_options
+ { :expires => YEAR, :subdomains => false }
+ end
+
+ def initialize(app, options = {})
+ @app = app
+
+ @hsts = options.fetch(:hsts, {})
+ @hsts = {} if @hsts == true
+ @hsts = self.class.default_hsts_options.merge(@hsts) if @hsts
+
+ @host = options[:host]
+ @port = options[:port]
+ end
+
+ def call(env)
+ request = Request.new(env)
+
+ if request.ssl?
+ status, headers, body = @app.call(env)
+ headers = hsts_headers.merge(headers)
+ flag_cookies_as_secure!(headers)
+ [status, headers, body]
+ else
+ redirect_to_https(request)
+ end
+ end
+
+ private
+ def redirect_to_https(request)
+ url = URI(request.url)
+ url.scheme = "https"
+ url.host = @host if @host
+ url.port = @port if @port
+ headers = hsts_headers.merge('Content-Type' => 'text/html',
+ 'Location' => url.to_s)
+
+ [301, headers, []]
+ end
+
+ # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02
+ def hsts_headers
+ if @hsts
+ value = "max-age=#{@hsts[:expires]}"
+ value += "; includeSubDomains" if @hsts[:subdomains]
+ { 'Strict-Transport-Security' => value }
+ else
+ {}
+ end
+ end
+
+ def flag_cookies_as_secure!(headers)
+ if cookies = headers['Set-Cookie']
+ cookies = cookies.split("\n")
+
+ headers['Set-Cookie'] = cookies.map { |cookie|
+ if cookie !~ /;\s+secure(;|$)/
+ "#{cookie}; secure"
+ else
+ cookie
+ end
+ }.join("\n")
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index a4308f528c..bbf734f103 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -75,6 +75,11 @@ module ActionDispatch
middlewares[i]
end
+ def unshift(*args, &block)
+ middleware = self.class::Middleware.new(*args, &block)
+ middlewares.unshift(middleware)
+ end
+
def initialize_copy(other)
self.middlewares = other.middlewares.dup
end
@@ -93,8 +98,9 @@ module ActionDispatch
end
def swap(target, *args, &block)
- insert_before(target, *args, &block)
- delete(target)
+ index = assert_index(target, :before)
+ insert(index, *args, &block)
+ middlewares.delete_at(index + 1)
end
def delete(target)
@@ -109,7 +115,7 @@ module ActionDispatch
def build(app = nil, &block)
app ||= block
raise "MiddlewareStack#build requires an app" unless app
- middlewares.reverse.inject(app) { |a, e| e.build(a) }
+ middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) }
end
protected
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index 404943d720..9073e6582d 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -1,4 +1,5 @@
require 'rack/utils'
+require 'active_support/core_ext/uri'
module ActionDispatch
class FileHandler
@@ -11,14 +12,14 @@ module ActionDispatch
def match?(path)
path = path.dup
- full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path))
+ full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
paths = "#{full_path}#{ext}"
matches = Dir[paths]
match = matches.detect { |m| File.file?(m) }
if match
match.sub!(@compiled_root, '')
- match
+ ::Rack::Utils.escape(match)
end
end
@@ -32,6 +33,15 @@ module ActionDispatch
"{,#{ext},/index#{ext}}"
end
end
+
+ def unescape_path(path)
+ URI.parser.unescape(path)
+ end
+
+ def escape_glob_chars(path)
+ path.force_encoding('binary') if path.respond_to? :force_encoding
+ path.gsub(/[*?{}\[\]]/, "\\\\\\&")
+ end
end
class Static
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
index 0c5bafa666..823f5d25b6 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb
@@ -12,8 +12,8 @@
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
- def debug_hash(hash)
- hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
+ def debug_hash(object)
+ object.to_hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
end unless self.class.method_defined?(:debug_hash)
%>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
index 6e71fd7ddc..1a308707d1 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb
@@ -16,6 +16,7 @@
background-color: #eee;
padding: 10px;
font-size: 11px;
+ white-space: pre-wrap;
}
a { color: #000; }
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
index ccfa858cce..177d383e94 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb
@@ -1,10 +1,17 @@
<h1>Routing Error</h1>
<p><pre><%=h @exception.message %></pre></p>
-<% unless @exception.failures.empty? %><p>
- <h2>Failure reasons:</h2>
- <ol>
- <% @exception.failures.each do |route, reason| %>
- <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
- <% end %>
- </ol>
-</p><% end %>
+<% unless @exception.failures.empty? %>
+ <p>
+ <h2>Failure reasons:</h2>
+ <ol>
+ <% @exception.failures.each do |route, reason| %>
+ <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li>
+ <% end %>
+ </ol>
+ </p>
+<% end %>
+<p>
+ Try running <code>rake routes</code> for more information on available routes.
+</p>
+
+<%= render :template => "rescues/_trace" %>
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 1af89858d1..62f906219c 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -9,11 +9,28 @@ module ActionDispatch
config.action_dispatch.best_standards_support = true
config.action_dispatch.tld_length = 1
config.action_dispatch.ignore_accept_header = false
- config.action_dispatch.rack_cache = {:metastore => "rails:/", :entitystore => "rails:/", :verbose => true}
+ config.action_dispatch.rescue_templates = { }
+ config.action_dispatch.rescue_responses = { }
+ config.action_dispatch.default_charset = nil
+
+ config.action_dispatch.rack_cache = {
+ :metastore => "rails:/",
+ :entitystore => "rails:/",
+ :verbose => false
+ }
initializer "action_dispatch.configure" do |app|
ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
+ ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
+
+ ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses)
+ ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates)
+
+ config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?
+ ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie
+
+ ActionDispatch.test_app = app
end
end
end
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
new file mode 100644
index 0000000000..4ad7071820
--- /dev/null
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -0,0 +1,166 @@
+require 'rack/session/abstract/id'
+
+module ActionDispatch
+ class Request < Rack::Request
+ # SessionHash is responsible to lazily load the session from store.
+ class Session # :nodoc:
+ ENV_SESSION_KEY = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc:
+ ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc:
+
+ def self.create(store, env, default_options)
+ session_was = find env
+ session = Request::Session.new(store, env)
+ session.merge! session_was if session_was
+
+ set(env, session)
+ Options.set(env, Request::Session::Options.new(store, env, default_options))
+ session
+ end
+
+ def self.find(env)
+ env[ENV_SESSION_KEY]
+ end
+
+ def self.set(env, session)
+ env[ENV_SESSION_KEY] = session
+ end
+
+ class Options #:nodoc:
+ def self.set(env, options)
+ env[ENV_SESSION_OPTIONS_KEY] = options
+ end
+
+ def self.find(env)
+ env[ENV_SESSION_OPTIONS_KEY]
+ end
+
+ def initialize(by, env, default_options)
+ @by = by
+ @env = env
+ @delegate = default_options.dup
+ end
+
+ def [](key)
+ if key == :id
+ @delegate.fetch(key) {
+ @delegate[:id] = @by.send(:extract_session_id, @env)
+ }
+ else
+ @delegate[key]
+ end
+ end
+
+ def []=(k,v); @delegate[k] = v; end
+ def to_hash; @delegate.dup; end
+ def values_at(*args); @delegate.values_at(*args); end
+ end
+
+ def initialize(by, env)
+ @by = by
+ @env = env
+ @delegate = {}
+ @loaded = false
+ @exists = nil # we haven't checked yet
+ end
+
+ def options
+ Options.find @env
+ end
+
+ def destroy
+ clear
+ options = self.options || {}
+ @by.send(:destroy_session, @env, options[:id], options)
+ options[:id] = nil
+ @loaded = false
+ end
+
+ def [](key)
+ load_for_read!
+ @delegate[key.to_s]
+ end
+
+ def has_key?(key)
+ load_for_read!
+ @delegate.key?(key.to_s)
+ end
+ alias :key? :has_key?
+ alias :include? :has_key?
+
+ def []=(key, value)
+ load_for_write!
+ @delegate[key.to_s] = value
+ end
+
+ def clear
+ load_for_write!
+ @delegate.clear
+ end
+
+ def to_hash
+ load_for_read!
+ @delegate.dup.delete_if { |_,v| v.nil? }
+ end
+
+ def update(hash)
+ load_for_write!
+ @delegate.update stringify_keys(hash)
+ end
+
+ def delete(key)
+ load_for_write!
+ @delegate.delete key.to_s
+ end
+
+ def inspect
+ if loaded?
+ super
+ else
+ "#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>"
+ end
+ end
+
+ def exists?
+ return @exists unless @exists.nil?
+ @exists = @by.send(:session_exists?, @env)
+ end
+
+ def loaded?
+ @loaded
+ end
+
+ def empty?
+ load_for_read!
+ @delegate.empty?
+ end
+
+ def merge!(other)
+ load_for_write!
+ @delegate.merge!(other)
+ end
+
+ private
+
+ def load_for_read!
+ load! if !loaded? && exists?
+ end
+
+ def load_for_write!
+ load! unless loaded?
+ end
+
+ def load!
+ id, session = @by.load_session @env
+ options[:id] = id
+ @delegate.replace(stringify_keys(session))
+ @loaded = true
+ end
+
+ def stringify_keys(other)
+ other.each_with_object({}) { |(key, value), hash|
+ hash[key.to_s] = value
+ }
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 1dcd83ceb5..38a0270151 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -182,15 +182,18 @@ module ActionDispatch
#
# == HTTP Methods
#
- # Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method.
- # Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>.
- # If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>.
- # The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods.
+ # Using the <tt>:via</tt> option when specifying a route allows you to
+ # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
+ # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
+ # <tt>:any</tt>. If your route needs to respond to more than one method you
+ # can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
+ # <tt>:any</tt> which means that the route will respond to any of the HTTP
+ # methods.
#
# Examples:
#
# match 'post/:id' => 'posts#show', :via => :get
- # match 'post/:id' => "posts#create_comment', :via => :post
+ # match 'post/:id' => 'posts#create_comment', :via => :post
#
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
# URL will route to the <tt>show</tt> action.
@@ -198,12 +201,12 @@ module ActionDispatch
# === HTTP helper methods
#
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
- # methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
+ # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
#
# Examples:
#
# get 'post/:id' => 'posts#show'
- # post 'post/:id' => "posts#create_comment'
+ # post 'post/:id' => 'posts#create_comment'
#
# This syntax is less verbose and the intention is more apparent to someone else reading your code,
# however if your route needs to respond to more than one HTTP method (or all methods) then using the
@@ -277,18 +280,12 @@ module ActionDispatch
#
module Routing
autoload :Mapper, 'action_dispatch/routing/mapper'
- autoload :Route, 'action_dispatch/routing/route'
autoload :RouteSet, 'action_dispatch/routing/route_set'
autoload :RoutesProxy, 'action_dispatch/routing/routes_proxy'
autoload :UrlFor, 'action_dispatch/routing/url_for'
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
SEPARATORS = %w( / . ? ) #:nodoc:
- HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
-
- # A helper module to hold URL related helpers.
- module Helpers #:nodoc:
- include PolymorphicRoutes
- end
+ HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index ef31d1e004..67a208263b 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/reverse_merge'
+require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/object/inclusion'
+require 'active_support/core_ext/enumerable'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'
@@ -16,7 +18,7 @@ module ActionDispatch
end
end
- attr_reader :app
+ attr_reader :app, :constraints
def initialize(app, constraints, request)
@app, @constraints, @request = app, constraints, request
@@ -34,6 +36,8 @@ module ActionDispatch
}
return true
+ ensure
+ req.reset_parameters
end
def call(env)
@@ -54,9 +58,20 @@ module ActionDispatch
def initialize(set, scope, path, options)
@set, @scope = set, scope
+ @segment_keys = nil
@options = (@scope[:options] || {}).merge(options)
@path = normalize_path(path)
normalize_options!
+
+ via_all = @options.delete(:via) if @options[:via] == :all
+
+ if !via_all && request_method_condition.empty?
+ msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
+ "If you want to expose your action to GET, use `get` in the router:\n\n" \
+ " Instead of: match \"controller#action\"\n" \
+ " Do: get \"controller#action\""
+ raise msg
+ end
end
def to_route
@@ -86,6 +101,10 @@ module ActionDispatch
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
end
end
+
+ if @options[:constraints].is_a?(Hash)
+ (@options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(@options[:constraints]))
+ end
end
# match "account/overview"
@@ -213,7 +232,9 @@ module ActionDispatch
end
def segment_keys
- @segment_keys ||= Journey::Path::Pattern.new(
+ return @segment_keys if @segment_keys
+
+ @segment_keys = Journey::Path::Pattern.new(
Journey::Router::Strexp.compile(@path, requirements, SEPARATORS)
).names
end
@@ -229,6 +250,11 @@ module ActionDispatch
def default_action
@options[:action] || @scope[:action]
end
+
+ def defaults_from_constraints(constraints)
+ url_keys = [:protocol, :subdomain, :domain, :host, :port]
+ constraints.slice(*url_keys).select{ |k, v| v.is_a?(String) || v.is_a?(Fixnum) }
+ end
end
# Invokes Rack::Mount::Utils.normalize path and ensure that
@@ -241,7 +267,7 @@ module ActionDispatch
end
def self.normalize_name(name)
- normalize_path(name)[1..-1].gsub("/", "_")
+ normalize_path(name)[1..-1].tr("/", "_")
end
module Base
@@ -251,11 +277,16 @@ module ActionDispatch
#
# For options, see +match+, as +root+ uses it internally.
#
+ # You can also pass a string which will expand
+ #
+ # root 'pages#main'
+ #
# You should put the root route at the top of <tt>config/routes.rb</tt>,
# because this means it will be matched first. As this is the most popular route
# of most Rails applications, this is beneficial.
def root(options = {})
- match '/', { :as => :root }.merge(options)
+ options = { :to => options } if options.is_a?(String)
+ match '/', { :as => :root, :via => :get }.merge(options)
end
# Matches a url pattern to one or more routes. Any symbols in a pattern
@@ -285,7 +316,7 @@ module ActionDispatch
# A pattern can also point to a +Rack+ endpoint i.e. anything that
# responds to +call+:
#
- # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon" }
+ # match 'photos/:id' => lambda {|hash| [200, {}, "Coming soon"] }
# match 'photos/:id' => PhotoRackApp
# # Yes, controller actions are just rack endpoints
# match 'photos/:id' => PhotosController.action(:show)
@@ -325,7 +356,7 @@ module ActionDispatch
# +call+ or a string representing a controller's action.
#
# match 'path', :to => 'controller#action'
- # match 'path', :to => lambda { [200, {}, "Success!"] }
+ # match 'path', :to => lambda { |env| [200, {}, "Success!"] }
# match 'path', :to => RackApp
#
# [:on]
@@ -374,10 +405,6 @@ module ActionDispatch
# # Matches any request starting with 'path'
# match 'path' => 'c#a', :anchor => false
def match(path, options=nil)
- mapping = Mapping.new(@set, @scope, path, options || {})
- app, conditions, requirements, defaults, as, anchor = mapping.to_route
- @set.add_route(app, conditions, requirements, defaults, as, anchor)
- self
end
# Mount a Rack-based application to be used within the application.
@@ -412,7 +439,7 @@ module ActionDispatch
options[:as] ||= app_name(app)
- match(path, options.merge(:to => app, :anchor => false, :format => false))
+ match(path, options.merge(:to => app, :anchor => false, :format => false, :via => :all))
define_generate_prefix(app, options[:as])
self
@@ -437,7 +464,7 @@ module ActionDispatch
app.railtie_name
else
class_name = app.class.is_a?(Class) ? app.name : app.class.name
- ActiveSupport::Inflector.underscore(class_name).gsub("/", "_")
+ ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
end
end
@@ -447,7 +474,11 @@ module ActionDispatch
_route = @set.named_routes.routes[name.to_sym]
_routes = @set
app.routes.define_mounted_helper(name)
- app.routes.class_eval do
+ app.routes.singleton_class.class_eval do
+ define_method :mounted? do
+ true
+ end
+
define_method :_generate_prefix do |options|
prefix_options = options.slice(*_route.segment_keys)
# we must actually delete prefix segment keys to avoid passing them to next url_for
@@ -464,49 +495,49 @@ module ActionDispatch
# Define a route that only recognizes HTTP GET.
# For supported arguments, see <tt>Base#match</tt>.
#
- # Example:
- #
- # get 'bacon', :to => 'food#bacon'
+ # get 'bacon', :to => 'food#bacon'
def get(*args, &block)
- map_method(:get, *args, &block)
+ map_method(:get, args, &block)
end
# Define a route that only recognizes HTTP POST.
# For supported arguments, see <tt>Base#match</tt>.
#
- # Example:
- #
- # post 'bacon', :to => 'food#bacon'
+ # post 'bacon', :to => 'food#bacon'
def post(*args, &block)
- map_method(:post, *args, &block)
+ map_method(:post, args, &block)
end
- # Define a route that only recognizes HTTP PUT.
+ # Define a route that only recognizes HTTP PATCH.
# For supported arguments, see <tt>Base#match</tt>.
#
- # Example:
- #
- # put 'bacon', :to => 'food#bacon'
- def put(*args, &block)
- map_method(:put, *args, &block)
+ # patch 'bacon', :to => 'food#bacon'
+ def patch(*args, &block)
+ map_method(:patch, args, &block)
end
# Define a route that only recognizes HTTP PUT.
# For supported arguments, see <tt>Base#match</tt>.
#
- # Example:
+ # put 'bacon', :to => 'food#bacon'
+ def put(*args, &block)
+ map_method(:put, args, &block)
+ end
+
+ # Define a route that only recognizes HTTP DELETE.
+ # For supported arguments, see <tt>Base#match</tt>.
#
- # delete 'broccoli', :to => 'food#broccoli'
+ # delete 'broccoli', :to => 'food#broccoli'
def delete(*args, &block)
- map_method(:delete, *args, &block)
+ map_method(:delete, args, &block)
end
private
- def map_method(method, *args, &block)
+ def map_method(method, args, &block)
options = args.extract_options!
- options[:via] = method
- args.push(options)
- match(*args, &block)
+ options[:via] = method
+ options[:path] ||= args.first if args.first.is_a?(String)
+ match(*args, options, &block)
self
end
end
@@ -524,13 +555,13 @@ module ActionDispatch
# This will create a number of routes for each of the posts and comments
# controller. For <tt>Admin::PostsController</tt>, Rails will create:
#
- # GET /admin/posts
- # GET /admin/posts/new
- # POST /admin/posts
- # GET /admin/posts/1
- # GET /admin/posts/1/edit
- # PUT /admin/posts/1
- # DELETE /admin/posts/1
+ # GET /admin/posts
+ # GET /admin/posts/new
+ # POST /admin/posts
+ # GET /admin/posts/1
+ # GET /admin/posts/1/edit
+ # PATCH/PUT /admin/posts/1
+ # DELETE /admin/posts/1
#
# If you want to route /posts (without the prefix /admin) to
# <tt>Admin::PostsController</tt>, you could use
@@ -558,13 +589,13 @@ module ActionDispatch
# not use scope. In the last case, the following paths map to
# +PostsController+:
#
- # GET /admin/posts
- # GET /admin/posts/new
- # POST /admin/posts
- # GET /admin/posts/1
- # GET /admin/posts/1/edit
- # PUT /admin/posts/1
- # DELETE /admin/posts/1
+ # GET /admin/posts
+ # GET /admin/posts/new
+ # POST /admin/posts
+ # GET /admin/posts/1
+ # GET /admin/posts/1/edit
+ # PATCH/PUT /admin/posts/1
+ # DELETE /admin/posts/1
module Scoping
# Scopes a set of routes to the given default options.
#
@@ -610,6 +641,10 @@ module ActionDispatch
block, options[:constraints] = options[:constraints], {}
end
+ if options[:constraints].is_a?(Hash)
+ (options[:defaults] ||= {}).reverse_merge!(defaults_from_constraints(options[:constraints]))
+ end
+
scope_options.each do |option|
if value = options.delete(option)
recover[option] = @scope[option]
@@ -636,7 +671,6 @@ module ActionDispatch
# Scopes routes to a specific controller
#
- # Example:
# controller "food" do
# match "bacon", :action => "bacon"
# end
@@ -653,13 +687,13 @@ module ActionDispatch
#
# This generates the following routes:
#
- # admin_posts GET /admin/posts(.:format) admin/posts#index
- # admin_posts POST /admin/posts(.:format) admin/posts#create
- # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
- # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
- # admin_post GET /admin/posts/:id(.:format) admin/posts#show
- # admin_post PUT /admin/posts/:id(.:format) admin/posts#update
- # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
+ # admin_posts GET /admin/posts(.:format) admin/posts#index
+ # admin_posts POST /admin/posts(.:format) admin/posts#create
+ # new_admin_post GET /admin/posts/new(.:format) admin/posts#new
+ # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
+ # admin_post GET /admin/posts/:id(.:format) admin/posts#show
+ # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
+ # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
#
# === Options
#
@@ -696,7 +730,7 @@ module ActionDispatch
# Allows you to constrain the nested routes based on a set of rules.
# For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
#
- # constraints(:id => /\d+\.\d+) do
+ # constraints(:id => /\d+\.\d+/) do
# resources :posts
# end
#
@@ -706,7 +740,7 @@ module ActionDispatch
# You may use this to also restrict other parameters:
#
# resources :posts do
- # constraints(:post_id => /\d+\.\d+) do
+ # constraints(:post_id => /\d+\.\d+/) do
# resources :comments
# end
# end
@@ -735,7 +769,7 @@ module ActionDispatch
# if the user should be given access to that route, or +false+ if the user should not.
#
# class Iphone
- # def self.matches(request)
+ # def self.matches?(request)
# request.env["HTTP_USER_AGENT"] =~ /iPhone/
# end
# end
@@ -818,6 +852,11 @@ module ActionDispatch
def override_keys(child) #:nodoc:
child.key?(:only) || child.key?(:except) ? [:only, :except] : []
end
+
+ def defaults_from_constraints(constraints)
+ url_keys = [:protocol, :subdomain, :domain, :host, :port]
+ constraints.slice(*url_keys).select{ |k, v| v.is_a?(String) || v.is_a?(Fixnum) }
+ end
end
# Resource routing allows you to quickly declare all of the common routes
@@ -863,24 +902,23 @@ module ActionDispatch
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
# a path appended since they fit properly in their scope level.
VALID_ON_OPTIONS = [:new, :collection, :member]
- RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
+ RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param]
CANONICAL_ACTIONS = %w(index create new show update destroy)
class Resource #:nodoc:
- DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit]
-
- attr_reader :controller, :path, :options
+ attr_reader :controller, :path, :options, :param
def initialize(entities, options = {})
@name = entities.to_s
@path = (options[:path] || @name).to_s
@controller = (options[:controller] || @name).to_s
@as = options[:as]
+ @param = options[:param] || :id
@options = options
end
def default_actions
- self.class::DEFAULT_ACTIONS
+ [:index, :create, :new, :show, :update, :destroy, :edit]
end
def actions
@@ -920,7 +958,7 @@ module ActionDispatch
alias :collection_scope :path
def member_scope
- "#{path}/:id"
+ "#{path}/:#{param}"
end
def new_scope(new_path)
@@ -928,22 +966,23 @@ module ActionDispatch
end
def nested_scope
- "#{path}/:#{singular}_id"
+ "#{path}/:#{singular}_#{param}"
end
end
class SingletonResource < Resource #:nodoc:
- DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit]
-
def initialize(entities, options)
super
-
@as = nil
@controller = (options[:controller] || plural).to_s
@as = options[:as]
end
+ def default_actions
+ [:show, :create, :update, :destroy, :new, :edit]
+ end
+
def plural
@plural ||= name.to_s.pluralize
end
@@ -975,12 +1014,12 @@ module ActionDispatch
# the +GeoCoders+ controller (note that the controller is named after
# the plural):
#
- # GET /geocoder/new
- # POST /geocoder
- # GET /geocoder
- # GET /geocoder/edit
- # PUT /geocoder
- # DELETE /geocoder
+ # GET /geocoder/new
+ # POST /geocoder
+ # GET /geocoder
+ # GET /geocoder/edit
+ # PATCH/PUT /geocoder
+ # DELETE /geocoder
#
# === Options
# Takes same options as +resources+.
@@ -991,7 +1030,7 @@ module ActionDispatch
return self
end
- resource_scope(SingletonResource.new(resources.pop, options)) do
+ resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
yield if block_given?
collection do
@@ -1003,9 +1042,12 @@ module ActionDispatch
end if parent_resource.actions.include?(:new)
member do
- get :edit if parent_resource.actions.include?(:edit)
- get :show if parent_resource.actions.include?(:show)
- put :update if parent_resource.actions.include?(:update)
+ get :edit if parent_resource.actions.include?(:edit)
+ get :show if parent_resource.actions.include?(:show)
+ if parent_resource.actions.include?(:update)
+ patch :update
+ put :update
+ end
delete :destroy if parent_resource.actions.include?(:destroy)
end
end
@@ -1023,12 +1065,13 @@ module ActionDispatch
# creates seven different routes in your application, all mapping to
# the +Photos+ controller:
#
- # GET /photos/new
- # POST /photos
- # GET /photos/:id
- # GET /photos/:id/edit
- # PUT /photos/:id
- # DELETE /photos/:id
+ # GET /photos
+ # GET /photos/new
+ # POST /photos
+ # GET /photos/:id
+ # GET /photos/:id/edit
+ # PATCH/PUT /photos/:id
+ # DELETE /photos/:id
#
# Resources can also be nested infinitely by using this block syntax:
#
@@ -1038,24 +1081,32 @@ module ActionDispatch
#
# This generates the following comments routes:
#
- # GET /photos/:photo_id/comments/new
- # POST /photos/:photo_id/comments
- # GET /photos/:photo_id/comments/:id
- # GET /photos/:photo_id/comments/:id/edit
- # PUT /photos/:photo_id/comments/:id
- # DELETE /photos/:photo_id/comments/:id
+ # GET /photos/:photo_id/comments
+ # GET /photos/:photo_id/comments/new
+ # POST /photos/:photo_id/comments
+ # GET /photos/:photo_id/comments/:id
+ # GET /photos/:photo_id/comments/:id/edit
+ # PATCH/PUT /photos/:photo_id/comments/:id
+ # DELETE /photos/:photo_id/comments/:id
#
# === Options
# Takes same options as <tt>Base#match</tt> as well as:
#
# [:path_names]
- # Allows you to change the paths of the seven default actions.
- # Paths not specified are not changed.
+ # Allows you to change the segment component of the +edit+ and +new+ actions.
+ # Actions not specified are not changed.
#
# resources :posts, :path_names => { :new => "brand_new" }
#
# The above example will now change /posts/new to /posts/brand_new
#
+ # [:path]
+ # Allows you to change the path prefix for the resource.
+ #
+ # resources :posts, :path => 'postings'
+ #
+ # The resource and all segments will now route to /postings instead of /posts
+ #
# [:only]
# Only generate routes for the given actions.
#
@@ -1098,13 +1149,32 @@ module ActionDispatch
#
# The +comments+ resource here will have the following routes generated for it:
#
- # post_comments GET /posts/:post_id/comments(.:format)
- # post_comments POST /posts/:post_id/comments(.:format)
- # new_post_comment GET /posts/:post_id/comments/new(.:format)
- # edit_comment GET /sekret/comments/:id/edit(.:format)
- # comment GET /sekret/comments/:id(.:format)
- # comment PUT /sekret/comments/:id(.:format)
- # comment DELETE /sekret/comments/:id(.:format)
+ # post_comments GET /posts/:post_id/comments(.:format)
+ # post_comments POST /posts/:post_id/comments(.:format)
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
+ # edit_comment GET /sekret/comments/:id/edit(.:format)
+ # comment GET /sekret/comments/:id(.:format)
+ # comment PATCH/PUT /sekret/comments/:id(.:format)
+ # comment DELETE /sekret/comments/:id(.:format)
+ #
+ # [:shallow_prefix]
+ # Prefixes nested shallow route names with specified prefix.
+ #
+ # scope :shallow_prefix => "sekret" do
+ # resources :posts do
+ # resources :comments, :shallow => true
+ # end
+ # end
+ #
+ # The +comments+ resource here will have the following routes generated for it:
+ #
+ # post_comments GET /posts/:post_id/comments(.:format)
+ # post_comments POST /posts/:post_id/comments(.:format)
+ # new_post_comment GET /posts/:post_id/comments/new(.:format)
+ # edit_sekret_comment GET /comments/:id/edit(.:format)
+ # sekret_comment GET /comments/:id(.:format)
+ # sekret_comment PATCH/PUT /comments/:id(.:format)
+ # sekret_comment DELETE /comments/:id(.:format)
#
# === Examples
#
@@ -1120,7 +1190,7 @@ module ActionDispatch
return self
end
- resource_scope(Resource.new(resources.pop, options)) do
+ resource_scope(:resources, Resource.new(resources.pop, options)) do
yield if block_given?
collection do
@@ -1133,9 +1203,12 @@ module ActionDispatch
end if parent_resource.actions.include?(:new)
member do
- get :edit if parent_resource.actions.include?(:edit)
- get :show if parent_resource.actions.include?(:show)
- put :update if parent_resource.actions.include?(:update)
+ get :edit if parent_resource.actions.include?(:edit)
+ get :show if parent_resource.actions.include?(:show)
+ if parent_resource.actions.include?(:update)
+ patch :update
+ put :update
+ end
delete :destroy if parent_resource.actions.include?(:destroy)
end
end
@@ -1243,32 +1316,63 @@ module ActionDispatch
parent_resource.instance_of?(Resource) && @scope[:shallow]
end
- def match(*args)
- options = args.extract_options!.dup
- options[:anchor] = true unless options.key?(:anchor)
+ def draw(name)
+ path = @draw_paths.find do |_path|
+ _path.join("#{name}.rb").file?
+ end
- if args.length > 1
- args.each { |path| match(path, options.dup) }
- return self
+ unless path
+ msg = "Your router tried to #draw the external file #{name}.rb,\n" \
+ "but the file was not found in:\n\n"
+ msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
+ raise ArgumentError, msg
+ end
+
+ route_path = path.join("#{name}.rb")
+ instance_eval(route_path.read, route_path.to_s)
+ end
+
+ # match 'path' => 'controller#action'
+ # match 'path', to: 'controller#action'
+ # match 'path', 'otherpath', on: :member, via: :get
+ def match(path, *rest)
+ if rest.empty? && Hash === path
+ options = path
+ path, to = options.find { |name, value| name.is_a?(String) }
+ options[:to] = to
+ options.delete(path)
+ paths = [path]
+ else
+ options = rest.pop || {}
+ paths = [path] + rest
end
- on = options.delete(:on)
- if VALID_ON_OPTIONS.include?(on)
- args.push(options)
- return send(on){ match(*args) }
- elsif on
+ options[:anchor] = true unless options.key?(:anchor)
+
+ if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
- if @scope[:scope_level] == :resources
- args.push(options)
- return nested { match(*args) }
- elsif @scope[:scope_level] == :resource
- args.push(options)
- return member { match(*args) }
+ paths.each { |_path| decomposed_match(_path, options.dup) }
+ self
+ end
+
+ def decomposed_match(path, options) # :nodoc:
+ if on = options.delete(:on)
+ send(on) { decomposed_match(path, options) }
+ else
+ case @scope[:scope_level]
+ when :resources
+ nested { decomposed_match(path, options) }
+ when :resource
+ member { decomposed_match(path, options) }
+ else
+ add_route(path, options)
+ end
end
+ end
- action = args.first
+ def add_route(action, options) # :nodoc:
path = path_for_action(action, options.delete(:path))
if action.to_s =~ /^[\w\/]+$/
@@ -1277,13 +1381,15 @@ module ActionDispatch
action = nil
end
- if options.key?(:as) && !options[:as]
+ if !options.fetch(:as, true)
options.delete(:as)
else
options[:as] = name_for_action(options[:as], action)
end
- super(path, options)
+ mapping = Mapping.new(@set, @scope, path, options)
+ app, conditions, requirements, defaults, as, anchor = mapping.to_route
+ @set.add_route(app, conditions, requirements, defaults, as, anchor)
end
def root(options={})
@@ -1339,7 +1445,7 @@ module ActionDispatch
end
def scope_action_options? #:nodoc:
- @scope[:options].is_a?(Hash) && (@scope[:options][:only] || @scope[:options][:except])
+ @scope[:options] && (@scope[:options][:only] || @scope[:options][:except])
end
def scope_action_options #:nodoc:
@@ -1347,11 +1453,11 @@ module ActionDispatch
end
def resource_scope? #:nodoc:
- @scope[:scope_level].in?([:resource, :resources])
+ [:resource, :resources].include? @scope[:scope_level]
end
def resource_method_scope? #:nodoc:
- @scope[:scope_level].in?([:collection, :member, :new])
+ [:collection, :member, :new].include? @scope[:scope_level]
end
def with_exclusive_scope
@@ -1376,8 +1482,8 @@ module ActionDispatch
@scope[:scope_level_resource] = old_resource
end
- def resource_scope(resource) #:nodoc:
- with_scope_level(resource.is_a?(SingletonResource) ? :resource : :resources, resource) do
+ def resource_scope(kind, resource) #:nodoc:
+ with_scope_level(kind, resource) do
scope(parent_resource.resource_scope) do
yield
end
@@ -1385,10 +1491,12 @@ module ActionDispatch
end
def nested_options #:nodoc:
- {}.tap do |options|
- options[:as] = parent_resource.member_name
- options[:constraints] = { "#{parent_resource.singular}_id".to_sym => id_constraint } if id_constraint?
- end
+ options = { :as => parent_resource.member_name }
+ options[:constraints] = {
+ :"#{parent_resource.singular}_id" => id_constraint
+ } if id_constraint?
+
+ options
end
def id_constraint? #:nodoc:
@@ -1411,7 +1519,7 @@ module ActionDispatch
prefix = shallow_scoping? ?
"#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
- path = if canonical_action?(action, path.blank?)
+ if canonical_action?(action, path.blank?)
prefix.to_s
else
"#{prefix}/#{action_path(action, path)}"
@@ -1419,8 +1527,7 @@ module ActionDispatch
end
def action_path(name, path = nil) #:nodoc:
- # Ruby 1.8 can't transform empty strings to symbols
- name = name.to_sym if name.is_a?(String) && !name.empty?
+ name = name.to_sym if name.is_a?(String)
path || @scope[:path_names][name] || name.to_s
end
@@ -1459,26 +1566,22 @@ module ActionDispatch
[name_prefix, member_name, prefix]
end
- candidate = name.select(&:present?).join("_").presence
- candidate unless as.nil? && @set.routes.find { |r| r.name == candidate }
- end
- end
-
- module Shorthand #:nodoc:
- def match(path, *rest)
- if rest.empty? && Hash === path
- options = path
- path, to = options.find { |name, value| name.is_a?(String) }
- options.merge!(:to => to).delete(path)
- super(path, options)
- else
- super
+ if candidate = name.select(&:present?).join("_").presence
+ # If a name was not explicitly given, we check if it is valid
+ # and return nil in case it isn't. Otherwise, we pass the invalid name
+ # forward so the underlying router engine treats it and raises an exception.
+ if as.nil?
+ candidate unless @set.routes.find { |r| r.name == candidate } || candidate !~ /\A[_a-z]/i
+ else
+ candidate
+ end
+ end
end
- end
end
def initialize(set) #:nodoc:
@set = set
+ @draw_paths = set.draw_paths
@scope = { :path_names => @set.resources_path_names }
end
@@ -1487,7 +1590,6 @@ module ActionDispatch
include Redirection
include Scoping
include Resources
- include Shorthand
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index e989a38d8b..8fde667108 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -43,16 +43,14 @@ module ActionDispatch
# edit_polymorphic_path(@post) # => "/posts/1/edit"
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
#
- # == Using with mounted engines
+ # == Usage with mounted engines
#
- # If you use mounted engine, there is a possibility that you will need to use
- # polymorphic_url pointing at engine's routes. To do that, just pass proxy used
- # to reach engine's routes as a first argument:
+ # If you are using a mounted engine and you need to use a polymorphic_url
+ # pointing at the engine's routes, pass in the engine's route proxy as the first
+ # argument to the method. For example:
#
- # For example:
- #
- # polymorphic_url([blog, @post]) # it will call blog.post_path(@post)
- # form_for([blog, @post]) # => "/blog/posts/1
+ # polymorphic_url([blog, @post]) # calls blog.post_path(@post)
+ # form_for([blog, @post]) # => "/blog/posts/1"
#
module PolymorphicRoutes
# Constructs a call to a named RESTful route for the given record and returns the
@@ -165,7 +163,7 @@ module ActionDispatch
if parent.is_a?(Symbol) || parent.is_a?(String)
parent
else
- ActiveModel::Naming.route_key(parent).singularize
+ ActiveModel::Naming.singular_route_key(parent)
end
end
else
@@ -176,9 +174,11 @@ module ActionDispatch
if record.is_a?(Symbol) || record.is_a?(String)
route << record
elsif record
- route << ActiveModel::Naming.route_key(record)
- route = [route.join("_").singularize] if inflection == :singular
- route << "index" if ActiveModel::Naming.uncountable?(record) && inflection == :plural
+ if inflection == :singular
+ route << ActiveModel::Naming.singular_route_key(record)
+ else
+ route << ActiveModel::Naming.route_key(record)
+ end
else
raise ArgumentError, "Nil location provided. Can't build URI."
end
diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb
index 804991ad5f..b3823bb496 100644
--- a/actionpack/lib/action_dispatch/routing/redirection.rb
+++ b/actionpack/lib/action_dispatch/routing/redirection.rb
@@ -1,7 +1,90 @@
require 'action_dispatch/http/request'
+require 'active_support/core_ext/uri'
+require 'active_support/core_ext/array/extract_options'
+require 'rack/utils'
module ActionDispatch
module Routing
+ class Redirect # :nodoc:
+ attr_reader :status, :block
+
+ def initialize(status, block)
+ @status = status
+ @block = block
+ end
+
+ def call(env)
+ req = Request.new(env)
+
+ uri = URI.parse(path(req.symbolized_path_parameters, req))
+ uri.scheme ||= req.scheme
+ uri.host ||= req.host
+ uri.port ||= req.port unless req.standard_port?
+
+ body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
+
+ headers = {
+ 'Location' => uri.to_s,
+ 'Content-Type' => 'text/html',
+ 'Content-Length' => body.length.to_s
+ }
+
+ [ status, headers, [body] ]
+ end
+
+ def path(params, request)
+ block.call params, request
+ end
+
+ def inspect
+ "redirect(#{status})"
+ end
+ end
+
+ class PathRedirect < Redirect
+ def path(params, request)
+ (params.empty? || !block.match(/%\{\w*\}/)) ? block : (block % escape(params))
+ end
+
+ def inspect
+ "redirect(#{status}, #{block})"
+ end
+
+ private
+ def escape(params)
+ Hash[params.map{ |k,v| [k, Rack::Utils.escape(v)] }]
+ end
+ end
+
+ class OptionRedirect < Redirect # :nodoc:
+ alias :options :block
+
+ def path(params, request)
+ url_options = {
+ :protocol => request.protocol,
+ :host => request.host,
+ :port => request.optional_port,
+ :path => request.path,
+ :params => request.query_parameters
+ }.merge options
+
+ if !params.empty? && url_options[:path].match(/%\{\w*\}/)
+ url_options[:path] = (url_options[:path] % escape_path(params))
+ end
+
+ ActionDispatch::Http::URL.url_for url_options
+ end
+
+ def inspect
+ "redirect(#{status}, #{options.map{ |k,v| "#{k}: #{v}" }.join(', ')})"
+ end
+
+ private
+ def escape_path(params)
+ Hash[params.map{ |k,v| [k, URI.parser.escape(v)] }]
+ end
+ end
+
module Redirection
# Redirect any path to another path:
@@ -19,10 +102,13 @@ module ActionDispatch
# params, depending of how many arguments your block accepts. A string is required as a
# return value.
#
- # match 'jokes/:number', :to => redirect do |params, request|
- # path = (params[:number].to_i.even? ? "/wheres-the-beef" : "/i-love-lamp")
+ # match 'jokes/:number', :to => redirect { |params, request|
+ # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp")
# "http://#{request.host_with_port}/#{path}"
- # end
+ # }
+ #
+ # Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass
+ # the block to +match+ instead of +redirect+. Use <tt>{ ... }</tt> instead.
#
# The options version of redirect allows you to supply only the parts of the url which need
# to change, it also supports interpolation of the path similar to the first example.
@@ -37,74 +123,17 @@ module ActionDispatch
# match 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
#
def redirect(*args, &block)
- options = args.last.is_a?(Hash) ? args.pop : {}
+ options = args.extract_options!
status = options.delete(:status) || 301
+ path = args.shift
- path = args.shift
-
- path_proc = if path.is_a?(String)
- proc { |params| (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params) }
- elsif options.any?
- options_proc(options)
- elsif path.respond_to?(:call)
- proc { |params, request| path.call(params, request) }
- elsif block
- block
- else
- raise ArgumentError, "redirection argument not supported"
- end
+ return OptionRedirect.new(status, options) if options.any?
+ return PathRedirect.new(status, path) if String === path
- redirection_proc(status, path_proc)
+ block = path if path.respond_to? :call
+ raise ArgumentError, "redirection argument not supported" unless block
+ Redirect.new status, block
end
-
- private
-
- def options_proc(options)
- proc do |params, request|
- path = if options[:path].nil?
- request.path
- elsif params.empty? || !options[:path].match(/%\{\w*\}/)
- options.delete(:path)
- else
- (options.delete(:path) % params)
- end
-
- default_options = {
- :protocol => request.protocol,
- :host => request.host,
- :port => request.optional_port,
- :path => path,
- :params => request.query_parameters
- }
-
- ActionDispatch::Http::URL.url_for(options.reverse_merge(default_options))
- end
- end
-
- def redirection_proc(status, path_proc)
- lambda do |env|
- req = Request.new(env)
-
- params = [req.symbolized_path_parameters]
- params << req if path_proc.arity > 1
-
- uri = URI.parse(path_proc.call(*params))
- uri.scheme ||= req.scheme
- uri.host ||= req.host
- uri.port ||= req.port unless req.standard_port?
-
- body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
-
- headers = {
- 'Location' => uri.to_s,
- 'Content-Type' => 'text/html',
- 'Content-Length' => body.length.to_s
- }
-
- [ status, headers, [body] ]
- end
- end
-
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index e7bc431783..0ae668d42a 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -9,6 +9,12 @@ require 'action_controller/metal/exceptions'
module ActionDispatch
module Routing
class RouteSet #:nodoc:
+ # Since the router holds references to many parts of the system
+ # like engines, controllers and the application itself, inspecting
+ # the route set can actually be really slow, therefore we default
+ # alias inspect to to_s.
+ alias inspect to_s
+
PARAMETERS_KEY = 'action_dispatch.request.path_parameters'
class Dispatcher #:nodoc:
@@ -31,13 +37,14 @@ module ActionDispatch
end
def prepare_params!(params)
+ normalize_controller!(params)
merge_default_action!(params)
split_glob_param!(params) if @glob_param
end
# If this is a default_controller (i.e. a controller specified by the user)
# we should raise an error in case it's not found, because it usually means
- # an user error. However, if the controller was retrieved through a dynamic
+ # a user error. However, if the controller was retrieved through a dynamic
# segment, as in :controller(/:action), we should simply return nil and
# delegate the control back to Rack cascade. Besides, if this is not a default
# controller, it means we should respect the @scope[:module] parameter.
@@ -66,6 +73,10 @@ module ActionDispatch
controller.action(action).call(env)
end
+ def normalize_controller!(params)
+ params[:controller] = params[:controller].underscore if params.key?(:controller)
+ end
+
def merge_default_action!(params)
params[:action] ||= 'index'
end
@@ -83,7 +94,27 @@ module ActionDispatch
attr_reader :routes, :helpers, :module
def initialize
- clear!
+ @routes = {}
+ @helpers = []
+ @module = Module.new do
+ protected
+
+ def handle_positional_args(args, options, segment_keys)
+ inner_options = args.extract_options!
+ result = options.dup
+
+ if args.size > 0
+ keys = segment_keys
+ if args.size < keys.size - 1 # take format into account
+ keys -= self.url_options.keys if self.respond_to?(:url_options)
+ keys -= options.keys
+ end
+ result.merge!(Hash[keys.zip(args)])
+ end
+
+ result.merge!(inner_options)
+ end
+ end
end
def helper_names
@@ -91,12 +122,8 @@ module ActionDispatch
end
def clear!
- @routes = {}
- @helpers = []
-
- @module ||= Module.new do
- instance_methods.each { |selector| remove_method(selector) }
- end
+ @routes.clear
+ @helpers.clear
end
def add(name, route)
@@ -125,60 +152,22 @@ module ActionDispatch
routes.length
end
- def reset!
- old_routes = routes.dup
- clear!
- old_routes.each do |name, route|
- add(name, route)
- end
- end
-
- def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
- reset! if regenerate
- Array(destinations).each do |dest|
- dest.__send__(:include, @module)
- end
- end
-
private
- def url_helper_name(name, kind = :url)
- :"#{name}_#{kind}"
- end
-
- def hash_access_name(name, kind = :url)
- :"hash_for_#{name}_#{kind}"
+ def url_helper_name(name, only_path)
+ if only_path
+ :"#{name}_path"
+ else
+ :"#{name}_url"
+ end
end
def define_named_route_methods(name, route)
- {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
- hash = route.defaults.merge(:use_route => name).merge(opts)
- define_hash_access route, name, kind, hash
- define_url_helper route, name, kind, hash
+ [true, false].each do |only_path|
+ hash = route.defaults.merge(:use_route => name, :only_path => only_path)
+ define_url_helper route, name, hash
end
end
- def define_hash_access(route, name, kind, options)
- selector = hash_access_name(name, kind)
-
- # We use module_eval to avoid leaks
- @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
- remove_possible_method :#{selector}
- def #{selector}(*args)
- options = args.extract_options!
- result = #{options.inspect}
-
- if args.any?
- result[:_positional_args] = args
- result[:_positional_keys] = #{route.segment_keys.inspect}
- end
-
- result.merge(options)
- end
- protected :#{selector}
- END_EVAL
- helpers << selector
- end
-
# Create a url helper allowing ordered parameters to be associated
# with corresponding dynamic segments, so you can do:
#
@@ -192,23 +181,54 @@ module ActionDispatch
#
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
- def define_url_helper(route, name, kind, options)
- selector = url_helper_name(name, kind)
- hash_access_method = hash_access_name(name, kind)
+ def define_url_helper(route, name, options)
+ selector = url_helper_name(name, options[:only_path])
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
remove_possible_method :#{selector}
def #{selector}(*args)
- url_for(#{hash_access_method}(*args))
+ if #{optimize_helper?(route)} && args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation?
+ options = #{options.inspect}
+ options.merge!(url_options) if respond_to?(:url_options)
+ options[:path] = "#{optimized_helper(route)}"
+ ActionDispatch::Http::URL.url_for(options)
+ else
+ url_for(handle_positional_args(args, #{options.inspect}, #{route.segment_keys.inspect}))
+ end
end
END_EVAL
+
helpers << selector
end
+
+ # Clause check about when we need to generate an optimized helper.
+ def optimize_helper?(route) #:nodoc:
+ route.requirements.except(:controller, :action).empty?
+ end
+
+ # Generates the interpolation to be used in the optimized helper.
+ def optimized_helper(route)
+ string_route = route.ast.to_s
+
+ while string_route.gsub!(/\([^\)]*\)/, "")
+ true
+ end
+
+ route.required_parts.each_with_index do |part, i|
+ # Replace each route parameter
+ # e.g. :id for regular parameter or *path for globbing
+ # with ruby string interpolation code
+ string_route.gsub!(/(\*|:)#{part}/, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}")
+ end
+
+ string_route
+ end
end
attr_accessor :formatter, :set, :named_routes, :default_scope, :router
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options, :request_class, :valid_conditions
+ attr_accessor :draw_paths
alias :routes :set
@@ -220,6 +240,7 @@ module ActionDispatch
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.default_url_options = {}
+ self.draw_paths = []
self.request_class = request_class
@valid_conditions = {}
@@ -262,8 +283,7 @@ module ActionDispatch
def eval_block(block)
if block.arity == 1
raise "You are using the old router DSL which has been removed in Rails 3.1. " <<
- "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/ " <<
- "or add the rails_legacy_mapper gem to your Gemfile"
+ "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
end
mapper = Mapper.new(self)
if default_scope
@@ -287,14 +307,15 @@ module ActionDispatch
@prepend.each { |blk| eval_block(blk) }
end
- def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
- Array(destinations).each { |d| d.module_eval { include Helpers } }
- named_routes.install(destinations, regenerate_code)
- end
-
- module MountedHelpers
+ module MountedHelpers #:nodoc:
+ extend ActiveSupport::Concern
+ include UrlFor
end
+ # Contains all the mounted helpers accross different
+ # engines and the `main_app` helper for the application.
+ # You can include this in your classes if you want to
+ # access routes for other engines.
def mounted_helpers
MountedHelpers
end
@@ -305,7 +326,7 @@ module ActionDispatch
routes = self
MountedHelpers.class_eval do
define_method "_#{name}" do
- RoutesProxy.new(routes, self._routes_context)
+ RoutesProxy.new(routes, _routes_context)
end
end
@@ -320,28 +341,36 @@ module ActionDispatch
@url_helpers ||= begin
routes = self
- helpers = Module.new do
+ Module.new do
extend ActiveSupport::Concern
include UrlFor
+ # Define url_for in the singleton level so one can do:
+ # Rails.application.routes.url_helpers.url_for(args)
@_routes = routes
class << self
- delegate :url_for, :to => '@_routes'
+ delegate :url_for, :optimize_routes_generation?, :to => '@_routes'
end
+
+ # Make named_routes available in the module singleton
+ # as well, so one can do:
+ # Rails.application.routes.url_helpers.posts_path
extend routes.named_routes.module
- # ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that
- # we can include?
- # Yes plz - JP
+ # Any class that includes this module will get all
+ # named routes...
+ include routes.named_routes.module
+
+ # plus a singleton class method called _routes ...
included do
- routes.install_helpers(self)
singleton_class.send(:redefine_method, :_routes) { routes }
end
+ # And an instance method _routes. Note that
+ # UrlFor (included in this module) add extra
+ # conveniences for working with @_routes.
define_method(:_routes) { @_routes || routes }
end
-
- helpers
end
end
@@ -356,7 +385,7 @@ module ActionDispatch
conditions = build_conditions(conditions, valid_conditions, path.names.map { |x| x.to_sym })
route = @set.add_route(app, path, conditions, defaults, name)
- named_routes[name] = route if name
+ named_routes[name] = route if name && !named_routes[name]
route
end
@@ -367,7 +396,26 @@ module ActionDispatch
SEPARATORS,
anchor)
- Journey::Path::Pattern.new(strexp)
+ pattern = Journey::Path::Pattern.new(strexp)
+
+ builder = Journey::GTG::Builder.new pattern.spec
+
+ # Get all the symbol nodes followed by literals that are not the
+ # dummy node.
+ symbols = pattern.spec.grep(Journey::Nodes::Symbol).find_all { |n|
+ builder.followpos(n).first.literal?
+ }
+
+ # Get all the symbol nodes preceded by literals.
+ symbols.concat pattern.spec.find_all(&:literal?).map { |n|
+ builder.followpos(n).first
+ }.find_all(&:symbol?)
+
+ symbols.each { |x|
+ x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
+ }
+
+ pattern
end
private :build_path
@@ -412,12 +460,12 @@ module ActionDispatch
normalize_options!
normalize_controller_action_id!
use_relative_controller!
- controller.sub!(%r{^/}, '') if controller
+ normalize_controller!
handle_nil_action!
end
def controller
- @controller ||= @options[:controller]
+ @options[:controller]
end
def current_controller
@@ -470,14 +518,19 @@ module ActionDispatch
# if the current controller is "foo/bar/baz" and :controller => "baz/bat"
# is specified, the controller becomes "foo/baz/bat"
def use_relative_controller!
- if !named_route && different_controller?
+ if !named_route && different_controller? && !controller.start_with?("/")
old_parts = current_controller.split('/')
size = controller.count("/") + 1
parts = old_parts[0...-size] << controller
- @controller = @options[:controller] = parts.join("/")
+ @options[:controller] = parts.join("/")
end
end
+ # Remove leading slashes from controllers
+ def normalize_controller!
+ @options[:controller] = controller.sub(%r{^/}, '') if controller
+ end
+
# This handles the case of :action => nil being explicitly passed.
# It is identical to :action => "index"
def handle_nil_action!
@@ -535,30 +588,35 @@ module ActionDispatch
RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
:trailing_slash, :anchor, :params, :only_path, :script_name]
+ def mounted?
+ false
+ end
+
+ def optimize_routes_generation?
+ !mounted? && default_url_options.empty?
+ end
+
def _generate_prefix(options = {})
nil
end
+ # The +options+ argument must be +nil+ or a hash whose keys are *symbols*.
def url_for(options)
- finalize!
- options = (options || {}).reverse_merge!(default_url_options)
-
- handle_positional_args(options)
+ options = default_url_options.merge(options || {})
user, password = extract_authentication(options)
path_segments = options.delete(:_path_segments)
- script_name = options.delete(:script_name)
-
- path = (script_name.blank? ? _generate_prefix(options) : script_name.chomp('/')).to_s
+ script_name = options.delete(:script_name).presence || _generate_prefix(options)
path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?
- path_addition, params = generate(path_options, path_segments || {})
- path << path_addition
+ path, params = generate(path_options, path_segments || {})
+ params.merge!(options[:params] || {})
- ActionDispatch::Http::URL.url_for(options.merge({
+ ActionDispatch::Http::URL.url_for(options.merge!({
:path => path,
+ :script_name => script_name,
:params => params,
:user => user,
:password => password
@@ -566,13 +624,13 @@ module ActionDispatch
end
def call(env)
- finalize!
@router.call(env)
end
def recognize_path(path, environment = {})
method = (environment[:method] || "GET").to_s.upcase
path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://}
+ extras = environment[:extras] || {}
begin
env = Rack::MockRequest.env_for(path, {:method => method})
@@ -582,13 +640,15 @@ module ActionDispatch
req = @request_class.new(env)
@router.recognize(req) do |route, matches, params|
+ params.merge!(extras)
params.each do |key, value|
if value.is_a?(String)
- value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware?
+ value = value.dup.force_encoding(Encoding::BINARY)
params[key] = URI.parser.unescape(value)
end
end
-
+ old_params = env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ env[::ActionDispatch::Routing::RouteSet::PARAMETERS_KEY] = (old_params || {}).merge(params)
dispatcher = route.app
while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
dispatcher = dispatcher.app
@@ -613,16 +673,6 @@ module ActionDispatch
end
end
- def handle_positional_args(options)
- return unless args = options.delete(:_positional_args)
-
- keys = options.delete(:_positional_keys)
- keys -= options.keys if args.size < keys.size - 1 # take format into account
-
- # Tell url_for to skip default_url_options
- options.merge!(Hash[args.zip(keys).map { |v, k| [k, v] }])
- end
-
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/routes_proxy.rb b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
index f7d5f6397d..73af5920ed 100644
--- a/actionpack/lib/action_dispatch/routing/routes_proxy.rb
+++ b/actionpack/lib/action_dispatch/routing/routes_proxy.rb
@@ -16,6 +16,10 @@ module ActionDispatch
end
end
+ def respond_to?(method, include_private = false)
+ super || routes.url_helpers.respond_to?(method)
+ end
+
def method_missing(method, *args)
if routes.url_helpers.respond_to?(method)
self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 8fc8dc191b..fd3bed7e8f 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -8,7 +8,8 @@ module ActionDispatch
#
# <b>Tip:</b> If you need to generate URLs from your models or some other place,
# then ActionController::UrlFor is what you're looking for. Read on for
- # an introduction.
+ # an introduction. In general, this module should not be included on its own,
+ # as it is usually included by url_helpers (as in Rails.application.routes.url_helpers).
#
# == URL generation from parameters
#
@@ -42,7 +43,7 @@ module ActionDispatch
# url_for(:controller => 'users',
# :action => 'new',
# :message => 'Welcome!',
- # :host => 'www.example.com') # Changed this.
+ # :host => 'www.example.com')
# # => "http://www.example.com/users/new?message=Welcome%21"
#
# By default, all controllers and views have access to a special version of url_for,
@@ -52,7 +53,7 @@ module ActionDispatch
#
# For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for.
# So within mailers, you only have to type 'url_for' instead of 'ActionController::UrlFor#url_for'
- # in full. However, mailers don't have hostname information, and what's why you'll still
+ # in full. However, mailers don't have hostname information, and that's why you'll still
# have to specify the <tt>:host</tt> argument when generating URLs in mailers.
#
#
@@ -67,7 +68,7 @@ module ActionDispatch
# This generates, among other things, the method <tt>users_path</tt>. By default,
# this method is accessible from your controllers, views and mailers. If you need
# to access this auto-generated method from other places (such as a model), then
- # you can do that by including ActionController::UrlFor in your class:
+ # you can do that by including Rails.application.routes.url_helpers in your class:
#
# class User < ActiveRecord::Base
# include Rails.application.routes.url_helpers
@@ -84,14 +85,12 @@ module ActionDispatch
include PolymorphicRoutes
included do
- # TODO: with_routing extends @controller with url_helpers, trickling down to including this module which overrides its default_url_options
unless method_defined?(:default_url_options)
# Including in a class uses an inheritable hash. Modules get a plain hash.
if respond_to?(:class_attribute)
class_attribute :default_url_options
else
- mattr_accessor :default_url_options
- remove_method :default_url_options
+ mattr_writer :default_url_options
end
self.default_url_options = {}
@@ -103,6 +102,9 @@ module ActionDispatch
super
end
+ # Hook overriden in controller to add request information
+ # with `default_url_options`. Application logic should not
+ # go into url_options.
def url_options
default_url_options
end
@@ -130,8 +132,6 @@ module ActionDispatch
# Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to
# +url_for+ is forwarded to the Routes module.
#
- # Examples:
- #
# url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080'
# # => 'http://somehost.org:8080/tasks/testing'
# url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true
@@ -142,26 +142,34 @@ module ActionDispatch
# # => 'http://somehost.org/tasks/testing?number=33'
def url_for(options = nil)
case options
+ when nil
+ _routes.url_for(url_options.symbolize_keys)
+ when Hash
+ _routes.url_for(options.symbolize_keys.reverse_merge!(url_options))
when String
options
- when nil, Hash
- _routes.url_for((options || {}).reverse_merge(url_options).symbolize_keys)
else
polymorphic_url(options)
end
end
protected
- def _with_routes(routes)
- old_routes, @_routes = @_routes, routes
- yield
- ensure
- @_routes = old_routes
- end
- def _routes_context
- self
- end
+ def optimize_routes_generation?
+ return @_optimized_routes if defined?(@_optimized_routes)
+ @_optimized_routes = _routes.optimize_routes_generation? && default_url_options.empty?
+ end
+
+ def _with_routes(routes)
+ old_routes, @_routes = @_routes, routes
+ yield
+ ensure
+ @_routes = old_routes
+ end
+
+ def _routes_context
+ self
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
index 47c84742aa..7dc3d0f97c 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb
@@ -5,32 +5,22 @@ module ActionDispatch
module DomAssertions
# \Test two HTML strings for equivalency (e.g., identical up to reordering of attributes)
#
- # ==== Examples
- #
# # assert that the referenced method generates the appropriate HTML string
# assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
- #
def assert_dom_equal(expected, actual, message = "")
expected_dom = HTML::Document.new(expected).root
actual_dom = HTML::Document.new(actual).root
- full_message = build_message(message, "<?> expected to be == to\n<?>.", expected_dom.to_s, actual_dom.to_s)
-
- assert_block(full_message) { expected_dom == actual_dom }
+ assert_equal expected_dom, actual_dom
end
# The negated form of +assert_dom_equivalent+.
#
- # ==== Examples
- #
# # assert that the referenced method does not generate the specified HTML string
# assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
- #
def assert_dom_not_equal(expected, actual, message = "")
expected_dom = HTML::Document.new(expected).root
actual_dom = HTML::Document.new(actual).root
- full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
-
- assert_block(full_message) { expected_dom != actual_dom }
+ refute_equal expected_dom, actual_dom
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index 7381617dd7..3d121b6b9c 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -4,11 +4,9 @@ module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
module ResponseAssertions
- extend ActiveSupport::Concern
-
# Asserts that the response is one of the following types:
#
- # * <tt>:success</tt> - Status code was 200
+ # * <tt>:success</tt> - Status code was in the 200-299 range
# * <tt>:redirect</tt> - Status code was in the 300-399 range
# * <tt>:missing</tt> - Status code was 404
# * <tt>:error</tt> - Status code was in the 500-599 range
@@ -17,25 +15,23 @@ module ActionDispatch
# or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
#
- # ==== Examples
- #
# # assert that the response was a redirection
# assert_response :redirect
#
# # assert that the response code was status code 401 (unauthorized)
# assert_response 401
- #
def assert_response(type, message = nil)
- validate_request!
+ message ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>"
- 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
- elsif type.is_a?(Symbol) && @response.response_code == Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
- assert_block("") { true } # to count the assertion
+ if Symbol === type
+ if [:success, :missing, :redirect, :error].include?(type)
+ assert @response.send("#{type}?"), message
+ else
+ code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
+ assert_equal @response.response_code, code, message
+ end
else
- flunk(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code))
+ assert_equal type, @response.response_code, message
end
end
@@ -43,8 +39,6 @@ module ActionDispatch
# This match can be partial, such that <tt>assert_redirected_to(:controller => "weblog")</tt> will also
# match the redirection of <tt>redirect_to(:controller => "weblog", :action => "show")</tt> and so on.
#
- # ==== Examples
- #
# # assert that the redirection was to the "index" action on the WeblogController
# assert_redirected_to :controller => "weblog", :action => "index"
#
@@ -54,16 +48,17 @@ module ActionDispatch
# # assert that the redirection was to the url for @customer
# assert_redirected_to @customer
#
+ # # asserts that the redirection matches the regular expression
+ # assert_redirected_to %r(\Ahttp://example.org)
def assert_redirected_to(options = {}, message=nil)
assert_response(:redirect, message)
- return true if options == @response.location
+ return true if options === @response.location
redirect_is = normalize_argument_to_redirection(@response.location)
redirect_expected = normalize_argument_to_redirection(options)
- if redirect_is != redirect_expected
- flunk "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
- end
+ message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
+ assert_operator redirect_expected, :===, redirect_is, message
end
private
@@ -73,23 +68,21 @@ module ActionDispatch
end
def normalize_argument_to_redirection(fragment)
- case fragment
- when %r{^\w[A-Za-z\d+.-]*:.*}
- fragment
- when String
- @request.protocol + @request.host_with_port + fragment
- when :back
- raise RedirectBackError unless refer = @request.headers["Referer"]
- refer
- else
- @controller.url_for(fragment)
- end.gsub(/[\r\n]/, '')
- end
+ normalized = case fragment
+ when Regexp
+ fragment
+ when %r{^\w[A-Za-z\d+.-]*:.*}
+ fragment
+ when String
+ @request.protocol + @request.host_with_port + fragment
+ when :back
+ raise RedirectBackError unless refer = @request.headers["Referer"]
+ refer
+ else
+ @controller.url_for(fragment)
+ end
- def validate_request!
- unless @request.is_a?(ActionDispatch::Request)
- raise ArgumentError, "@request must be an ActionDispatch::Request"
- end
+ normalized.respond_to?(:delete) ? normalized.delete("\0\r\n") : normalized
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index b10aab9029..567ca0c392 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -26,7 +26,6 @@ module ActionDispatch
#
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
#
- # ==== Examples
# # Check the default route (i.e., the index action)
# assert_recognizes({:controller => 'items', :action => 'index'}, 'items')
#
@@ -39,15 +38,16 @@ module ActionDispatch
# # Test a custom route
# assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1')
def assert_recognizes(expected_options, path, extras={}, message=nil)
- request = recognized_request_for(path)
+ request = recognized_request_for(path, extras)
expected_options = expected_options.clone
- extras.each_key { |key| expected_options.delete key } unless extras.nil?
expected_options.stringify_keys!
- msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
+
+ # FIXME: minitest does object diffs, do we need to have our own?
+ message ||= sprintf("The recognized options <%s> did not match <%s>, difference: <%s>",
request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
- assert_equal(expected_options, request.path_parameters, msg)
+ assert_equal(expected_options, request.path_parameters, message)
end
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
@@ -56,7 +56,6 @@ module ActionDispatch
#
# The +defaults+ parameter is unused.
#
- # ==== Examples
# # Asserts that the default action is generated for a route with no action
# assert_generates "/items", :controller => "items", :action => "index"
#
@@ -84,10 +83,10 @@ module ActionDispatch
generated_path, extra_keys = @routes.generate_extras(options, defaults)
found_extras = options.reject {|k, v| ! extra_keys.include? k}
- msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
+ msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
assert_equal(extras, found_extras, msg)
- msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
+ msg = message || sprintf("The generated path <%s> did not match <%s>", generated_path,
expected_path)
assert_equal(expected_path, generated_path, msg)
end
@@ -99,7 +98,6 @@ module ActionDispatch
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
# +message+ parameter allows you to specify a custom error message to display upon failure.
#
- # ==== Examples
# # Assert a basic route: a controller with the default action (index)
# assert_routing '/home', :controller => 'home', :action => 'index'
#
@@ -179,7 +177,7 @@ module ActionDispatch
private
# Recognizes the route for a given path.
- def recognized_request_for(path)
+ def recognized_request_for(path, extras = {})
if path.is_a?(Hash)
method = path[:method]
path = path[:path]
@@ -207,7 +205,7 @@ module ActionDispatch
request.request_method = method if method
- params = @routes.recognize_path(path, { :method => method })
+ params = @routes.recognize_path(path, { :method => method, :extras => extras })
request.path_parameters = params.with_indifferent_access
request
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index b4555f4f59..5f9c3bbf48 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -39,7 +39,6 @@ module ActionDispatch
# The selector may be a CSS selector expression (String), an expression
# with substitution values (Array) or an HTML::Selector object.
#
- # ==== Examples
# # Selects all div tags
# divs = css_select("div")
#
@@ -58,7 +57,6 @@ module ActionDispatch
# inputs = css_select(form, "input")
# ...
# end
- #
def css_select(*args)
# See assert_select to understand what's going on here.
arg = args.shift
@@ -269,8 +267,9 @@ module ActionDispatch
end
end
text.strip! unless NO_STRIP.include?(match.name)
+ text.sub!(/\A\n/, '') if match.name == "textarea"
unless match_with.is_a?(Regexp) ? (text =~ match_with) : (text == match_with.to_s)
- content_mismatch ||= build_message(message, "<?> expected but was\n<?>.", match_with, text)
+ content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, text)
true
end
end
@@ -279,7 +278,7 @@ module ActionDispatch
html = match.children.map(&:to_s).join
html.strip! unless NO_STRIP.include?(match.name)
unless match_with.is_a?(Regexp) ? (html =~ match_with) : (html == match_with.to_s)
- content_mismatch ||= build_message(message, "<?> expected but was\n<?>.", match_with, html)
+ content_mismatch ||= sprintf("<%s> expected but was\n<%s>.", match_with, html)
true
end
end
@@ -289,12 +288,15 @@ module ActionDispatch
message ||= content_mismatch if matches.empty?
# Test minimum/maximum occurrence.
min, max, count = equals[:minimum], equals[:maximum], equals[:count]
+
+ # FIXME: minitest provides messaging when we use assert_operator,
+ # so is this custom message really needed?
message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}.)
if count
- assert matches.size == count, message
+ assert_equal matches.size, count, message
else
- assert matches.size >= min, message if min
- assert matches.size <= max, message if max
+ assert_operator matches.size, :>=, min, message if min
+ assert_operator matches.size, :<=, max, message if max
end
# If a block is given call that block. Set @selected to allow
@@ -336,9 +338,8 @@ module ActionDispatch
# The content of each element is un-encoded, and wrapped in the root
# element +encoded+. It then calls the block with all un-encoded elements.
#
- # ==== Examples
- # # Selects all bold tags from within the title of an ATOM feed's entries (perhaps to nab a section name prefix)
- # assert_select_feed :atom, 1.0 do
+ # # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix)
+ # assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do
# # Select each entry item and then the title item
# assert_select "entry>title" do
# # Run assertions on the encoded title elements
@@ -350,7 +351,7 @@ module ActionDispatch
#
#
# # Selects all paragraph tags from within the description of an RSS feed
- # assert_select_feed :rss, 2.0 do
+ # assert_select "rss[version=2.0]" do
# # Select description element of each feed item.
# assert_select "channel>item>description" do
# # Run assertions on the encoded elements.
@@ -397,8 +398,6 @@ module ActionDispatch
# You must enable deliveries for this assertion to work, use:
# ActionMailer::Base.perform_deliveries = true
#
- # ==== Examples
- #
# assert_select_email do
# assert_select "h1", "Email alert"
# end
@@ -409,13 +408,12 @@ module ActionDispatch
# # Work with items here...
# end
# end
- #
def assert_select_email(&block)
deliveries = ActionMailer::Base.deliveries
assert !deliveries.empty?, "No e-mail in delivery list"
- for delivery in deliveries
- for part in (delivery.parts.empty? ? [delivery] : delivery.parts)
+ deliveries.each do |delivery|
+ (delivery.parts.empty? ? [delivery] : delivery.parts).each do |part|
if part["Content-Type"].to_s =~ /^text\/html\W/
root = HTML::Document.new(part.body.to_s).root
assert_select root, ":root", &block
diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
index 5c735e61b2..68f1347e7c 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/tag.rb
@@ -48,8 +48,6 @@ module ActionDispatch
# * if the condition is +true+, the value must not be +nil+.
# * if the condition is +false+ or +nil+, the value must be +nil+.
#
- # === Examples
- #
# # Assert that there is a "span" tag
# assert_tag :tag => "span"
#
@@ -104,7 +102,6 @@ module ActionDispatch
# Identical to +assert_tag+, but asserts that a matching tag does _not_
# exist. (See +assert_tag+ for a full discussion of the syntax.)
#
- # === Examples
# # Assert that there is not a "div" containing a "p"
# assert_no_tag :tag => "div", :descendant => { :tag => "p" }
#
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 0f1bb9f260..08fd28d72d 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -4,7 +4,6 @@ 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'
module ActionDispatch
module Integration #:nodoc:
@@ -27,8 +26,8 @@ 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, PATCH, PUT, DELETE, and HEAD requests with
+ # +#post+, +#patch+, +#put+, +#delete+, and +#head+.
def get(path, parameters = nil, headers = nil)
process :get, path, parameters, headers
end
@@ -39,6 +38,12 @@ module ActionDispatch
process :post, path, parameters, headers
end
+ # Performs a PATCH request with the given parameters. See +#get+ for more
+ # details.
+ def patch(path, parameters = nil, headers = nil)
+ process :patch, path, parameters, headers
+ end
+
# Performs a PUT request with the given parameters. See +#get+ for more
# details.
def put(path, parameters = nil, headers = nil)
@@ -57,13 +62,19 @@ module ActionDispatch
process :head, path, parameters, headers
end
+ # Performs a OPTIONS request with the given parameters. See +#get+ for
+ # more details.
+ def options(path, parameters = nil, headers = nil)
+ process :options, path, parameters, headers
+ end
+
# 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
- # 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.
+ # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
+ # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
+ # string; the headers are a hash. Keys are automatically upcased and
+ # prefixed with 'HTTP_' if not already.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@@ -103,6 +114,12 @@ module ActionDispatch
request_via_redirect(:post, path, parameters, headers)
end
+ # Performs a PATCH request, following any subsequent redirect.
+ # See +request_via_redirect+ for more information.
+ def patch_via_redirect(path, parameters = nil, headers = nil)
+ request_via_redirect(:patch, path, parameters, headers)
+ end
+
# Performs a PUT request, following any subsequent redirect.
# See +request_via_redirect+ for more information.
def put_via_redirect(path, parameters = nil, headers = nil)
@@ -127,7 +144,7 @@ module ActionDispatch
class Session
DEFAULT_HOST = "www.example.com"
- include Test::Unit::Assertions
+ include MiniTest::Assertions
include TestProcess, RequestHelpers, Assertions
%w( status status_message headers body redirect? ).each do |method|
@@ -184,9 +201,16 @@ module ActionDispatch
reset!
end
- remove_method :default_url_options
- def default_url_options
- { :host => host, :protocol => https? ? "https" : "http" }
+ def url_options
+ @url_options ||= default_url_options.dup.tap do |url_options|
+ url_options.reverse_merge!(controller.url_options) if controller
+
+ if @app.respond_to?(:routes) && @app.routes.respond_to?(:default_url_options)
+ url_options.reverse_merge!(@app.routes.default_url_options)
+ end
+
+ url_options.reverse_merge!(:host => host, :protocol => https? ? "https" : "http")
+ end
end
# Resets the instance. This can be used to reset the state information
@@ -199,6 +223,7 @@ module ActionDispatch
@controller = @request = @response = nil
@_mock_session = nil
@request_count = 0
+ @url_options = nil
self.host = DEFAULT_HOST
self.remote_addr = "127.0.0.1"
@@ -293,6 +318,7 @@ module ActionDispatch
response = _mock_session.last_response
@response = ActionDispatch::TestResponse.new(response.status, response.headers, response.body)
@html_document = nil
+ @url_options = nil
@controller = session.last_request.env['action_controller.instance']
@@ -313,7 +339,7 @@ module ActionDispatch
@integration_session = Integration::Session.new(app)
end
- %w(get post put head delete cookies assigns
+ %w(get post patch put head delete options cookies assigns
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
define_method(method) do |*args|
reset! unless integration_session
@@ -350,12 +376,14 @@ module ActionDispatch
end
end
- extend ActiveSupport::Concern
- include ActionDispatch::Routing::UrlFor
+ def default_url_options
+ reset! unless integration_session
+ integration_session.default_url_options
+ end
- def url_options
+ def default_url_options=(options)
reset! unless integration_session
- integration_session.url_options
+ integration_session.default_url_options = options
end
def respond_to?(method, include_private = false)
@@ -459,13 +487,17 @@ module ActionDispatch
class IntegrationTest < ActiveSupport::TestCase
include Integration::Runner
include ActionController::TemplateAssertions
+ include ActionDispatch::Routing::UrlFor
@@app = nil
def self.app
- # DEPRECATE Rails application fallback
- # This should be set by the initializer
- @@app || (defined?(Rails.application) && Rails.application) || nil
+ if !@@app && !ActionDispatch.test_app
+ ActiveSupport::Deprecation.warn "Rails application fallback is deprecated " \
+ "and no longer works, please set ActionDispatch.test_app", caller
+ end
+
+ @@app || ActionDispatch.test_app
end
def self.app=(app)
@@ -475,5 +507,10 @@ module ActionDispatch
def app
super || self.class.app
end
+
+ def url_options
+ reset! unless integration_session
+ integration_session.url_options
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb
index b08ff41950..3a6d081721 100644
--- a/actionpack/lib/action_dispatch/testing/test_process.rb
+++ b/actionpack/lib/action_dispatch/testing/test_process.rb
@@ -5,7 +5,8 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module TestProcess
def assigns(key = nil)
- assigns = @controller.view_assigns.with_indifferent_access
+ assigns = {}.with_indifferent_access
+ @controller.view_assigns.each {|k, v| assigns.regular_writer(k, v)}
key.nil? ? assigns : assigns[key]
end
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index 7280e9a93b..d04be2099c 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/core_ext/hash/reverse_merge'
require 'rack/utils'
module ActionDispatch
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index 914b13dbfb..39c7faf740 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2011 David Heinemeier Hansson
+# Copyright (c) 2004-2012 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index add6b56425..94cbc8e478 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,7 +1,7 @@
module ActionPack
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index d7229419a9..3823f87027 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2011 David Heinemeier Hansson
+# Copyright (c) 2004-2012 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -21,9 +21,7 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-require 'active_support/ruby/shim'
-require 'active_support/core_ext/class/attribute_accessors'
-
+require 'active_support'
require 'action_pack'
module ActionView
@@ -33,6 +31,7 @@ module ActionView
autoload :AssetPaths
autoload :Base
autoload :Context
+ autoload :CompiledTemplates, "action_view/context"
autoload :Helpers
autoload :LookupContext
autoload :PathSet
@@ -77,7 +76,8 @@ module ActionView
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
end
-require 'active_support/i18n'
require 'active_support/core_ext/string/output_safety'
-I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
+ActiveSupport.on_load(:i18n) do
+ I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
+end
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index 1d16e34df6..4ce41d51f1 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -4,6 +4,8 @@ require 'action_controller/metal/exceptions'
module ActionView
class AssetPaths #:nodoc:
+ URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}
+
attr_reader :config, :controller
def initialize(config, controller = nil)
@@ -37,7 +39,7 @@ module ActionView
end
def is_uri?(path)
- path =~ %r{^[-a-z]+://|^cid:|^//}
+ path =~ URI_REGEXP
end
private
@@ -103,8 +105,8 @@ module ActionView
if host.respond_to?(:call)
args = [source]
arity = arity_of(host)
- if arity > 1 && !has_request?
- invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request.")
+ if (arity > 1 || arity < -2) && !has_request?
+ invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request, or make it optional.")
end
args << current_request if (arity > 1 || arity < 0) && has_request?
host.call(*args)
@@ -115,7 +117,7 @@ module ActionView
end
def relative_url_root
- config.relative_url_root
+ config.relative_url_root || current_request.try(:script_name)
end
def asset_host_config
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 36c49d9c91..f98648d930 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -1,16 +1,15 @@
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/ordered_options'
require 'action_view/log_subscriber'
-require 'active_support/core_ext/module/deprecation'
module ActionView #:nodoc:
# = Action View Base
#
- # 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.
+ # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERb
+ # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then Jim Weirich's Builder::XmlMarkup library is used.
#
# == ERB
#
@@ -94,10 +93,10 @@ module ActionView #:nodoc:
#
# Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
#
- # xml.div {
+ # xml.div do
# xml.h1(@person.name)
# xml.p(@person.bio)
- # }
+ # end
#
# would produce something like:
#
@@ -141,14 +140,20 @@ module ActionView #:nodoc:
# How to complete the streaming when an exception occurs.
# This is our best guess: first try to close the attribute, then the tag.
cattr_accessor :streaming_completion_on_exception
- @@streaming_completion_on_exception = %("><script type="text/javascript">window.location = "/500.html"</script></html>)
+ @@streaming_completion_on_exception = %("><script>window.location = "/500.html"</script></html>)
+
+ # Specify whether rendering within namespaced controllers should prefix
+ # the partial paths for ActiveModel objects with the namespace.
+ # (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb)
+ cattr_accessor :prefix_partial_path_with_controller_namespace
+ @@prefix_partial_path_with_controller_namespace = true
class_attribute :helpers
class_attribute :_routes
+ class_attribute :logger
class << self
delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
- delegate :logger, :to => 'ActionController::Base', :allow_nil => true
def cache_template_loading
ActionView::Resolver.caching?
@@ -158,12 +163,6 @@ module ActionView #:nodoc:
ActionView::Resolver.caching = value
end
- def process_view_paths(value)
- value.is_a?(PathSet) ?
- value.dup : ActionView::PathSet.new(Array.wrap(value))
- end
- deprecate :process_view_paths
-
def xss_safe? #:nodoc:
true
end
@@ -202,7 +201,7 @@ module ActionView #:nodoc:
# TODO Provide a new API for AV::Base and deprecate this one.
if context.is_a?(ActionView::Renderer)
@view_renderer = context
- elsif
+ else
lookup_context = context.is_a?(ActionView::LookupContext) ?
context : ActionView::LookupContext.new(context)
lookup_context.formats = formats if formats
diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb
index be7f65c2ce..2372d3c433 100644
--- a/actionpack/lib/action_view/buffers.rb
+++ b/actionpack/lib/action_view/buffers.rb
@@ -4,7 +4,7 @@ module ActionView
class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
def initialize(*)
super
- encode! if encoding_aware?
+ encode!
end
def <<(value)
diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb
index 083856b2ca..245849d706 100644
--- a/actionpack/lib/action_view/context.rb
+++ b/actionpack/lib/action_view/context.rb
@@ -5,7 +5,7 @@ module ActionView
# = Action View Context
#
- # Action View contexts are supplied to Action Controller to render template.
+ # Action View contexts are supplied to Action Controller to render a template.
# The default Action View context is ActionView::Base.
#
# In order to work with ActionController, a Context must just include this module.
@@ -25,7 +25,7 @@ module ActionView
end
# Encapsulates the interaction with the view flow so it
- # returns the correct buffer on yield. This is usually
+ # returns the correct buffer on +yield+. This is usually
# overwriten by helpers to add more behavior.
# :api: plugin
def _layout_for(name=nil)
diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb
index a8f740713f..c0e458cd41 100644
--- a/actionpack/lib/action_view/flows.rb
+++ b/actionpack/lib/action_view/flows.rb
@@ -22,11 +22,8 @@ module ActionView
def append(key, value)
@content[key] << value
end
+ alias_method :append!, :append
- # Called by provide
- def append!(key, value)
- @content[key] << value
- end
end
class StreamingFlow < OutputFlow #:nodoc:
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index 262e0f1010..f2a3a494bc 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -1,5 +1,3 @@
-require 'active_support/benchmarkable'
-
module ActionView #:nodoc:
module Helpers #:nodoc:
extend ActiveSupport::Autoload
@@ -7,6 +5,7 @@ module ActionView #:nodoc:
autoload :ActiveModelHelper
autoload :AssetTagHelper
autoload :AtomFeedHelper
+ autoload :BenchmarkHelper
autoload :CacheHelper
autoload :CaptureHelper
autoload :ControllerHelper
@@ -33,10 +32,10 @@ module ActionView #:nodoc:
extend SanitizeHelper::ClassMethods
end
- include ActiveSupport::Benchmarkable
include ActiveModelHelper
include AssetTagHelper
include AtomFeedHelper
+ include BenchmarkHelper
include CacheHelper
include CaptureHelper
include ControllerHelper
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 96c3eec337..e27111012d 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -1,4 +1,3 @@
-require 'action_view/helpers/form_helper'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/object/blank'
@@ -17,8 +16,8 @@ module ActionView
end
end
- %w(content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth|
- module_eval "def #{meth}(*) error_wrapping(super) end", __FILE__, __LINE__
+ def content_tag(*)
+ error_wrapping(super)
end
def tag(type, options, *)
@@ -40,16 +39,12 @@ module ActionView
private
def object_has_errors?
- object.respond_to?(:errors) && object.errors.respond_to?(:full_messages) && error_message.any?
+ object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
end
def tag_generate_errors?(options)
options['type'] != 'hidden'
end
end
-
- class InstanceTag
- include ActiveModelInstanceTag
- end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb
deleted file mode 100644
index fae2e4fc1c..0000000000
--- a/actionpack/lib/action_view/helpers/asset_paths.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-ActiveSupport::Deprecation.warn "ActionView::Helpers::AssetPaths is deprecated. Please use ActionView::AssetPaths instead."
-
-module ActionView
- module Helpers
- AssetPaths = ::ActionView::AssetPaths
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 7d01e5ddb8..02c1250c76 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/array/extract_options'
+require 'active_support/core_ext/hash/keys'
require 'action_view/helpers/asset_tag_helpers/javascript_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers'
require 'action_view/helpers/asset_tag_helpers/asset_paths'
@@ -11,27 +13,29 @@ module ActionView
# the assets exist before linking to them:
#
# image_tag("rails.png")
- # # => <img alt="Rails" src="/images/rails.png?1230601161" />
+ # # => <img alt="Rails" src="/assets/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
+ #
#
# === Using asset hosts
#
# By default, Rails links to these assets on the current host in the public
# folder, but you can direct Rails to link to assets from a dedicated asset
- # server by setting ActionController::Base.asset_host in the application
+ # server by setting <tt>ActionController::Base.asset_host</tt> in the application
# configuration, typically in <tt>config/environments/production.rb</tt>.
# For example, you'd define <tt>assets.example.com</tt> to be your asset
- # host this way:
+ # host this way, inside the <tt>configure</tt> block of your environment-specific
+ # configuration files or <tt>config/application.rb</tt>:
#
- # ActionController::Base.asset_host = "assets.example.com"
+ # config.action_controller.asset_host = "assets.example.com"
#
# Helpers take that into account:
#
# image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets.example.com/images/rails.png?1230601161" />
+ # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
# Browsers typically open at most two simultaneous connections to a single
# host, which means your assets often have to wait for other assets to finish
@@ -42,9 +46,9 @@ module ActionView
# will open eight simultaneous connections rather than two.
#
# image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
+ # # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
# To do this, you can either setup four actual hosts, or you can use wildcard
# DNS to CNAME the wildcard to a single asset host. You can read more about
@@ -61,29 +65,28 @@ module ActionView
# "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
# }
# image_tag("rails.png")
- # # => <img alt="Rails" src="http://assets1.example.com/images/rails.png?1230601161" />
+ # # => <img alt="Rails" src="http://assets1.example.com/assets/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
# The example above generates "http://assets1.example.com" and
# "http://assets2.example.com". This option is useful for example if
# you need fewer/more than four hosts, custom host names, etc.
#
# As you see the proc takes a +source+ parameter. That's a string with the
- # absolute path of the asset with any extensions and timestamps in place,
- # for example "/images/rails.png?1230601161".
+ # absolute path of the asset, for example "/assets/rails.png".
#
# ActionController::Base.asset_host = Proc.new { |source|
- # if source.starts_with?('/images')
- # "http://images.example.com"
+ # if source.ends_with?('.css')
+ # "http://stylesheets.example.com"
# else
# "http://assets.example.com"
# end
# }
# image_tag("rails.png")
- # # => <img alt="Rails" src="http://images.example.com/images/rails.png?1230601161" />
+ # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://stylesheets.example.com/assets/application.css" media="screen" rel="stylesheet" />
#
# Alternatively you may ask for a second parameter +request+. That one is
# particularly useful for serving assets from an SSL-protected page. The
@@ -160,7 +163,7 @@ module ActionView
# image_tag("rails.png")
# # => <img alt="Rails" src="/release-12345/images/rails.png" />
# stylesheet_link_tag("application")
- # # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="/release-12345/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" />
#
# Changing the asset_path does require that your web servers have
# knowledge of the asset template paths that you rewrite to so it's not
@@ -196,7 +199,7 @@ module ActionView
include JavascriptTagHelpers
include StylesheetTagHelpers
# Returns a link tag that browsers and news readers can use to auto-detect
- # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
+ # an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or
# <tt>:atom</tt>. Control the link options in url_for format using the
# +url_options+. You can modify the LINK tag itself in +tag_options+.
#
@@ -228,23 +231,19 @@ module ActionView
)
end
- # Web browsers cache favicons. If you just throw a <tt>favicon.ico</tt> into the document
- # root of your application and it changes later, clients that have it in their cache
- # won't see the update. Using this helper prevents that because it appends an asset ID:
- #
# <%= favicon_link_tag %>
#
# generates
#
- # <link href="/favicon.ico?4649789979" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # <link href="/assets/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
#
# You may specify a different file in the first argument:
#
- # <%= favicon_link_tag 'favicon.ico' %>
+ # <%= favicon_link_tag '/myicon.ico' %>
#
# That's passed to +path_to_image+ as is, so it gives
#
- # <link href="/images/favicon.ico?4649789979" rel="shortcut icon" type="image/vnd.microsoft.icon" />
+ # <link href="/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
#
# The helper accepts an additional options hash where you can override "rel" and "type".
#
@@ -253,8 +252,7 @@ module ActionView
# The following call would generate such a tag:
#
# <%= favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %>
- #
- def favicon_link_tag(source='/favicon.ico', options={})
+ def favicon_link_tag(source='favicon.ico', options={})
tag('link', {
:rel => 'shortcut icon',
:type => 'image/vnd.microsoft.icon',
@@ -262,13 +260,13 @@ module ActionView
}.merge(options.symbolize_keys))
end
- # Computes the path to an image asset in the public images directory.
+ # Computes the path to an image asset.
# Full paths from the document root will be passed through.
# Used internally by +image_tag+ to build the image path:
#
- # image_path("edit") # => "/images/edit"
- # image_path("edit.png") # => "/images/edit.png"
- # image_path("icons/edit.png") # => "/images/icons/edit.png"
+ # image_path("edit") # => "/assets/edit"
+ # image_path("edit.png") # => "/assets/edit.png"
+ # image_path("icons/edit.png") # => "/assets/icons/edit.png"
# image_path("/icons/edit.png") # => "/icons/edit.png"
# image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png"
#
@@ -276,15 +274,21 @@ module ActionView
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
# plugin authors are encouraged to do so.
def image_path(source)
- asset_paths.compute_public_path(source, 'images')
+ source.present? ? asset_paths.compute_public_path(source, 'images') : ""
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
+ # Computes the full URL to an image asset.
+ # This will use +image_path+ internally, so most of their behaviors will be the same.
+ def image_url(source)
+ URI.join(current_host, path_to_image(source)).to_s
+ end
+ alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
+
# Computes the path to a video asset in the public videos directory.
# Full paths from the document root will be passed through.
# Used internally by +video_tag+ to build the video path.
#
- # ==== Examples
# video_path("hd") # => /videos/hd
# video_path("hd.avi") # => /videos/hd.avi
# video_path("trailers/hd.avi") # => /videos/trailers/hd.avi
@@ -295,11 +299,17 @@ module ActionView
end
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
+ # Computes the full URL to a video asset in the public videos directory.
+ # This will use +video_path+ internally, so most of their behaviors will be the same.
+ def video_url(source)
+ URI.join(current_host, path_to_video(source)).to_s
+ end
+ alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route
+
# Computes the path to an audio asset in the public audios directory.
# Full paths from the document root will be passed through.
# Used internally by +audio_tag+ to build the audio path.
#
- # ==== Examples
# audio_path("horse") # => /audios/horse
# audio_path("horse.wav") # => /audios/horse.wav
# audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav
@@ -310,8 +320,35 @@ module ActionView
end
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
+ # Computes the full URL to an audio asset in the public audios directory.
+ # This will use +audio_path+ internally, so most of their behaviors will be the same.
+ def audio_url(source)
+ URI.join(current_host, path_to_audio(source)).to_s
+ end
+ alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
+
+ # Computes the path to a font asset.
+ # Full paths from the document root will be passed through.
+ #
+ # font_path("font") # => /assets/font
+ # font_path("font.ttf") # => /assets/font.ttf
+ # font_path("dir/font.ttf") # => /assets/dir/font.ttf
+ # font_path("/dir/font.ttf") # => /dir/font.ttf
+ # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
+ def font_path(source)
+ asset_paths.compute_public_path(source, 'fonts')
+ end
+ alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
+
+ # Computes the full URL to a font asset.
+ # This will use +font_path+ internally, so most of their behaviors will be the same.
+ def font_url(source)
+ URI.join(current_host, path_to_font(source)).to_s
+ end
+ alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
+
# Returns an html image tag for the +source+. The +source+ can be a full
- # path or a file that exists in your public images directory.
+ # path or a file.
#
# ==== Options
# You can add HTML attributes using the +options+. The +options+ supports
@@ -322,33 +359,25 @@ module ActionView
# * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes
# width="30" and height="45". <tt>:size</tt> will be ignored if the
# value is not in the correct format.
- # * <tt>:mouseover</tt> - Set an alternate image to be used when the onmouseover
- # event is fired, and sets the original image to be replaced onmouseout.
- # This can be used to implement an easy image toggle that fires on onmouseover.
#
- # ==== Examples
# image_tag("icon") # =>
- # <img src="/images/icon" alt="Icon" />
+ # <img src="/assets/icon" alt="Icon" />
# image_tag("icon.png") # =>
- # <img src="/images/icon.png" alt="Icon" />
+ # <img src="/assets/icon.png" alt="Icon" />
# image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # =>
- # <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" />
+ # <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" />
# image_tag("/icons/icon.gif", :size => "16x16") # =>
# <img src="/icons/icon.gif" width="16" height="16" alt="Icon" />
# image_tag("/icons/icon.gif", :height => '32', :width => '32') # =>
# <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
# image_tag("/icons/icon.gif", :class => "menu_icon") # =>
# <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
- # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
- # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
- # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
- # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
- def image_tag(source, options = {})
- options.symbolize_keys!
+ def image_tag(source, options={})
+ options = options.symbolize_keys
src = options[:src] = path_to_image(source)
- unless src =~ /^cid:/
+ unless src =~ /^(?:cid|data):/ || src.blank?
options[:alt] = options.fetch(:alt){ image_alt(src) }
end
@@ -356,11 +385,6 @@ module ActionView
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
end
- if mouseover = options.delete(:mouseover)
- options[:onmouseover] = "this.src='#{path_to_image(mouseover)}'"
- options[:onmouseout] = "this.src='#{src}'"
- end
-
tag("img", options)
end
@@ -384,7 +408,6 @@ module ActionView
# width="30" and height="45". <tt>:size</tt> will be ignored if the
# value is not in the correct format.
#
- # ==== Examples
# video_tag("trailer") # =>
# <video src="/videos/trailer" />
# video_tag("trailer.ogg") # =>
@@ -392,31 +415,24 @@ module ActionView
# video_tag("trailer.ogg", :controls => true, :autobuffer => true) # =>
# <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
# video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") # =>
- # <video src="/videos/trailer.m4v" width="16" height="10" poster="/images/screenshot.png" />
+ # <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" />
# video_tag("/trailers/hd.avi", :size => "16x16") # =>
# <video src="/trailers/hd.avi" width="16" height="16" />
# video_tag("/trailers/hd.avi", :height => '32', :width => '32') # =>
# <video height="32" src="/trailers/hd.avi" width="32" />
+ # video_tag("trailer.ogg", "trailer.flv") # =>
+ # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
# video_tag(["trailer.ogg", "trailer.flv"]) # =>
- # <video><source src="trailer.ogg" /><source src="trailer.ogg" /><source src="trailer.flv" /></video>
- # video_tag(["trailer.ogg", "trailer.flv"] :size => "160x120") # =>
- # <video height="120" width="160"><source src="trailer.ogg" /><source src="trailer.flv" /></video>
- def video_tag(sources, options = {})
- options.symbolize_keys!
-
- options[:poster] = path_to_image(options[:poster]) if options[:poster]
-
- if size = options.delete(:size)
- options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
- end
+ # <video><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ # video_tag(["trailer.ogg", "trailer.flv"], :size => "160x120") # =>
+ # <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
+ def video_tag(*sources)
+ multiple_sources_tag('video', sources) do |options|
+ options[:poster] = path_to_image(options[:poster]) if options[:poster]
- if sources.is_a?(Array)
- content_tag("video", options) do
- sources.map { |source| tag("source", :src => source) }.join.html_safe
+ if size = options.delete(:size)
+ options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
end
- else
- options[:src] = path_to_video(sources)
- tag("video", options)
end
end
@@ -424,17 +440,16 @@ module ActionView
# The +source+ can be full path or file that exists in
# your public audios directory.
#
- # ==== Examples
- # audio_tag("sound") # =>
- # <audio src="/audios/sound" />
- # audio_tag("sound.wav") # =>
- # <audio src="/audios/sound.wav" />
- # audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
- # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
- def audio_tag(source, options = {})
- options.symbolize_keys!
- options[:src] = path_to_audio(source)
- tag("audio", options)
+ # audio_tag("sound") # =>
+ # <audio src="/audios/sound" />
+ # audio_tag("sound.wav") # =>
+ # <audio src="/audios/sound.wav" />
+ # audio_tag("sound.wav", :autoplay => true, :controls => true) # =>
+ # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" />
+ # audio_tag("sound.wav", "sound.mid") # =>
+ # <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
+ def audio_tag(*sources)
+ multiple_sources_tag('audio', sources)
end
private
@@ -442,6 +457,26 @@ module ActionView
def asset_paths
@asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller)
end
+
+ def multiple_sources_tag(type, sources)
+ options = sources.extract_options!.symbolize_keys
+ sources.flatten!
+
+ yield options if block_given?
+
+ if sources.size > 1
+ content_tag(type, options) do
+ safe_join sources.map { |source| tag("source", :src => send("path_to_#{type}", source)) }
+ end
+ else
+ options[:src] = send("path_to_#{type}", sources.first)
+ content_tag(type, nil, options)
+ end
+ end
+
+ def current_host
+ url_for(:only_path => false)
+ end
end
end
end
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 dd4e9ae4cc..35f91cec18 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
@@ -1,5 +1,6 @@
require 'thread'
require 'active_support/core_ext/file'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionView
module Helpers
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 09700bd0c5..4292d29f60 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
@@ -16,7 +16,7 @@ module ActionView
end
def asset_tag(source, options)
- content_tag("script", "", { "type" => Mime::JS, "src" => path_to_asset(source) }.merge(options))
+ content_tag("script", "", { "src" => path_to_asset(source) }.merge(options))
end
def custom_dir
@@ -60,9 +60,9 @@ module ActionView
# ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"]
#
# javascript_include_tag :monkey # =>
- # <script type="text/javascript" src="/javascripts/head.js"></script>
- # <script type="text/javascript" src="/javascripts/body.js"></script>
- # <script type="text/javascript" src="/javascripts/tail.js"></script>
+ # <script src="/javascripts/head.js"></script>
+ # <script src="/javascripts/body.js"></script>
+ # <script src="/javascripts/tail.js"></script>
def register_javascript_expansion(expansions)
js_expansions = JavascriptIncludeTag.expansions
expansions.each do |key, values|
@@ -76,7 +76,6 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by javascript_include_tag to build the script path.
#
- # ==== Examples
# javascript_path "xmlhr" # => /javascripts/xmlhr.js
# javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
@@ -87,6 +86,13 @@ module ActionView
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
+ # Computes the full URL to a javascript asset in the public javascripts directory.
+ # This will use +javascript_path+ internally, so most of their behaviors will be the same.
+ def javascript_url(source)
+ URI.join(current_host, path_to_javascript(source)).to_s
+ end
+ alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
+
# 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
@@ -107,38 +113,35 @@ module ActionView
# 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>
+ # # => <script src="/javascripts/xmlhr.js?1284139606"></script>
#
# javascript_include_tag "xmlhr.js"
- # # => <script type="text/javascript" src="/javascripts/xmlhr.js?1284139606"></script>
+ # # => <script 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>
+ # # => <script src="/javascripts/common.javascript?1284139606"></script>
+ # # <script src="/elsewhere/cools.js?1423139606"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr"
- # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js?1284139606"></script>
+ # # => <script src="http://www.example.com/xmlhr"></script>
#
# javascript_include_tag "http://www.example.com/xmlhr.js"
- # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js?1284139606"></script>
+ # # => <script src="http://www.example.com/xmlhr.js"></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
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/rails.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
#
# 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/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>
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/rails.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
+ # # <script src="/javascripts/shop.js?1284139606"></script>
+ # # <script src="/javascripts/checkout.js?1284139606"></script>
#
# Note that your defaults of choice will be included first, so they will be available to all subsequently
# included files.
@@ -155,29 +158,27 @@ module ActionView
# <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
- #
# # 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>
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/rails.js?1284139606"></script>
+ # # <script src="/javascripts/application.js?1284139606"></script>
+ # # <script src="/javascripts/shop.js?1284139606"></script>
+ # # <script 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>
+ # # => <script src="/javascripts/all.js?1344139789"></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>
+ # # => <script src="/javascripts/jquery.js?1284139606"></script>
+ # # <script src="/javascripts/cart.js?1289139157"></script>
+ # # <script src="/javascripts/checkout.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>
+ # # => <script src="/javascripts/shop.js?1299139816"></script>
#
# The <tt>:recursive</tt> option is also available for caching:
#
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 343153c8c5..57b0627225 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
@@ -17,7 +17,7 @@ module ActionView
def asset_tag(source, options)
# We force the :request protocol here to avoid a double-download bug in IE7 and IE8
- tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options))
+ tag("link", { "rel" => "stylesheet", "media" => "screen", "href" => path_to_asset(source, :protocol => :request) }.merge(options))
end
def custom_dir
@@ -38,9 +38,9 @@ module ActionView
# ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"]
#
# stylesheet_link_tag :monkey # =>
- # <link href="/stylesheets/head.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/head.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/body.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" />
def register_stylesheet_expansion(expansions)
style_expansions = StylesheetIncludeTag.expansions
expansions.each do |key, values|
@@ -54,7 +54,6 @@ module ActionView
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
#
- # ==== Examples
# stylesheet_path "style" # => /stylesheets/style.css
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
# stylesheet_path "/dir/style.css" # => /dir/style.css
@@ -65,36 +64,45 @@ module ActionView
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
+ # Computes the full URL to a stylesheet asset in the public stylesheets directory.
+ # This will use +stylesheet_path+ internally, so most of their behaviors will be the same.
+ def stylesheet_url(source)
+ URI.join(current_host, path_to_stylesheet(source)).to_s
+ end
+ alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
+
# Returns a stylesheet link tag for the sources specified as arguments. If
# you don't specify an extension, <tt>.css</tt> will be appended automatically.
# You can modify the link attributes by passing a hash as the last argument.
+ # For historical reasons, the 'media' attribute will always be present and defaults
+ # to "screen", so you must explicitely set it to "all" for the stylesheet(s) to
+ # apply to all media types.
#
- # ==== Examples
# stylesheet_link_tag "style" # =>
- # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "style.css" # =>
- # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "http://www.example.com/style.css" # =>
- # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "style", :media => "all" # =>
- # <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="all" rel="stylesheet" />
#
# stylesheet_link_tag "style", :media => "print" # =>
- # <link href="/stylesheets/style.css" media="print" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style.css" media="print" rel="stylesheet" />
#
# stylesheet_link_tag "random.styles", "/css/stylish" # =>
- # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />
+ # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
#
# You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source:
#
# stylesheet_link_tag :all # =>
- # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" />
#
# If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>:
#
@@ -103,26 +111,25 @@ module ActionView
# == Caching multiple stylesheets into one
#
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
- # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
+ # compressed by gzip (leading to faster transfers). Caching will only happen if +config.perform_caching+
# is set to true (which is the case by default for the Rails production environment, but not for the development
# environment). Examples:
#
- # ==== Examples
# stylesheet_link_tag :all, :cache => true # when config.perform_caching is false =>
- # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag :all, :cache => true # when config.perform_caching is true =>
- # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false =>
- # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" />
- # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" />
+ # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" />
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true =>
- # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
+ # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" />
#
# The <tt>:recursive</tt> option is also available for caching:
#
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index 39c37b25dc..f9aa8d7cee 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -32,7 +32,7 @@ module ActionView
# app/views/posts/index.atom.builder:
# atom_feed do |feed|
# feed.title("My great blog!")
- # feed.updated(@posts.first.created_at)
+ # feed.updated(@posts[0].created_at) if @posts.length > 0
#
# @posts.each do |post|
# feed.entry(post) do |entry|
@@ -176,6 +176,7 @@ module ActionView
# * <tt>:updated</tt>: Time of update. Defaults to the updated_at attribute on the record if one such exists.
# * <tt>:url</tt>: The URL for this entry. Defaults to the polymorphic_url for the record.
# * <tt>:id</tt>: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}"
+ # * <tt>:type</tt>: The TYPE for this entry. Defaults to "text/html".
def entry(record, options = {})
@xml.entry do
@xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}")
@@ -188,7 +189,9 @@ module ActionView
@xml.updated((options[:updated] || record.updated_at).xmlschema)
end
- @xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:url] || @view.polymorphic_url(record))
+ type = options.fetch(:type, 'text/html')
+
+ @xml.link(:rel => 'alternate', :type => type, :href => options[:url] || @view.polymorphic_url(record))
yield AtomBuilder.new(@xml)
end
diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb
new file mode 100644
index 0000000000..dfdd5a786d
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/benchmark_helper.rb
@@ -0,0 +1,13 @@
+require 'active_support/benchmarkable'
+
+module ActionView
+ module Helpers
+ module BenchmarkHelper
+ include ActiveSupport::Benchmarkable
+
+ def benchmark(*)
+ capture { super }
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index 850dd5f448..33799d7d71 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -10,7 +10,6 @@ module ActionView
#
# See ActionController::Caching::Fragments for usage instructions.
#
- # ==== Examples
# If you want to cache a navigation menu, you can do following:
#
# <% cache do %>
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 8abd85c3a3..397738dd98 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -13,7 +13,6 @@ module ActionView
# The capture method allows you to extract part of a template into a
# variable. You can then use this variable anywhere in your templates or layout.
#
- # ==== Examples
# The capture method can be used in ERB templates...
#
# <% @greeting = capture do %>
@@ -81,8 +80,8 @@ module ActionView
# <%# This is the layout %>
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
# <head>
- # <title>My Website</title>
- # <%= yield :script %>
+ # <title>My Website</title>
+ # <%= yield :script %>
# </head>
# <body>
# <%= yield %>
@@ -96,7 +95,7 @@ module ActionView
# Please login!
#
# <% content_for :script do %>
- # <script type="text/javascript">alert('You are not authorized to view this page!')</script>
+ # <script>alert('You are not authorized to view this page!')</script>
# <% end %>
#
# Then, in another view, you could to do something like this:
@@ -110,7 +109,7 @@ module ActionView
# 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
+ # Note that content_for concatenates (default) the blocks it is given for a particular
# identifier in order. For example:
#
# <% content_for :navigation do %>
@@ -127,16 +126,37 @@ module ActionView
#
# <ul><%= content_for :navigation %></ul>
#
+ # If the flush parameter is true content_for replaces the blocks it is given. For example:
+ #
+ # <% content_for :navigation do %>
+ # <li><%= link_to 'Home', :action => 'index' %></li>
+ # <% end %>
+ #
+ # <%# Add some other content, or use a different template: %>
+ #
+ # <% content_for :navigation, true do %>
+ # <li><%= link_to 'Login', :action => 'login' %></li>
+ # <% end %>
+ #
+ # Then, in another template or layout, this code would render only the last link:
+ #
+ # <ul><%= content_for :navigation %></ul>
+ #
# Lastly, simple content can be passed as a parameter:
#
# <% content_for :script, javascript_include_tag(:defaults) %>
#
# WARNING: content_for is ignored in caches. So you shouldn't use it
# for elements that will be fragment cached.
- def content_for(name, content = nil, &block)
+ def content_for(name, content = nil, flush = false, &block)
if content || block_given?
- content = capture(&block) if block_given?
- @view_flow.append(name, content) if content
+ if block_given?
+ flush = content if content
+ content = capture(&block)
+ end
+ if content
+ flush ? @view_flow.set(name, content) : @view_flow.append(name, content)
+ end
nil
else
@view_flow.get(name)
@@ -164,8 +184,8 @@ module ActionView
# <%# This is the layout %>
# <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
# <head>
- # <title>My Website</title>
- # <%= yield :script %>
+ # <title>My Website</title>
+ # <%= yield :script %>
# </head>
# <body class="<%= content_for?(:right_col) ? 'one-column' : 'two-column' %>">
# <%= yield %>
@@ -181,7 +201,7 @@ module ActionView
def with_output_buffer(buf = nil) #:nodoc:
unless buf
buf = ActionView::OutputBuffer.new
- buf.force_encoding(output_buffer.encoding) if output_buffer.respond_to?(:encoding) && buf.respond_to?(:force_encoding)
+ buf.force_encoding(output_buffer.encoding) if output_buffer
end
self.output_buffer, old_buffer = buf, output_buffer
yield
@@ -194,7 +214,7 @@ module ActionView
def flush_output_buffer #:nodoc:
if output_buffer && !output_buffer.empty?
response.body_parts << output_buffer
- self.output_buffer = output_buffer[0,0]
+ self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0]
nil
end
end
diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb
index 1f2bc28cac..eeb0ed94b9 100644
--- a/actionpack/lib/action_view/helpers/csrf_helper.rb
+++ b/actionpack/lib/action_view/helpers/csrf_helper.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/string/strip'
-
module ActionView
# = Action View CSRF Helper
module Helpers
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 4deb87180c..659aacf6d7 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -12,14 +12,14 @@ module ActionView
# elements. All of the select-type methods share a number of common options that are as follows:
#
# * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday"
- # would give birthday[month] instead of date[month] if passed to the <tt>select_month</tt> method.
+ # would give \birthday[month] instead of \date[month] if passed to the <tt>select_month</tt> method.
# * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date.
# * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true,
# the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead
- # of "date[month]".
+ # of \date[month].
module DateHelper
# Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds.
- # Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs.
+ # Pass <tt>:include_seconds => true</tt> if you want more detailed approximations when distance < 1 min, 29 secs.
# Distances are reported based on the following table:
#
# 0 <-> 29 secs # => less than a minute
@@ -29,14 +29,15 @@ module ActionView
# 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
+ # 29 days, 23 hrs, 59 mins, 30 secs <-> 44 days, 23 hrs, 59 mins, 29 secs # => about 1 month
+ # 44 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 2 months
# 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
# 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year
# 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years
# 2 yrs <-> max time or date # => (same rules as 1 yr)
#
- # With <tt>include_seconds</tt> = true and the difference < 1 minute 29 seconds:
+ # With <tt>:include_seconds => true</tt> and the difference < 1 minute 29 seconds:
# 0-4 secs # => less than 5 seconds
# 5-9 secs # => less than 10 seconds
# 10-19 secs # => less than 20 seconds
@@ -46,36 +47,44 @@ module ActionView
#
# ==== Examples
# from_time = Time.now
- # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
- # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
- # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
- # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
- # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
- # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days
- # distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
- # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
- # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
- # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
- # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
+ # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
+ # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
+ # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
+ # distance_of_time_in_words(from_time, from_time + 15.seconds, :include_seconds => true) # => less than 20 seconds
+ # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
+ # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days
+ # distance_of_time_in_words(from_time, from_time + 45.seconds, :include_seconds => true) # => less than a minute
+ # distance_of_time_in_words(from_time, from_time - 45.seconds, :include_seconds => true) # => less than a minute
+ # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
+ # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
+ # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
# distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years
#
# to_time = Time.now + 6.years + 19.days
- # distance_of_time_in_words(from_time, to_time, true) # => about 6 years
- # distance_of_time_in_words(to_time, from_time, true) # => about 6 years
- # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
- #
- def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
+ # distance_of_time_in_words(from_time, to_time, :include_seconds => true) # => about 6 years
+ # distance_of_time_in_words(to_time, from_time, :include_seconds => true) # => about 6 years
+ # distance_of_time_in_words(Time.now, Time.now) # => less than a minute
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {})
+ if include_seconds_or_options.is_a?(Hash)
+ options = include_seconds_or_options
+ else
+ ActiveSupport::Deprecation.warn "distance_of_time_in_words and time_ago_in_words now accept :include_seconds " +
+ "as a part of options hash, not a boolean argument", caller
+ options[:include_seconds] ||= !!include_seconds_or_options
+ end
+
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
- distance_in_minutes = (((to_time - from_time).abs)/60).round
- distance_in_seconds = ((to_time - from_time).abs).round
+ from_time, to_time = to_time, from_time if from_time > to_time
+ distance_in_minutes = ((to_time - from_time)/60.0).round
+ distance_in_seconds = (to_time - from_time).round
I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
locale.t(:less_than_x_minutes, :count => 1) :
- locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
+ locale.t(:x_minutes, :count => distance_in_minutes) unless options[:include_seconds]
case distance_in_seconds
when 0..4 then locale.t :less_than_x_seconds, :count => 5
@@ -86,26 +95,35 @@ module ActionView
else locale.t :x_minutes, :count => 1
end
- 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..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
+ when 2...45 then locale.t :x_minutes, :count => distance_in_minutes
+ when 45...90 then locale.t :about_x_hours, :count => 1
+ # 90 mins up to 24 hours
+ when 90...1440 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
+ # 24 hours up to 42 hours
+ when 1440...2520 then locale.t :x_days, :count => 1
+ # 42 hours up to 30 days
+ when 2520...43200 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
+ # 30 days up to 60 days
+ when 43200...86400 then locale.t :about_x_months, :count => (distance_in_minutes.to_f / 43200.0).round
+ # 60 days up to 365 days
+ when 86400...525600 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
- fyear = from_time.year
- fyear += 1 if from_time.month >= 3
- tyear = to_time.year
- tyear -= 1 if to_time.month < 3
- leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
- minute_offset_for_leap_year = leap_years * 1440
- # Discount the leap year days when calculating year distance.
- # e.g. if there are 20 leap year days between 2 dates having the same day
- # and month then the based on 365 days calculation
- # the distance in years will come out to over 80 years when in written
- # english it would read better as about 80 years.
- minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ if from_time.acts_like?(:time) && to_time.acts_like?(:time)
+ fyear = from_time.year
+ fyear += 1 if from_time.month >= 3
+ tyear = to_time.year
+ tyear -= 1 if to_time.month < 3
+ leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
+ minute_offset_for_leap_year = leap_years * 1440
+ # Discount the leap year days when calculating year distance.
+ # e.g. if there are 20 leap year days between 2 dates having the same day
+ # and month then the based on 365 days calculation
+ # the distance in years will come out to over 80 years when in written
+ # english it would read better as about 80 years.
+ minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ else
+ minutes_with_offset = distance_in_minutes
+ end
remainder = (minutes_with_offset % 525600)
distance_in_years = (minutes_with_offset / 525600)
if remainder < 131400
@@ -121,16 +139,15 @@ module ActionView
# Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
#
- # ==== Examples
- # time_ago_in_words(3.minutes.from_now) # => 3 minutes
- # time_ago_in_words(Time.now - 15.hours) # => about 15 hours
- # time_ago_in_words(Time.now) # => less than a minute
+ # time_ago_in_words(3.minutes.from_now) # => 3 minutes
+ # time_ago_in_words(Time.now - 15.hours) # => about 15 hours
+ # time_ago_in_words(Time.now) # => less than a minute
+ # time_ago_in_words(Time.now, :include_seconds => true) # => less than 5 seconds
#
# from_time = Time.now - 3.days - 14.minutes - 25.seconds
# time_ago_in_words(from_time) # => 3 days
- #
- def time_ago_in_words(from_time, include_seconds = false)
- distance_of_time_in_words(from_time, Time.now, include_seconds)
+ def time_ago_in_words(from_time, include_seconds_or_options = {})
+ distance_of_time_in_words(from_time, Time.now, include_seconds_or_options)
end
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
@@ -142,6 +159,8 @@ module ActionView
# ==== Options
# * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g.
# "2" instead of "February").
+ # * <tt>:use_two_digit_numbers</tt> - Set to true if you want to display two digit month and day numbers (e.g.
+ # "02" instead of "February" and "08" instead of "8").
# * <tt>:use_short_month</tt> - Set to true if you want to use abbreviated month names instead of full
# month names (e.g. "Feb" instead of "February").
# * <tt>:add_month_numbers</tt> - Set to true if you want to use both month numbers and month names (e.g.
@@ -175,7 +194,6 @@ module ActionView
#
# NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
#
- # ==== Examples
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute.
# date_select("article", "written_on")
#
@@ -189,6 +207,10 @@ module ActionView
# date_select("article", "written_on", :start_year => 1995, :use_month_numbers => true,
# :discard_day => true, :include_blank => true)
#
+ # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute,
+ # # with two digit numbers used for months and days.
+ # date_select("article", "written_on", :use_two_digit_numbers => true)
+ #
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
# # with the fields ordered as day, month, year rather than month, day, year.
# date_select("article", "written_on", :order => [:day, :month, :year])
@@ -213,7 +235,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
# all month choices are valid.
def date_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
+ Tags::DateSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
@@ -227,7 +249,6 @@ module ActionView
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
- # ==== Examples
# # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute.
# time_select("article", "sunrise")
#
@@ -251,7 +272,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
# all month choices are valid.
def time_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
+ Tags::TimeSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a
@@ -260,7 +281,6 @@ module ActionView
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
- # ==== Examples
# # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on
# # attribute.
# datetime_select("article", "written_on")
@@ -287,7 +307,7 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
+ Tags::DatetimeSelect.new(object_name, method, self, options, html_options).render
end
# Returns a set of html select-tags (one for year, month, day, hour, minute, and second) pre-selected with the
@@ -299,7 +319,6 @@ module ActionView
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
- # ==== Examples
# my_date_time = Time.now + 4.days
#
# # Generates a datetime select that defaults to the datetime in my_date_time (four days after today).
@@ -336,7 +355,6 @@ module ActionView
# select_datetime(my_date_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_datetime(my_date_time, :prompt => {:hour => true}) # generic prompt for hours
# select_datetime(my_date_time, :prompt => true) # generic prompts for all
- #
def select_datetime(datetime = Time.current, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_datetime
end
@@ -348,7 +366,6 @@ module ActionView
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
- # ==== Examples
# my_date = Time.now + 6.days
#
# # Generates a date select that defaults to the date in my_date (six days after today).
@@ -377,7 +394,6 @@ module ActionView
# select_date(my_date, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_date(my_date, :prompt => {:hour => true}) # generic prompt for hours
# select_date(my_date, :prompt => true) # generic prompts for all
- #
def select_date(date = Date.current, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_date
end
@@ -388,7 +404,6 @@ module ActionView
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
- # ==== Examples
# my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds
#
# # Generates a time select that defaults to the time in my_time.
@@ -416,16 +431,14 @@ module ActionView
# select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'})
# select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours
# select_time(my_time, :prompt => true) # generic prompts for all
- #
def select_time(datetime = Time.current, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_time
end
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
- # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
+ # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'second' by default.
#
- # ==== Examples
# my_time = Time.now + 16.minutes
#
# # Generates a select field for seconds that defaults to the seconds for the time in my_time.
@@ -441,17 +454,15 @@ module ActionView
# # Generates a select field for seconds with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_second(14, :prompt => 'Choose seconds')
- #
def select_second(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_second
end
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
# Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute
- # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
+ # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
#
- # ==== Examples
# my_time = Time.now + 6.hours
#
# # Generates a select field for minutes that defaults to the minutes for the time in my_time.
@@ -467,16 +478,14 @@ module ActionView
# # Generates a select field for minutes with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_minute(14, :prompt => 'Choose minutes')
- #
def select_minute(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_minute
end
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
- # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
+ # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer.
# Override the field name using the <tt>:field_name</tt> option, 'hour' by default.
#
- # ==== Examples
# my_time = Time.now + 6.hours
#
# # Generates a select field for hours that defaults to the hour for the time in my_time.
@@ -495,16 +504,15 @@ module ActionView
#
# # Generate a select field for hours in the AM/PM format
# select_hour(my_time, :ampm => true)
- #
def select_hour(datetime, options = {}, html_options = {})
DateTimeSelector.new(datetime, options, html_options).select_hour
end
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
# The <tt>date</tt> can also be substituted for a day number.
+ # If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
# Override the field name using the <tt>:field_name</tt> option, 'day' by default.
#
- # ==== Examples
# my_date = Time.now + 2.days
#
# # Generates a select field for days that defaults to the day for the date in my_date.
@@ -513,6 +521,9 @@ module ActionView
# # Generates a select field for days that defaults to the number given.
# select_day(5)
#
+ # # Generates a select field for days that defaults to the number given, but displays it with two digits.
+ # select_day(5, :use_two_digit_numbers => true)
+ #
# # Generates a select field for days that defaults to the day for the date in my_date
# # that is named 'due' rather than 'day'.
# select_day(my_time, :field_name => 'due')
@@ -520,7 +531,6 @@ module ActionView
# # Generates a select field for days with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_day(5, :prompt => 'Choose day')
- #
def select_day(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_day
end
@@ -532,9 +542,9 @@ module ActionView
# want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer
# to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want
# to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names.
+ # If you want to display months with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true.
# Override the field name using the <tt>:field_name</tt> option, 'month' by default.
#
- # ==== Examples
# # Generates a select field for months that defaults to the current month that
# # will use keys like "January", "March".
# select_month(Date.today)
@@ -559,10 +569,13 @@ module ActionView
# # will use keys like "Januar", "Marts."
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
+ # # Generates a select field for months that defaults to the current month that
+ # # will use keys with two digit numbers like "01", "03".
+ # select_month(Date.today, :use_two_digit_numbers => true)
+ #
# # Generates a select field for months with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_month(14, :prompt => 'Choose month')
- #
def select_month(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_month
end
@@ -573,7 +586,6 @@ module ActionView
# greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number.
# Override the field name using the <tt>:field_name</tt> option, 'year' by default.
#
- # ==== Examples
# # Generates a select field for years that defaults to the current year that
# # has ascending year values.
# select_year(Date.today, :start_year => 1992, :end_year => 2007)
@@ -593,14 +605,12 @@ module ActionView
# # Generates a select field for years with a custom prompt. Use <tt>:prompt => true</tt> for a
# # generic prompt.
# select_year(14, :prompt => 'Choose year')
- #
def select_year(date, options = {}, html_options = {})
DateTimeSelector.new(date, options, html_options).select_year
end
# Returns an html time tag for the given date or time.
#
- # ==== Examples
# time_tag Date.today # =>
# <time datetime="2010-11-04">November 04, 2010</time>
# time_tag Time.now # =>
@@ -610,13 +620,17 @@ module ActionView
# time_tag Date.today, :pubdate => true # =>
# <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
#
- def time_tag(date_or_time, *args)
+ # <%= time_tag Time.now do %>
+ # <span>Right now</span>
+ # <% end %>
+ # # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time>
+ def time_tag(date_or_time, *args, &block)
options = args.extract_options!
format = options.delete(:format) || :long
content = args.first || I18n.l(date_or_time, :format => format)
datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339
- content_tag(:time, content, options.reverse_merge(:datetime => datetime))
+ content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block)
end
end
@@ -654,11 +668,7 @@ module ActionView
@options[:discard_minute] ||= true if @options[:discard_hour]
@options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
- # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
- # valid (otherwise it could be 31 and February wouldn't be a valid date)
- if @datetime && @options[:discard_day] && !@options[:discard_month]
- @datetime = @datetime.change(:day => 1)
- end
+ set_day_if_discarded
if @options[:tag] && @options[:ignore_date]
select_time
@@ -681,11 +691,7 @@ module ActionView
@options[:discard_month] ||= true unless order.include?(:month)
@options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
- # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
- # valid (otherwise it could be 31 and February wouldn't be a valid date)
- if @datetime && @options[:discard_day] && !@options[:discard_month]
- @datetime = @datetime.change(:day => 1)
- end
+ set_day_if_discarded
[:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
@@ -734,15 +740,15 @@ module ActionView
def select_day
if @options[:use_hidden] || @options[:discard_day]
- build_hidden(:day, day)
+ build_hidden(:day, day || 1)
else
- build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false)
+ build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers])
end
end
def select_month
if @options[:use_hidden] || @options[:discard_month]
- build_hidden(:month, month)
+ build_hidden(:month, month || 1)
else
month_options = []
1.upto(12) do |month_number|
@@ -756,7 +762,7 @@ module ActionView
def select_year
if !@datetime || @datetime == 0
- val = ''
+ val = '1'
middle_year = Date.today.year
else
val = middle_year = year
@@ -783,7 +789,15 @@ module ActionView
private
%w( sec min hour day month year ).each do |method|
define_method(method) do
- @datetime.kind_of?(Fixnum) ? @datetime : @datetime.send(method) if @datetime
+ @datetime.kind_of?(Numeric) ? @datetime : @datetime.send(method) if @datetime
+ end
+ end
+
+ # If the day is hidden, the day should be set to the 1st so all month and year choices are
+ # valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid.
+ def set_day_if_discarded
+ if @datetime && @options[:discard_day]
+ @datetime = @datetime.change(:day => 1)
end
end
@@ -817,11 +831,16 @@ module ActionView
# If <tt>:use_month_numbers</tt> option is passed
# month_name(1) => 1
#
+ # If <tt>:use_two_month_numbers</tt> option is passed
+ # month_name(1) => '01'
+ #
# If <tt>:add_month_numbers</tt> option is passed
# month_name(1) => "1 - January"
def month_name(number)
if @options[:use_month_numbers]
number
+ elsif @options[:use_two_digit_numbers]
+ sprintf "%02d", number
elsif @options[:add_month_numbers]
"#{number} - #{month_names[number]}"
else
@@ -834,7 +853,15 @@ module ActionView
end
def translated_date_order
- I18n.translate(:'date.order', :locale => @options[:locale]) || []
+ date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
+
+ forbidden_elements = date_order - [:year, :month, :day]
+ if forbidden_elements.any?
+ raise StandardError,
+ "#{@options[:locale]}.date.order only accepts :year, :month and :day"
+ end
+
+ date_order
end
# Build full select tag from date type and options.
@@ -848,6 +875,12 @@ module ActionView
# <option value="2">2</option>
# <option value="3">3</option>..."
#
+ # If <tt>:use_two_digit_numbers => true</tt> option is passed
+ # build_options(15, :start => 1, :end => 31, :use_two_digit_numbers => true)
+ # => "<option value="1">01</option>
+ # <option value="2">02</option>
+ # <option value="3">03</option>..."
+ #
# If <tt>:step</tt> options is passed
# build_options(15, :start => 1, :end => 31, :step => 2)
# => "<option value="1">1</option>
@@ -857,7 +890,7 @@ module ActionView
start = options.delete(:start) || 0
stop = options.delete(:end) || 59
step = options.delete(:step) || 1
- options.reverse_merge!({:leading_zeros => true, :ampm => false})
+ options.reverse_merge!({:leading_zeros => true, :ampm => false, :use_two_digit_numbers => false})
leading_zeros = options.delete(:leading_zeros)
select_options = []
@@ -865,7 +898,8 @@ module ActionView
value = leading_zeros ? sprintf("%02d", i) : i
tag_options = { :value => value }
tag_options[:selected] = "selected" if selected == i
- text = options[:ampm] ? AMPM_TRANSLATION[i] : value
+ text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
+ text = options[:ampm] ? AMPM_TRANSLATION[i] : text
select_options << content_tag(:option, text, tag_options)
end
(select_options.join("\n") + "\n").html_safe
@@ -937,15 +971,19 @@ module ActionView
# Returns the id attribute for the input tag.
# => "post_written_on_1i"
def input_id_from_type(type)
- input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
+ id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
+ id = @options[:namespace] + '_' + id if @options[:namespace]
+
+ id
end
# Given an ordering of datetime components, create the selection HTML
# and join them with their appropriate separators.
def build_selects_from_types(order)
select = ''
+ first_visible = order.find { |type| !@options[:"discard_#{type}"] }
order.reverse.each do |type|
- separator = separator(type) unless type == order.first # don't add on last field
+ separator = separator(type) unless type == first_visible # don't add before first visible field
select.insert(0, separator.to_s + send("select_#{type}").to_s)
end
select.html_safe
@@ -953,75 +991,15 @@ module ActionView
# Returns the separator for a given datetime component.
def separator(type)
+ return "" if @options[:use_hidden]
+
case type
- when :year
- @options[:discard_year] ? "" : @options[:date_separator]
- when :month
- @options[:discard_month] ? "" : @options[:date_separator]
- when :day
- @options[:discard_day] ? "" : @options[:date_separator]
+ when :year, :month, :day
+ @options[:"discard_#{type}"] ? "" : @options[:date_separator]
when :hour
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
- when :minute
- @options[:discard_minute] ? "" : @options[:time_separator]
- when :second
- @options[:include_seconds] ? @options[:time_separator] : ""
- end
- end
- end
-
- class InstanceTag #:nodoc:
- def to_date_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_date.html_safe
- end
-
- def to_time_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_time.html_safe
- end
-
- def to_datetime_select_tag(options = {}, html_options = {})
- datetime_selector(options, html_options).select_datetime.html_safe
- end
-
- private
- def datetime_selector(options, html_options)
- datetime = value(object) || default_datetime(options)
- @auto_index ||= nil
-
- options = options.dup
- options[:field_name] = @method_name
- options[:include_position] = true
- options[:prefix] ||= @object_name
- options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
-
- DateTimeSelector.new(datetime, options, html_options)
- end
-
- def default_datetime(options)
- return if options[:include_blank] || options[:prompt]
-
- case options[:default]
- when nil
- Time.current
- when Date, Time
- options[:default]
- else
- default = options[:default].dup
-
- # Rename :minute and :second to :min and :sec
- default[:min] ||= default[:minute]
- default[:sec] ||= default[:second]
-
- time = Time.current
-
- [:year, :month, :day, :hour, :min, :sec].each do |key|
- default[key] ||= time.send(key)
- end
-
- Time.utc_time(
- default[:year], default[:month], default[:day],
- default[:hour], default[:min], default[:sec]
- )
+ when :minute, :second
+ @options[:"discard_#{type}"] ? "" : @options[:time_separator]
end
end
end
diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb
index c0cc7d347c..878a8734a4 100644
--- a/actionpack/lib/action_view/helpers/debug_helper.rb
+++ b/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -8,8 +8,6 @@ module ActionView
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
# Useful for inspecting an object at the time of rendering.
#
- # ==== Example
- #
# @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %>
# debug(@user)
# # =>
@@ -25,7 +23,6 @@ module ActionView
#
# new_record: true
# </pre>
-
def debug(object)
begin
Marshal::dump(object)
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index f22c466666..6510610034 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -2,12 +2,16 @@ require 'cgi'
require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
require 'action_view/helpers/form_tag_helper'
+require 'action_view/helpers/active_model_helper'
+require 'action_view/helpers/tags'
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/module/method_names'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/array/extract_options'
+require 'active_support/deprecation'
+require 'active_support/core_ext/string/inflections'
module ActionView
# = Action View Form Helpers
@@ -15,17 +19,28 @@ module ActionView
# Form helpers are designed to make working with resources much easier
# compared to using vanilla HTML.
#
- # Forms for models are created with +form_for+. That method yields a form
- # builder that knows the model the form is about. The form builder is thus
- # able to generate default values for input fields that correspond to model
- # attributes, and also convenient names, IDs, endpoints, etc.
+ # Typically, a form designed to create or update a resource reflects the
+ # identity of the resource in several ways: (i) the url that the form is
+ # sent to (the form element's +action+ attribute) should result in a request
+ # being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
+ # parameter in the case of an existing resource), (ii) input fields should
+ # be named in such a way that in the controller their values appear in the
+ # appropriate places within the +params+ hash, and (iii) for an existing record,
+ # when the form is initially displayed, input fields corresponding to attributes
+ # of the resource should show the current values of those attributes.
#
- # Conventions in the generated field names allow controllers to receive form
- # data nicely structured in +params+ with no effort on your side.
+ # In Rails, this is usually achieved by creating the form using +form_for+ and
+ # a number of related helper methods. +form_for+ generates an appropriate <tt>form</tt>
+ # tag and yields a form builder object that knows the model the form is about.
+ # Input fields are created by calling methods defined on the form builder, which
+ # means they are able to generate the appropriate names and default values
+ # corresponding to the model attributes, as well as convenient IDs, etc.
+ # Conventions in the generated field names allow controllers to receive form data
+ # nicely structured in +params+ with no effort on your side.
#
# For example, to create a new person you typically set up a new instance of
# +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and
- # pass it to +form_for+:
+ # in the view template pass that object to +form_for+:
#
# <%= form_for @person do |f| %>
# <%= f.label :first_name %>:
@@ -44,10 +59,10 @@ module ActionView
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# </div>
# <label for="person_first_name">First name</label>:
- # <input id="person_first_name" name="person[first_name]" size="30" type="text" /><br />
+ # <input id="person_first_name" name="person[first_name]" type="text" /><br />
#
# <label for="person_last_name">Last name</label>:
- # <input id="person_last_name" name="person[last_name]" size="30" type="text" /><br />
+ # <input id="person_last_name" name="person[last_name]" type="text" /><br />
#
# <input name="commit" type="submit" value="Create Person" />
# </form>
@@ -75,10 +90,10 @@ module ActionView
# <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
# </div>
# <label for="person_first_name">First name</label>:
- # <input id="person_first_name" name="person[first_name]" size="30" type="text" value="John" /><br />
+ # <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br />
#
# <label for="person_last_name">Last name</label>:
- # <input id="person_last_name" name="person[last_name]" size="30" type="text" value="Smith" /><br />
+ # <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br />
#
# <input name="commit" type="submit" value="Update Person" />
# </form>
@@ -108,29 +123,14 @@ module ActionView
object.respond_to?(:to_model) ? object.to_model : object
end
- # Creates a form and a scope around a specific model object that is used
- # as a base for questioning about values for the fields.
+ # Creates a form that allows the user to create or update the attributes
+ # of a specific model object.
#
- # Rails provides succinct resource-oriented form generation with +form_for+
- # like this:
- #
- # <%= form_for @offer do |f| %>
- # <%= f.label :version, 'Version' %>:
- # <%= f.text_field :version %><br />
- # <%= f.label :author, 'Author' %>:
- # <%= f.text_field :author %><br />
- # <%= f.submit %>
- # <% end %>
- #
- # There, +form_for+ is able to generate the rest of RESTful form
- # parameters based on introspection on the record, but to understand what
- # it does we need to dig first into the alternative generic usage it is
- # based upon.
- #
- # === Generic form_for
- #
- # The generic way to call +form_for+ yields a form builder around a
- # model:
+ # The method can be used in several slightly different ways, depending on
+ # how much you wish to rely on Rails to infer automatically from the model
+ # how the form should be constructed. For a generic model object, a form
+ # can be created by passing +form_for+ a string or symbol representing
+ # the object we are concerned with:
#
# <%= form_for :person do |f| %>
# First name: <%= f.text_field :first_name %><br />
@@ -140,35 +140,53 @@ module ActionView
# <%= f.submit %>
# <% end %>
#
- # There, the argument is a symbol or string with the name of the
- # object the form is about.
- #
- # The form builder acts as a regular form helper that somehow carries the
- # model. Thus, the idea is that
+ # The variable +f+ yielded to the block is a FormBuilder object that
+ # incorporates the knowledge about the model object represented by
+ # <tt>:person</tt> passed to +form_for+. Methods defined on the FormBuilder
+ # are used to generate fields bound to this model. Thus, for example,
#
# <%= f.text_field :first_name %>
#
- # gets expanded to
+ # will get expanded to
#
# <%= text_field :person, :first_name %>
+ # which results in an html <tt><input></tt> tag whose +name+ attribute is
+ # <tt>person[first_name]</tt>. This means that when the form is submitted,
+ # the value entered by the user will be available in the controller as
+ # <tt>params[:person][:first_name]</tt>.
+ #
+ # For fields generated in this way using the FormBuilder,
+ # if <tt>:person</tt> also happens to be the name of an instance variable
+ # <tt>@person</tt>, the default value of the field shown when the form is
+ # initially displayed (e.g. in the situation where you are editing an
+ # existing record) will be the value of the corresponding attribute of
+ # <tt>@person</tt>.
#
# The rightmost argument to +form_for+ is an
- # optional hash of options:
- #
- # * <tt>:url</tt> - The URL the form is submitted to. It takes the same
- # fields you pass to +url_for+ or +link_to+. In particular you may pass
- # here a named route directly as well. Defaults to the current action.
+ # optional hash of options -
+ #
+ # * <tt>:url</tt> - The URL the form is to be submitted to. This may be
+ # represented in the same way as values passed to +url_for+ or +link_to+.
+ # So for example you may use a named route directly. When the model is
+ # represented by a string or symbol, as in the example above, if the
+ # <tt>:url</tt> option is not specified, by default the form will be
+ # sent back to the current url (We will describe below an alternative
+ # resource-oriented usage of +form_for+ in which the URL does not need
+ # to be specified explicitly).
+ # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
+ # id attributes on form elements. The namespace attribute will be prefixed
+ # with underscore on the generated HTML id.
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
#
# Also note that +form_for+ doesn't create an exclusive scope. It's still
# possible to use both the stand-alone FormHelper methods and methods
# from FormTagHelper. For example:
#
- # <%= form_for @person do |f| %>
+ # <%= form_for :person do |f| %>
# First name: <%= f.text_field :first_name %>
# Last name : <%= f.text_field :last_name %>
# Biography : <%= text_area :person, :biography %>
- # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
+ # Admin? : <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
# <%= f.submit %>
# <% end %>
#
@@ -176,26 +194,65 @@ module ActionView
# are designed to work with an object as base, like
# FormOptionHelper#collection_select and DateHelper#datetime_select.
#
- # === Resource-oriented style
+ # === #form_for with a model object
+ #
+ # In the examples above, the object to be created or edited was
+ # represented by a symbol passed to +form_for+, and we noted that
+ # a string can also be used equivalently. It is also possible, however,
+ # to pass a model object itself to +form_for+. For example, if <tt>@post</tt>
+ # is an existing record you wish to edit, you can create the form using
+ #
+ # <%= form_for @post do |f| %>
+ # ...
+ # <% end %>
+ #
+ # This behaves in almost the same way as outlined previously, with a
+ # couple of small exceptions. First, the prefix used to name the input
+ # elements within the form (hence the key that denotes them in the +params+
+ # hash) is actually derived from the object's _class_, e.g. <tt>params[:post]</tt>
+ # if the object's class is +Post+. However, this can be overwritten using
+ # the <tt>:as</tt> option, e.g. -
+ #
+ # <%= form_for(@person, :as => :client) do |f| %>
+ # ...
+ # <% end %>
+ #
+ # would result in <tt>params[:client]</tt>.
+ #
+ # Secondly, the field values shown when the form is initially displayed
+ # are taken from the attributes of the object passed to +form_for+,
+ # regardless of whether the object is an instance
+ # variable. So, for example, if we had a _local_ variable +post+
+ # representing an existing record,
+ #
+ # <%= form_for post do |f| %>
+ # ...
+ # <% end %>
+ #
+ # would produce a form with fields whose initial state reflect the current
+ # values of the attributes of +post+.
#
- # As we said above, in addition to manually configuring the +form_for+
- # call, you can rely on automated resource identification, which will use
- # the conventions and named routes of that approach. This is the
- # preferred way to use +form_for+ nowadays.
+ # === Resource-oriented style
#
- # For example, if <tt>@post</tt> is an existing record you want to edit
+ # In the examples just shown, although not indicated explicitly, we still
+ # need to use the <tt>:url</tt> option in order to specify where the
+ # form is going to be sent. However, further simplification is possible
+ # if the record passed to +form_for+ is a _resource_, i.e. it corresponds
+ # to a set of RESTful routes, e.g. defined using the +resources+ method
+ # in <tt>config/routes.rb</tt>. In this case Rails will simply infer the
+ # appropriate URL from the record itself. For example,
#
# <%= form_for @post do |f| %>
# ...
# <% end %>
#
- # is equivalent to something like:
+ # is then equivalent to something like:
#
# <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
# ...
# <% end %>
#
- # And for new records
+ # And for a new record
#
# <%= form_for(Post.new) do |f| %>
# ...
@@ -207,7 +264,7 @@ module ActionView
# ...
# <% end %>
#
- # You can also overwrite the individual conventions, like this:
+ # However you can still overwrite individual conventions, such as:
#
# <%= form_for(@post, :url => super_posts_path) do |f| %>
# ...
@@ -219,13 +276,6 @@ module ActionView
# ...
# <% end %>
#
- # If you have an object that needs to be represented as a different
- # parameter, like a Person that acts as a Client:
- #
- # <%= form_for(@person, :as => :client) do |f| %>
- # ...
- # <% end %>
- #
# For namespaced routes, like +admin_post_url+:
#
# <%= form_for([:admin, @post]) do |f| %>
@@ -246,11 +296,11 @@ module ActionView
#
# You can force the form to use the full array of HTTP verbs by setting
#
- # :method => (:get|:post|:put|:delete)
+ # :method => (:get|:post|:patch|:put|:delete)
#
- # in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
- # form will be set to POST and a hidden input called _method will carry the intended verb for the server
- # to interpret.
+ # in the options hash. If the verb is not GET or POST, which are natively
+ # supported by HTML forms, the form will be set to POST and a hidden input
+ # called _method will carry the intended verb for the server to interpret.
#
# === Unobtrusive JavaScript
#
@@ -362,34 +412,33 @@ module ActionView
else
object = record.is_a?(Array) ? record.last : record
object_name = options[:as] || ActiveModel::Naming.param_key(object)
- apply_form_for_options!(record, options)
+ apply_form_for_options!(record, object, options)
end
options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
options[:html][:method] = options.delete(:method) if options.has_key?(:method)
options[:html][:authenticity_token] = options.delete(:authenticity_token)
- builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)
+ builder = options[:parent_builder] = instantiate_builder(object_name, object, options)
fields_for = fields_for(object_name, object, options, &proc)
default_options = builder.multipart? ? { :multipart => true } : {}
- output = form_tag(options.delete(:url) || {}, default_options.merge!(options.delete(:html)))
- output << fields_for
- output.safe_concat('</form>')
+ default_options.merge!(options.delete(:html))
+
+ form_tag(options.delete(:url) || {}, default_options) { fields_for }
end
- def apply_form_for_options!(object_or_array, options) #:nodoc:
- object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
+ def apply_form_for_options!(record, object, options) #:nodoc:
object = convert_to_model(object)
as = options[:as]
- action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
+ action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
options[:html].reverse_merge!(
- :class => as ? "#{as}_#{action}" : dom_class(object, action),
- :id => as ? "#{as}_#{action}" : dom_id(object, action),
+ :class => as ? "#{action}_#{as}" : dom_class(object, action),
+ :id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
:method => method
)
- options[:url] ||= polymorphic_path(object_or_array, :format => options.delete(:format))
+ options[:url] ||= polymorphic_path(record, :format => options.delete(:format))
end
private :apply_form_for_options!
@@ -399,30 +448,59 @@ module ActionView
#
# === Generic Examples
#
+ # Although the usage and purpose of +field_for+ is similar to +form_for+'s,
+ # its method signature is slightly different. Like +form_for+, it yields
+ # a FormBuilder object associated with a particular model object to a block,
+ # and within the block allows methods to be called on the builder to
+ # generate fields associated with the model object. Fields may reflect
+ # a model object in two ways - how they are named (hence how submitted
+ # values appear within the +params+ hash in the controller) and what
+ # default values are shown when the form the fields appear in is first
+ # displayed. In order for both of these features to be specified independently,
+ # both an object name (represented by either a symbol or string) and the
+ # object itself can be passed to the method separately -
+ #
# <%= form_for @person do |person_form| %>
# First name: <%= person_form.text_field :first_name %>
# Last name : <%= person_form.text_field :last_name %>
#
- # <%= fields_for @person.permission do |permission_fields| %>
+ # <%= fields_for :permission, @person.permission do |permission_fields| %>
# Admin? : <%= permission_fields.check_box :admin %>
# <% end %>
#
# <%= f.submit %>
# <% end %>
#
- # ...or if you have an object that needs to be represented as a different
- # parameter, like a Client that acts as a Person:
+ # In this case, the checkbox field will be represented by an HTML +input+
+ # tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted
+ # value will appear in the controller as <tt>params[:permission][:admin]</tt>.
+ # If <tt>@person.permission</tt> is an existing record with an attribute
+ # +admin+, the initial state of the checkbox when first displayed will
+ # reflect the value of <tt>@person.permission.admin</tt>.
+ #
+ # Often this can be simplified by passing just the name of the model
+ # object to +fields_for+ -
#
- # <%= fields_for :person, @client do |permission_fields| %>
+ # <%= fields_for :permission do |permission_fields| %>
# Admin?: <%= permission_fields.check_box :admin %>
# <% end %>
#
- # ...or if you don't have an object, just a name of the parameter:
+ # ...in which case, if <tt>:permission</tt> also happens to be the name of an
+ # instance variable <tt>@permission</tt>, the initial state of the input
+ # field will reflect the value of that variable's attribute <tt>@permission.admin</tt>.
#
- # <%= fields_for :person do |permission_fields| %>
+ # Alternatively, you can pass just the model object itself (if the first
+ # argument isn't a string or symbol +fields_for+ will realize that the
+ # name has been omitted) -
+ #
+ # <%= fields_for @person.permission do |permission_fields| %>
# Admin?: <%= permission_fields.check_box :admin %>
# <% end %>
#
+ # and +fields_for+ will derive the required name of the field from the
+ # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
+ # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
+ #
# Note: This also works for the methods in FormOptionHelper and
# DateHelper that are designed to work with an object as base, like
# FormOptionHelper#collection_select and DateHelper#datetime_select.
@@ -597,8 +675,21 @@ module ActionView
# <% end %>
# ...
# <% end %>
+ #
+ # When a collection is used you might want to know the index of each
+ # object into the array. For this purpose, the <tt>index</tt> method
+ # is available in the FormBuilder object.
+ #
+ # <%= form_for @person do |person_form| %>
+ # ...
+ # <%= person_form.fields_for :projects do |project_fields| %>
+ # Project #<%= project_fields.index %>
+ # ...
+ # <% end %>
+ # ...
+ # <% end %>
def fields_for(record_name, record_object = nil, options = {}, &block)
- builder = instantiate_builder(record_name, record_object, options, &block)
+ builder = instantiate_builder(record_name, record_object, options)
output = capture(builder, &block)
output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
output
@@ -649,19 +740,10 @@ module ActionView
# # => <label for="post_privacy_public">Public Post</label>
#
# label(:post, :terms) do
- # 'Accept <a href="/terms">Terms</a>.'
+ # 'Accept <a href="/terms">Terms</a>.'.html_safe
# end
def label(object_name, method, content_or_options = nil, options = nil, &block)
- content_is_options = content_or_options.is_a?(Hash)
- if content_is_options || block_given?
- options = content_or_options if content_is_options
- text = nil
- else
- text = content_or_options
- end
-
- options ||= {}
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block)
+ Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -683,7 +765,7 @@ module ActionView
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
#
def text_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
+ Tags::TextField.new(object_name, method, self, options).render
end
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -705,7 +787,7 @@ module ActionView
# # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
#
def password_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", { :value => nil }.merge!(options))
+ Tags::PasswordField.new(object_name, method, self, options).render
end
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -723,7 +805,7 @@ module ActionView
# hidden_field(:user, :token)
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
def hidden_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
+ Tags::HiddenField.new(object_name, method, self, options).render
end
# Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -738,13 +820,13 @@ module ActionView
# # => <input type="file" id="user_avatar" name="user[avatar]" />
#
# file_field(:post, :attached, :accept => 'text/html')
- # # => <input type="file" id="post_attached" name="post[attached]" />
+ # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
#
# file_field(:attachment, :file, :class => 'file_input')
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
#
def file_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
+ Tags::FileField.new(object_name, method, self, options).render
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -772,7 +854,7 @@ module ActionView
# # #{@entry.body}
# # </textarea>
def text_area(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
+ Tags::TextArea.new(object_name, method, self, options).render
end
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -818,11 +900,10 @@ module ActionView
# In that case it is preferable to either use +check_box_tag+ or to use
# hashes instead of arrays.
#
- # ==== Examples
# # Let's say that @post.validated? is 1:
# check_box("post", "validated")
# # => <input name="post[validated]" type="hidden" value="0" />
- # # <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
+ # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" />
#
# # Let's say that @puppy.gooddog is "no":
# check_box("puppy", "gooddog", {}, "yes", "no")
@@ -834,7 +915,7 @@ module ActionView
# # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
#
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
+ Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
end
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
@@ -844,7 +925,6 @@ module ActionView
# To force the radio button to be checked pass <tt>:checked => true</tt> in the
# +options+ hash. You may pass HTML options there as well.
#
- # ==== Examples
# # Let's say that @post.category returns "rails":
# radio_button("post", "category", "rails")
# radio_button("post", "category", "java")
@@ -856,74 +936,93 @@ module ActionView
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
def radio_button(object_name, method, tag_value, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
+ Tags::RadioButton.new(object_name, method, self, tag_value, options).render
end
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
# some browsers.
#
- # ==== Examples
- #
# search_field(:user, :name)
- # # => <input id="user_name" name="user[name]" size="30" type="search" />
+ # # => <input id="user_name" name="user[name]" type="search" />
# search_field(:user, :name, :autosave => false)
- # # => <input autosave="false" id="user_name" name="user[name]" size="30" type="search" />
+ # # => <input autosave="false" id="user_name" name="user[name]" type="search" />
# search_field(:user, :name, :results => 3)
- # # => <input id="user_name" name="user[name]" results="3" size="30" type="search" />
+ # # => <input id="user_name" name="user[name]" results="3" type="search" />
# # Assume request.host returns "www.example.com"
# search_field(:user, :name, :autosave => true)
- # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" size="30" type="search" />
+ # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" />
# search_field(:user, :name, :onsearch => true)
- # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
+ # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
# search_field(:user, :name, :autosave => false, :onsearch => true)
- # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" />
+ # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
# search_field(:user, :name, :autosave => true, :onsearch => true)
- # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" size="30" type="search" />
- #
+ # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" />
def search_field(object_name, method, options = {})
- options = options.stringify_keys
-
- if options["autosave"]
- if options["autosave"] == true
- options["autosave"] = request.host.split(".").reverse.join(".")
- end
- options["results"] ||= 10
- end
-
- if options["onsearch"]
- options["incremental"] = true unless options.has_key?("incremental")
- end
-
- InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
+ Tags::SearchField.new(object_name, method, self, options).render
end
# Returns a text_field of type "tel".
#
# telephone_field("user", "phone")
- # # => <input id="user_phone" name="user[phone]" size="30" type="tel" />
+ # # => <input id="user_phone" name="user[phone]" type="tel" />
#
def telephone_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
+ Tags::TelField.new(object_name, method, self, options).render
end
alias phone_field telephone_field
+ # Returns a text_field of type "date".
+ #
+ # date_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" />
+ #
+ # The default value is generated by trying to call "to_date"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone. You can still override that
+ # by passing the "value" option explicitly, e.g.
+ #
+ # @user.born_on = Date.new(1984, 1, 27)
+ # date_field("user", "born_on", value: "1984-05-12")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
+ #
+ def date_field(object_name, method, options = {})
+ Tags::DateField.new(object_name, method, self, options).render
+ end
+
+ # Returns a text_field of type "time".
+ #
+ # The default value is generated by trying to call +strftime+ with "%T.%L"
+ # on the objects's value. It is still possible to override that
+ # by passing the "value" option.
+ #
+ # === Options
+ # * Accepts same options as time_field_tag
+ #
+ # === Example
+ # time_field("task", "started_at")
+ # # => <input id="task_started_at" name="task[started_at]" type="time" />
+ #
+ def time_field(object_name, method, options = {})
+ Tags::TimeField.new(object_name, method, self, options).render
+ end
+
# Returns a text_field of type "url".
#
# url_field("user", "homepage")
- # # => <input id="user_homepage" size="30" name="user[homepage]" type="url" />
+ # # => <input id="user_homepage" name="user[homepage]" type="url" />
#
def url_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
+ Tags::UrlField.new(object_name, method, self, options).render
end
# Returns a text_field of type "email".
#
# email_field("user", "address")
- # # => <input id="user_address" size="30" name="user[address]" type="email" />
+ # # => <input id="user_address" name="user[address]" type="email" />
#
def email_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
+ Tags::EmailField.new(object_name, method, self, options).render
end
# Returns an input tag of type "number".
@@ -931,7 +1030,7 @@ module ActionView
# ==== Options
# * Accepts same options as number_field_tag
def number_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("number", options)
+ Tags::NumberField.new(object_name, method, self, options).render
end
# Returns an input tag of type "range".
@@ -939,12 +1038,12 @@ module ActionView
# ==== Options
# * Accepts same options as range_field_tag
def range_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
+ Tags::RangeField.new(object_name, method, self, options).render
end
private
- def instantiate_builder(record_name, record_object, options, &block)
+ def instantiate_builder(record_name, record_object, options)
case record_name
when String, Symbol
object = record_object
@@ -954,272 +1053,24 @@ module ActionView
object_name = ActiveModel::Naming.param_key(object)
end
- builder = options[:builder] || ActionView::Base.default_form_builder
- builder.new(object_name, object, self, options, block)
- end
- end
-
- class InstanceTag
- include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
-
- attr_reader :object, :method_name, :object_name
-
- DEFAULT_FIELD_OPTIONS = { "size" => 30 }
- DEFAULT_RADIO_OPTIONS = { }
- DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
-
- def initialize(object_name, method_name, template_object, object = nil)
- @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
- @template_object = template_object
- @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
- @object = retrieve_object(object)
- @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
- end
-
- def to_label_tag(text = nil, options = {}, &block)
- options = options.stringify_keys
- tag_value = options.delete("value")
- name_and_id = options.dup
-
- if name_and_id["for"]
- name_and_id["id"] = name_and_id["for"]
- else
- name_and_id.delete("id")
- end
-
- add_default_name_and_id_for_value(tag_value, name_and_id)
- options.delete("index")
- options["for"] ||= name_and_id["id"]
-
- if block_given?
- label_tag(name_and_id["id"], options, &block)
- else
- content = if text.blank?
- method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
- I18n.t("helpers.label.#{object_name}.#{method_and_value}", :default => "").presence
- else
- text.to_s
- end
-
- content ||= if object && object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(method_name)
- end
-
- content ||= method_name.humanize
-
- label_tag(name_and_id["id"], content, options)
- end
- end
-
- def to_input_field_tag(field_type, options = {})
- options = options.stringify_keys
- options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
- options = DEFAULT_FIELD_OPTIONS.merge(options)
- if field_type == "hidden"
- options.delete("size")
- end
- options["type"] ||= field_type
- options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
- options["value"] &&= ERB::Util.html_escape(options["value"])
- add_default_name_and_id(options)
- tag("input", options)
- end
-
- def to_number_field_tag(field_type, options = {})
- options = options.stringify_keys
- if range = options.delete("in") || options.delete("within")
- options.update("min" => range.min, "max" => range.max)
- end
- to_input_field_tag(field_type, options)
- end
-
- def to_radio_button_tag(tag_value, options = {})
- options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
- options["type"] = "radio"
- options["value"] = tag_value
- if options.has_key?("checked")
- cv = options.delete "checked"
- checked = cv == true || cv == "checked"
- else
- checked = self.class.radio_button_checked?(value(object), tag_value)
- end
- options["checked"] = "checked" if checked
- add_default_name_and_id_for_value(tag_value, options)
- tag("input", options)
- end
-
- def to_text_area_tag(options = {})
- options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
- add_default_name_and_id(options)
-
- if size = options.delete("size")
- options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
- end
-
- content_tag("textarea", ERB::Util.html_escape(options.delete('value') || value_before_type_cast(object)), options)
- end
-
- def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
- options = options.stringify_keys
- options["type"] = "checkbox"
- options["value"] = checked_value
- if options.has_key?("checked")
- cv = options.delete "checked"
- checked = cv == true || cv == "checked"
- else
- checked = self.class.check_box_checked?(value(object), checked_value)
- end
- options["checked"] = "checked" if checked
- if options["multiple"]
- add_default_name_and_id_for_value(checked_value, options)
- options.delete("multiple")
- else
- add_default_name_and_id(options)
- end
- hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
- checkbox = tag("input", options)
- (hidden + checkbox).html_safe
- end
-
- def to_boolean_select_tag(options = {})
- options = options.stringify_keys
- add_default_name_and_id(options)
- value = value(object)
- tag_text = "<select"
- tag_text << tag_options(options)
- tag_text << "><option value=\"false\""
- tag_text << " selected" if value == false
- tag_text << ">False</option><option value=\"true\""
- tag_text << " selected" if value
- tag_text << ">True</option></select>"
- end
-
- def to_content_tag(tag_name, options = {})
- content_tag(tag_name, value(object), options)
- end
-
- def retrieve_object(object)
- if object
- object
- elsif @template_object.instance_variable_defined?("@#{@object_name}")
- @template_object.instance_variable_get("@#{@object_name}")
- end
- rescue NameError
- # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
- nil
- end
-
- def retrieve_autoindex(pre_match)
- object = self.object || @template_object.instance_variable_get("@#{pre_match}")
- if object && object.respond_to?(:to_param)
- object.to_param
- else
- raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
- end
- end
-
- def value(object)
- self.class.value(object, @method_name)
- end
-
- def value_before_type_cast(object)
- self.class.value_before_type_cast(object, @method_name)
- end
-
- class << self
- def value(object, method_name)
- object.send method_name if object
- end
-
- def value_before_type_cast(object, method_name)
- unless object.nil?
- object.respond_to?(method_name + "_before_type_cast") ?
- object.send(method_name + "_before_type_cast") :
- object.send(method_name)
- end
- end
-
- def check_box_checked?(value, checked_value)
- case value
- when TrueClass, FalseClass
- value
- when NilClass
- false
- when Integer
- value != 0
- when String
- value == checked_value
- when Array
- value.include?(checked_value)
- else
- value.to_i != 0
- end
- end
-
- def radio_button_checked?(value, checked_value)
- value.to_s == checked_value.to_s
- end
- end
-
- private
- def add_default_name_and_id_for_value(tag_value, options)
- unless tag_value.nil?
- pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
- specified_id = options["id"]
- add_default_name_and_id(options)
- options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
- else
- add_default_name_and_id(options)
- end
- end
-
- def add_default_name_and_id(options)
- if options.has_key?("index")
- options["name"] ||= tag_name_with_index(options["index"])
- options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
- options.delete("index")
- elsif defined?(@auto_index)
- options["name"] ||= tag_name_with_index(@auto_index)
- options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
- else
- options["name"] ||= tag_name + (options['multiple'] ? '[]' : '')
- options["id"] = options.fetch("id"){ tag_id }
- end
- end
-
- def tag_name
- "#{@object_name}[#{sanitized_method_name}]"
- end
-
- def tag_name_with_index(index)
- "#{@object_name}[#{index}][#{sanitized_method_name}]"
- end
-
- def tag_id
- "#{sanitized_object_name}_#{sanitized_method_name}"
- end
-
- def tag_id_with_index(index)
- "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ builder = options[:builder] || default_form_builder
+ builder.new(object_name, object, self, options)
end
- def sanitized_object_name
- @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
- end
-
- def sanitized_method_name
- @sanitized_method_name ||= @method_name.sub(/\?$/,"")
+ def default_form_builder
+ builder = ActionView::Base.default_form_builder
+ builder.respond_to?(:constantize) ? builder.constantize : builder
end
end
class FormBuilder
# The methods which wrap a form helper call.
class_attribute :field_helpers
- self.field_helpers = FormHelper.instance_method_names - %w(form_for convert_to_model)
+ self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model]
attr_accessor :object_name, :object, :options
- attr_reader :multipart, :parent_builder
+ attr_reader :multipart, :parent_builder, :index
alias :multipart? :multipart
def multipart=(multipart)
@@ -1239,11 +1090,16 @@ module ActionView
self
end
- def initialize(object_name, object, template, options, proc)
+ def initialize(object_name, object, template, options, block=nil)
+ if block
+ ActiveSupport::Deprecation.warn(
+ "Giving a block to FormBuilder is deprecated and has no effect anymore.")
+ end
+
@nested_child_index = {}
- @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
+ @object_name, @object, @template, @options = object_name, object, template, options
@parent_builder = options[:parent_builder]
- @default_options = @options ? @options.slice(:index) : {}
+ @default_options = @options ? @options.slice(:index, :namespace) : {}
if @object_name.to_s.match(/\[\]$/)
if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
@auto_index = object.to_param
@@ -1252,9 +1108,10 @@ module ActionView
end
end
@multipart = nil
+ @index = options[:index] || options[:child_index]
end
- (field_helpers - %w(label check_box radio_button fields_for hidden_field file_field)).each do |selector|
+ (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{selector}(method, options = {}) # def text_field(method, options = {})
@template.send( # @template.send(
@@ -1270,6 +1127,7 @@ module ActionView
fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
fields_options[:builder] ||= options[:builder]
fields_options[:parent_builder] = self
+ fields_options[:namespace] = options[:namespace]
case record_name
when String, Symbol
@@ -1282,12 +1140,14 @@ module ActionView
end
index = if options.has_key?(:index)
- "[#{options[:index]}]"
+ options[:index]
elsif defined?(@auto_index)
self.object_name = @object_name.to_s.sub(/\[\]$/,"")
- "[#{@auto_index}]"
+ @auto_index
end
- record_name = "#{object_name}#{index}[#{record_name}]"
+
+ record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
+ fields_options[:child_index] = index
@template.fields_for(record_name, record_object, fields_options, &block)
end
@@ -1347,6 +1207,39 @@ module ActionView
@template.submit_tag(value, options)
end
+ # Add the submit button for the given form. When no value is given, it checks
+ # if the object is a new resource or not to create the proper label:
+ #
+ # <%= form_for @post do |f| %>
+ # <%= f.button %>
+ # <% end %>
+ #
+ # In the example above, if @post is a new record, it will use "Create Post" as
+ # button label, otherwise, it uses "Update Post".
+ #
+ # Those labels can be customized using I18n, under the helpers.submit key
+ # (the same as submit helper) and accept the %{model} as translation interpolation:
+ #
+ # en:
+ # helpers:
+ # submit:
+ # create: "Create a %{model}"
+ # update: "Confirm changes to %{model}"
+ #
+ # It also searches for a key specific for the given object:
+ #
+ # en:
+ # helpers:
+ # submit:
+ # post:
+ # create: "Add %{model}"
+ #
+ def button(value=nil, options={})
+ value, options = nil, value if value.is_a?(Hash)
+ value ||= submit_default_value
+ @template.button_tag(value, options)
+ end
+
def emitted_hidden_id?
@emitted_hidden_id ||= nil
end
@@ -1392,7 +1285,8 @@ module ActionView
explicit_child_index = options[:child_index]
output = ActiveSupport::SafeBuffer.new
association.each do |child|
- output << fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block)
+ options[:child_index] = nested_child_index(name) unless explicit_child_index
+ output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
end
output
elsif association
@@ -1421,9 +1315,6 @@ module ActionView
end
ActiveSupport.on_load(:action_view) do
- class ActionView::Base
- cattr_accessor :default_form_builder
- @@default_form_builder = ::ActionView::Helpers::FormBuilder
- end
+ cattr_accessor(:default_form_builder) { ::ActionView::Helpers::FormBuilder }
end
end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index d636702111..eef426703d 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -134,7 +134,7 @@ module ActionView
#
# ==== Gotcha
#
- # The HTML specification says when +multiple+ parameter passed to select and all options got deselected
+ # The HTML specification says when +multiple+ parameter passed to select and all options got deselected
# web browsers do not send any value to server. Unfortunately this introduces a gotcha:
# if an +User+ model has many +roles+ and have +role_ids+ accessor, and in the form that edits roles of the user
# the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So,
@@ -153,8 +153,10 @@ module ActionView
# form, and parameters extraction gets the last occurrence of any repeated
# key in the query string, that works for ordinary forms.
#
+ # In case if you don't want the helper to generate this hidden field you can specify <tt>:include_blank => false</tt> option.
+ #
def select(object, method, choices, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
+ Tags::Select.new(object, method, self, choices, options, html_options).render
end
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
@@ -164,7 +166,9 @@ module ActionView
#
# The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member
# of +collection+. The return values are used as the +value+ attribute and contents of each
- # <tt><option></tt> tag, respectively.
+ # <tt><option></tt> tag, respectively. They can also be any object that responds to +call+, such
+ # as a +proc+, that will be called for each member of the +collection+ to
+ # retrieve the value/text.
#
# Example object structure for use with this method:
# class Post < ActiveRecord::Base
@@ -188,10 +192,9 @@ module ActionView
# <option value="3">M. Clark</option>
# </select>
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
+ Tags::CollectionSelect.new(object, method, self, collection, value_method, text_method, options, html_options).render
end
-
# Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of
# +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will
# be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt>
@@ -240,7 +243,7 @@ module ActionView
# </select>
#
def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
+ Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render
end
# Return select and option tags for the given object and method, using
@@ -261,7 +264,6 @@ module ActionView
# Finally, this method supports a <tt>:default</tt> option, which selects
# a default ActiveSupport::TimeZone if the object's time zone is +nil+.
#
- # Examples:
# time_zone_select( "user", "time_zone", nil, :include_blank => true)
#
# time_zone_select( "user", "time_zone", nil, :default => "Pacific Time (US & Canada)" )
@@ -274,7 +276,7 @@ module ActionView
#
# time_zone_select( "user", "time_zone", ActiveSupport::TimeZone.all.sort, :model => ActiveSupport::TimeZone)
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
+ Tags::TimeZoneSelect.new(object, method, self, priority_zones, options, html_options).render
end
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
@@ -285,58 +287,77 @@ module ActionView
#
# Examples (call, result):
# options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
- # <option value="$">Dollar</option>\n<option value="DKK">Kroner</option>
+ # # <option value="$">Dollar</option>
+ # # <option value="DKK">Kroner</option>
#
# options_for_select([ "VISA", "MasterCard" ], "MasterCard")
- # <option>VISA</option>\n<option selected="selected">MasterCard</option>
+ # # <option>VISA</option>
+ # # <option selected="selected">MasterCard</option>
#
# options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
- # <option value="$20">Basic</option>\n<option value="$40" selected="selected">Plus</option>
+ # # <option value="$20">Basic</option>
+ # # <option value="$40" selected="selected">Plus</option>
#
# options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
- # <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option>
+ # # <option selected="selected">VISA</option>
+ # # <option>MasterCard</option>
+ # # <option selected="selected">Discover</option>
#
# You can optionally provide html attributes as the last element of the array.
#
# Examples:
# options_for_select([ "Denmark", ["USA", {:class => 'bold'}], "Sweden" ], ["USA", "Sweden"])
- # <option value="Denmark">Denmark</option>\n<option value="USA" class="bold" selected="selected">USA</option>\n<option value="Sweden" selected="selected">Sweden</option>
+ # # <option value="Denmark">Denmark</option>
+ # # <option value="USA" class="bold" selected="selected">USA</option>
+ # # <option value="Sweden" selected="selected">Sweden</option>
#
# options_for_select([["Dollar", "$", {:class => "bold"}], ["Kroner", "DKK", {:onclick => "alert('HI');"}]])
- # <option value="$" class="bold">Dollar</option>\n<option value="DKK" onclick="alert('HI');">Kroner</option>
+ # # <option value="$" class="bold">Dollar</option>
+ # # <option value="DKK" onclick="alert('HI');">Kroner</option>
#
# If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value
# or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags.
#
# Examples:
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => "Super Platinum")
- # <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # <option value="Free">Free</option>
+ # # <option value="Basic">Basic</option>
+ # # <option value="Advanced">Advanced</option>
+ # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :disabled => ["Advanced", "Super Platinum"])
- # <option value="Free">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced" disabled="disabled">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # <option value="Free">Free</option>
+ # # <option value="Basic">Basic</option>
+ # # <option value="Advanced" disabled="disabled">Advanced</option>
+ # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# options_for_select(["Free", "Basic", "Advanced", "Super Platinum"], :selected => "Free", :disabled => "Super Platinum")
- # <option value="Free" selected="selected">Free</option>\n<option value="Basic">Basic</option>\n<option value="Advanced">Advanced</option>\n<option value="Super Platinum" disabled="disabled">Super Platinum</option>
+ # # <option value="Free" selected="selected">Free</option>
+ # # <option value="Basic">Basic</option>
+ # # <option value="Advanced">Advanced</option>
+ # # <option value="Super Platinum" disabled="disabled">Super Platinum</option>
#
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
def options_for_select(container, selected = nil)
return container if String === container
- selected, disabled = extract_selected_and_disabled(selected).map do | r |
- Array.wrap(r).map { |item| item.to_s }
+ selected, disabled = extract_selected_and_disabled(selected).map do |r|
+ Array(r).map { |item| item.to_s }
end
container.map do |element|
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element).map { |item| item.to_s }
- selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
- disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
- %(<option value="#{ERB::Util.html_escape(value)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{ERB::Util.html_escape(text)}</option>)
- end.join("\n").html_safe
+ html_attributes[:selected] = 'selected' if option_value_selected?(value, selected)
+ html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled)
+ html_attributes[:value] = value
+
+ content_tag(:option, text, html_attributes)
+ end.join("\n").html_safe
end
- # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the
+ # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning
# the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
# Example:
# options_from_collection_for_select(@people, 'id', 'name')
@@ -362,12 +383,13 @@ module ActionView
# should produce the desired results.
def options_from_collection_for_select(collection, value_method, text_method, selected = nil)
options = collection.map do |element|
- [element.send(text_method), element.send(value_method)]
+ [value_for_collection(element, text_method), value_for_collection(element, value_method)]
end
selected, disabled = extract_selected_and_disabled(selected)
- select_deselect = {}
- select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected)
- select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled)
+ select_deselect = {
+ :selected => extract_values_from_collection(collection, value_method, selected),
+ :disabled => extract_values_from_collection(collection, value_method, disabled)
+ }
options_for_select(options, select_deselect)
end
@@ -420,10 +442,10 @@ module ActionView
# wrap the output in an appropriate <tt><select></tt> tag.
def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil)
collection.map do |group|
- group_label_string = eval("group.#{group_label_method}")
- "<optgroup label=\"#{ERB::Util.html_escape(group_label_string)}\">" +
- options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key) +
- '</optgroup>'
+ option_tags = options_from_collection_for_select(
+ group.send(group_method), option_key_method, option_value_method, selected_key)
+
+ content_tag(:optgroup, option_tags, :label => group.send(group_label_method))
end.join.html_safe
end
@@ -438,8 +460,11 @@ module ActionView
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
# which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
# as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
- # * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this
+ #
+ # Options:
+ # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this
# prepends an option with a generic prompt - "Please select" - or the given prompt string.
+ # * <tt>:divider</tt> - the divider for the options groups.
#
# Sample usage (Array):
# grouped_options = [
@@ -452,8 +477,8 @@ module ActionView
#
# Sample usage (Hash):
# grouped_options = {
- # 'North America' => [['United States','US'], 'Canada'],
- # 'Europe' => ['Denmark','Germany','France']
+ # 'North America' => [['United States','US'], 'Canada'],
+ # 'Europe' => ['Denmark','Germany','France']
# }
# grouped_options_for_select(grouped_options)
#
@@ -468,19 +493,54 @@ module ActionView
# <option value="Canada">Canada</option>
# </optgroup>
#
+ # Sample usage (divider):
+ # grouped_options = [
+ # [['United States','US'], 'Canada'],
+ # ['Denmark','Germany','France']
+ # ]
+ # grouped_options_for_select(grouped_options, nil, divider: '---------')
+ #
+ # Possible output:
+ # <optgroup label="---------">
+ # <option value="Denmark">Denmark</option>
+ # <option value="Germany">Germany</option>
+ # <option value="France">France</option>
+ # </optgroup>
+ # <optgroup label="---------">
+ # <option value="US">United States</option>
+ # <option value="Canada">Canada</option>
+ # </optgroup>
+ #
# <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to
# wrap the output in an appropriate <tt><select></tt> tag.
- def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
- body = ''
- body << content_tag(:option, prompt, { :value => "" }, true) if prompt
+ def grouped_options_for_select(grouped_options, selected_key = nil, options = {})
+ if options.is_a?(Hash)
+ prompt = options[:prompt]
+ divider = options[:divider]
+ else
+ prompt = options
+ options = {}
+ ActiveSupport::Deprecation.warn "Passing the prompt to grouped_options_for_select as an argument is deprecated. Please use an options hash like `{ prompt: #{prompt.inspect} }`."
+ end
+
+ body = "".html_safe
+
+ if prompt
+ body.safe_concat content_tag(:option, prompt_text(prompt), :value => "")
+ end
grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
- grouped_options.each do |group|
- body << content_tag(:optgroup, options_for_select(group[1], selected_key), :label => group[0])
+ grouped_options.each do |container|
+ if divider
+ label = divider
+ else
+ label, container = container
+ end
+ body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label)
end
- body.html_safe
+ body
end
# Returns a string of option tags for pretty much any time zone in the
@@ -502,42 +562,162 @@ module ActionView
# NOTE: Only the option tags are returned, you have to wrap this call in
# a regular HTML select tag.
def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone)
- zone_options = ""
+ zone_options = "".html_safe
zones = model.all
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
if priority_zones
- if priority_zones.is_a?(Regexp)
- priority_zones = model.all.find_all {|z| z =~ priority_zones}
- end
- zone_options += options_for_select(convert_zones[priority_zones], selected)
- zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
+ if priority_zones.is_a?(Regexp)
+ priority_zones = zones.select { |z| z =~ priority_zones }
+ end
- zones = zones.reject { |z| priority_zones.include?( z ) }
+ zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
+ zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled')
+ zone_options.safe_concat "\n"
+
+ zones.reject! { |z| priority_zones.include?(z) }
end
- zone_options += options_for_select(convert_zones[zones], selected)
- zone_options.html_safe
+ zone_options.safe_concat options_for_select(convert_zones[zones], selected)
+ end
+
+ # Returns radio button tags for the collection of existing return values
+ # of +method+ for +object+'s class. The value returned from calling
+ # +method+ on the instance +object+ will be selected. If calling +method+
+ # returns +nil+, no selection is made.
+ #
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
+ # methods to be called on each member of +collection+. The return values
+ # are used as the +value+ attribute and contents of each radio button tag,
+ # respectively. They can also be any object that responds to +call+, such
+ # as a +proc+, that will be called for each member of the +collection+ to
+ # retrieve the value/text.
+ #
+ # Example object structure for use with this method:
+ # class Post < ActiveRecord::Base
+ # belongs_to :author
+ # end
+ # class Author < ActiveRecord::Base
+ # has_many :posts
+ # def name_with_initial
+ # "#{first_name.first}. #{last_name}"
+ # end
+ # end
+ #
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
+ #
+ # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
+ # <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
+ # <label for="post_author_id_1">D. Heinemeier Hansson</label>
+ # <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
+ # <label for="post_author_id_2">D. Thomas</label>
+ # <input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
+ # <label for="post_author_id_3">M. Clark</label>
+ #
+ # It is also possible to customize the way the elements will be shown by
+ # giving a block to the method:
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
+ # b.label { b.radio_button }
+ # end
+ #
+ # The argument passed to the block is a special kind of builder for this
+ # collection, which has the ability to generate the label and radio button
+ # for the current item in the collection, with proper text and value.
+ # Using it, you can change the label and radio button display order or
+ # even use the label as wrapper, as in the example above.
+ #
+ # The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept
+ # extra html options:
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:class => "radio_button") { b.radio_button(:class => "radio_button") }
+ # end
+ #
+ # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
+ # <tt>value</tt>, which are the current item being rendered, its text and value methods,
+ # respectively. You can use them like this:
+ # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:"data-value" => b.value) { b.radio_button + b.text }
+ # end
+ def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
+ Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
+ end
+
+ # Returns check box tags for the collection of existing return values of
+ # +method+ for +object+'s class. The value returned from calling +method+
+ # on the instance +object+ will be selected. If calling +method+ returns
+ # +nil+, no selection is made.
+ #
+ # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are
+ # methods to be called on each member of +collection+. The return values
+ # are used as the +value+ attribute and contents of each check box tag,
+ # respectively. They can also be any object that responds to +call+, such
+ # as a +proc+, that will be called for each member of the +collection+ to
+ # retrieve the value/text.
+ #
+ # Example object structure for use with this method:
+ # class Post < ActiveRecord::Base
+ # has_and_belongs_to_many :author
+ # end
+ # class Author < ActiveRecord::Base
+ # has_and_belongs_to_many :posts
+ # def name_with_initial
+ # "#{first_name.first}. #{last_name}"
+ # end
+ # end
+ #
+ # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial)
+ #
+ # If <tt>@post.author_ids</tt> is already <tt>[1]</tt>, this would return:
+ # <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
+ # <label for="post_author_ids_1">D. Heinemeier Hansson</label>
+ # <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
+ # <label for="post_author_ids_2">D. Thomas</label>
+ # <input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
+ # <label for="post_author_ids_3">M. Clark</label>
+ # <input name="post[author_ids][]" type="hidden" value="" />
+ #
+ # It is also possible to customize the way the elements will be shown by
+ # giving a block to the method:
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
+ # b.label { b.check_box }
+ # end
+ #
+ # The argument passed to the block is a special kind of builder for this
+ # collection, which has the ability to generate the label and check box
+ # for the current item in the collection, with proper text and value.
+ # Using it, you can change the label and check box display order or even
+ # use the label as wrapper, as in the example above.
+ #
+ # The builder methods <tt>label</tt> and <tt>check_box</tt> also accept
+ # extra html options:
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:class => "check_box") { b.check_box(:class => "check_box") }
+ # end
+ #
+ # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and
+ # <tt>value</tt>, which are the current item being rendered, its text and value methods,
+ # respectively. You can use them like this:
+ # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b|
+ # b.label(:"data-value" => b.value) { b.check_box + b.text }
+ # end
+ def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block)
+ Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block)
end
private
def option_html_attributes(element)
- return "" unless Array === element
- html_attributes = []
- element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
- html_attributes << " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\""
- end
- html_attributes.join
+ return {} unless Array === element
+
+ Hash[element.select { |e| Hash === e }.reduce({}, :merge).map { |k, v| [k, ERB::Util.html_escape(v.to_s)] }]
end
def option_text_and_value(option)
# Options are [text, value] pairs or strings used for both.
- case
- when Array === option
- option = option.reject { |e| Hash === e }
- [option.first, option.last]
- when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
+ if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
+ option = option.reject { |e| Hash === e } if Array === option
[option.first, option.last]
else
[option, option]
@@ -545,20 +725,17 @@ module ActionView
end
def option_value_selected?(value, selected)
- if selected.respond_to?(:include?) && !selected.is_a?(String)
- selected.include? value
- else
- value == selected
- end
+ Array(selected).include? value
end
def extract_selected_and_disabled(selected)
if selected.is_a?(Proc)
- [ selected, nil ]
+ [selected, nil]
else
selected = Array.wrap(selected)
options = selected.extract_options!.symbolize_keys
- [ options.include?(:selected) ? options[:selected] : selected, options[:disabled] ]
+ selected_items = options.fetch(:selected, selected)
+ [selected_items, options[:disabled]]
end
end
@@ -571,63 +748,13 @@ module ActionView
selected
end
end
- end
-
- class InstanceTag #:nodoc:
- include FormOptionsHelper
- def to_select_tag(choices, options, html_options)
- selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
-
- if !choices.empty? && Array === choices.first
- option_tags = grouped_options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
- else
- option_tags = options_for_select(choices, :selected => selected_value, :disabled => options[:disabled])
+ def value_for_collection(item, value)
+ value.respond_to?(:call) ? value.call(item) : item.send(value)
end
- select_content_tag(option_tags, options, html_options)
- end
-
- def to_collection_select_tag(collection, value_method, text_method, options, html_options)
- selected_value = options.has_key?(:selected) ? options[:selected] : value(object)
- select_content_tag(
- options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options
- )
- end
-
- def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
- select_content_tag(
- option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options
- )
- end
-
- def to_time_zone_select_tag(priority_zones, options, html_options)
- select_content_tag(
- time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options
- )
- end
-
- private
- def add_options(option_tags, options, value = nil)
- if options[:include_blank]
- option_tags = "<option value=\"\">#{ERB::Util.html_escape(options[:include_blank]) if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
- end
- if value.blank? && options[:prompt]
- prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
- option_tags = "<option value=\"\">#{ERB::Util.html_escape(prompt)}</option>\n" + option_tags
- end
- option_tags.html_safe
- end
-
- def select_content_tag(option_tags, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
- select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
- if html_options["multiple"]
- tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
- else
- select
- end
+ def prompt_text(prompt)
+ prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select')
end
end
@@ -647,6 +774,14 @@ module ActionView
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
@template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
end
+
+ def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {})
+ @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
+ end
+
+ def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {})
+ @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 13b9dc8553..e65b4e3e95 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -2,6 +2,7 @@ require 'cgi'
require 'action_view/helpers/tag_helper'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
+require 'active_support/core_ext/module/attribute_accessors'
module ActionView
# = Action View Form Tag Helpers
@@ -17,6 +18,9 @@ module ActionView
include UrlHelper
include TextHelper
+ mattr_accessor :embed_authenticity_token_in_remote_forms
+ self.embed_authenticity_token_in_remote_forms = false
+
# Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
# ActionController::Base#url_for. The method for the form defaults to POST.
#
@@ -27,7 +31,11 @@ module ActionView
# is added to simulate the verb over post.
# * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to
# pass custom authenticity token string, or to not add authenticity_token field at all
- # (by passing <tt>false</tt>).
+ # (by passing <tt>false</tt>). Remote forms may omit the embedded authenticity token
+ # by setting <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>.
+ # This is helpful when you're fragment-caching the form. Remote forms get the
+ # authenticity from the <tt>meta</tt> tag, so embedding is unnecessary unless you
+ # support browsers without JavaScript.
# * A list of parameters to feed to the URL the form will be posted to.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
# submit behavior. By default this behavior is an ajax submit.
@@ -37,7 +45,7 @@ module ActionView
# # => <form action="/posts" method="post">
#
# form_tag('/posts/1', :method => :put)
- # # => <form action="/posts/1" method="put">
+ # # => <form action="/posts/1" method="post"> ... <input name="_method" type="hidden" value="put" /> ...
#
# form_tag('/upload', :multipart => true)
# # => <form action="/upload" method="post" enctype="multipart/form-data">
@@ -47,7 +55,7 @@ module ActionView
# <% end -%>
# # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
#
- # <%= form_tag('/posts', :remote => true) %>
+ # <%= form_tag('/posts', :remote => true) %>
# # => <form action="/posts" method="post" data-remote="true">
#
# form_tag('http://far.away.com/form', :authenticity_token => false)
@@ -93,7 +101,7 @@ module ActionView
# # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option>
# # <option>Green</option><option>Blue</option></select>
#
- # select_tag "locations", "<option>Home</option><option selected="selected">Work</option><option>Out</option>".html_safe
+ # select_tag "locations", "<option>Home</option><option selected='selected'>Work</option><option>Out</option>".html_safe
# # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
# # <option>Out</option></select>
#
@@ -114,11 +122,11 @@ module ActionView
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
if options.delete(:include_blank)
- option_tags = "<option value=\"\"></option>".html_safe + option_tags
+ option_tags = content_tag(:option, '', :value => '').safe_concat(option_tags)
end
if prompt = options.delete(:prompt)
- option_tags = "<option value=\"\">#{prompt}</option>".html_safe + option_tags
+ option_tags = content_tag(:option, prompt, :value => '').safe_concat(option_tags)
end
content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
@@ -313,7 +321,7 @@ module ActionView
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
end
- escape = options.key?("escape") ? options.delete("escape") : true
+ escape = options.delete("escape") { true }
content = ERB::Util.html_escape(content) if escape
content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
@@ -378,9 +386,6 @@ module ActionView
# drivers will provide a prompt with the question specified. If the user accepts,
# the form is processed normally, otherwise no action is taken.
# * <tt>:disabled</tt> - If true, the user will not be able to use this input.
- # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a
- # disabled version of the submit button when the form is submitted. This feature is
- # provided by the unobtrusive JavaScript driver.
# * Any other key creates standard HTML options for the tag.
#
# ==== Examples
@@ -393,29 +398,21 @@ module ActionView
# submit_tag "Save edits", :disabled => true
# # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
#
- #
- # submit_tag "Complete sale", :disable_with => "Please wait..."
- # # => <input name="commit" data-disable-with="Please wait..."
- # # type="submit" value="Complete sale" />
+ # submit_tag "Complete sale", :data => { :disable_with => "Please wait..." }
+ # # => <input name="commit" data-disable-with="Please wait..." type="submit" value="Complete sale" />
#
# submit_tag nil, :class => "form_submit"
# # => <input class="form_submit" name="commit" type="submit" />
#
- # submit_tag "Edit", :disable_with => "Editing...", :class => "edit_button"
- # # => <input class="edit_button" data-disable_with="Editing..."
- # # name="commit" type="submit" value="Edit" />
+ # submit_tag "Edit", :class => "edit_button"
+ # # => <input class="edit_button" name="commit" type="submit" value="Edit" />
#
# submit_tag "Save", :confirm => "Are you sure?"
- # # => <input name='commit' type='submit' value='Save'
- # data-confirm="Are you sure?" />
+ # # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" />
#
def submit_tag(value = "Save changes", options = {})
options = options.stringify_keys
- if disable_with = options.delete("disable_with")
- options["data-disable-with"] = disable_with
- end
-
if confirm = options.delete("confirm")
options["data-confirm"] = confirm
end
@@ -437,10 +434,6 @@ module ActionView
# processed normally, otherwise no action is taken.
# * <tt>:disabled</tt> - If true, the user will not be able to
# use this input.
- # * <tt>:disable_with</tt> - Value of this parameter will be
- # used as the value for a disabled version of the submit
- # button when the form is submitted. This feature is provided
- # by the unobtrusive JavaScript driver.
# * Any other key creates standard HTML options for the tag.
#
# ==== Examples
@@ -451,22 +444,14 @@ module ActionView
# content_tag(:strong, 'Ask me!')
# end
# # => <button name="button" type="button">
- # <strong>Ask me!</strong>
- # </button>
- #
- # button_tag "Checkout", :disable_with => "Please wait..."
- # # => <button data-disable-with="Please wait..." name="button"
- # type="submit">Checkout</button>
+ # # <strong>Ask me!</strong>
+ # # </button>
#
def button_tag(content_or_options = nil, options = nil, &block)
options = content_or_options if block_given? && content_or_options.is_a?(Hash)
options ||= {}
options = options.stringify_keys
- if disable_with = options.delete("disable_with")
- options["data-disable-with"] = disable_with
- end
-
if confirm = options.delete("confirm")
options["data-confirm"] = confirm
end
@@ -499,6 +484,9 @@ module ActionView
#
# image_submit_tag("agree.png", :disabled => true, :class => "agree_disagree_button")
# # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" />
+ #
+ # image_submit_tag("save.png", :confirm => "Are you sure?")
+ # # => <input src="/images/save.png" data-confirm="Are you sure?" type="image" />
def image_submit_tag(source, options = {})
options = options.stringify_keys
@@ -530,10 +518,9 @@ module ActionView
# <% end %>
# # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
def field_set_tag(legend = nil, options = nil, &block)
- content = capture(&block)
output = tag(:fieldset, options, true)
output.safe_concat(content_tag(:legend, legend)) unless legend.blank?
- output.concat(content)
+ output.concat(capture(&block)) if block_given?
output.safe_concat("</fieldset>")
end
@@ -554,6 +541,25 @@ module ActionView
end
alias phone_field_tag telephone_field_tag
+ # Creates a text field of type "date".
+ #
+ # ==== Options
+ # * Accepts the same options as text_field_tag.
+ def date_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "date"))
+ end
+
+ # Creates a text field of type "time".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def time_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "time"))
+ end
+
# Creates a text field of type "url".
#
# ==== Options
@@ -582,7 +588,7 @@ module ActionView
#
# ==== Examples
# number_field_tag 'quantity', nil, :in => 1...10
- # => <input id="quantity" name="quantity" min="1" max="9" />
+ # # => <input id="quantity" name="quantity" min="1" max="9" type="number" />
def number_field_tag(name, value = nil, options = {})
options = options.stringify_keys
options["type"] ||= "number"
@@ -614,8 +620,19 @@ module ActionView
# responsibility of the caller to escape all the values.
html_options["action"] = url_for(url_for_options)
html_options["accept-charset"] = "UTF-8"
+
html_options["data-remote"] = true if html_options.delete("remote")
- html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token")
+
+ if html_options["data-remote"] &&
+ !embed_authenticity_token_in_remote_forms &&
+ html_options["authenticity_token"].blank?
+ # The authenticity token is taken from the meta tag in this case
+ html_options["authenticity_token"] = false
+ elsif html_options["authenticity_token"] == true
+ # Include the default authenticity_token, which is only generated when its set to nil,
+ # but we needed the true value to override the default of no authenticity_token on data-remote.
+ html_options["authenticity_token"] = nil
+ end
end
end
@@ -632,7 +649,7 @@ module ActionView
token_tag(authenticity_token)
else
html_options["method"] = "post"
- tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)
+ method_tag(method) + token_tag(authenticity_token)
end
tags = utf8_enforcer_tag << method_tag
@@ -641,26 +658,16 @@ module ActionView
def form_tag_html(html_options)
extra_tags = extra_tags_for_form(html_options)
- (tag(:form, html_options, true) + extra_tags).html_safe
+ tag(:form, html_options, true) + extra_tags
end
def form_tag_in_block(html_options, &block)
content = capture(&block)
- output = ActiveSupport::SafeBuffer.new
- output.safe_concat(form_tag_html(html_options))
+ output = form_tag_html(html_options)
output << content
output.safe_concat("</form>")
end
- def token_tag(token)
- if token == false || !protect_against_forgery?
- ''
- else
- token ||= form_authenticity_token
- tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
- end
- end
-
# see http://www.w3.org/TR/html4/types.html#type-name
def sanitize_to_id(name)
name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_")
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 1adcd716f8..cc20518b93 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -1,5 +1,4 @@
require 'action_view/helpers/tag_helper'
-require 'active_support/core_ext/string/encoding'
module ActionView
module Helpers
@@ -14,11 +13,8 @@ module ActionView
"'" => "\\'"
}
- if "ruby".encoding_aware?
- JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '&#x2028;'
- else
- JS_ESCAPE_MAP["\342\200\250"] = '&#x2028;'
- end
+ JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '&#x2028;'
+ JS_ESCAPE_MAP["\342\200\251".force_encoding('UTF-8').encode!] = '&#x2029;'
# Escapes carriage returns and single and double quotes for JavaScript segments.
#
@@ -27,7 +23,7 @@ module ActionView
# $('some_element').replaceWith('<%=j render 'some/element_template' %>');
def escape_javascript(javascript)
if javascript
- result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
javascript.html_safe? ? result.html_safe : result
else
''
@@ -40,7 +36,7 @@ module ActionView
# javascript_tag "alert('All is good')"
#
# Returns:
- # <script type="text/javascript">
+ # <script>
# //<![CDATA[
# alert('All is good')
# //]]>
@@ -49,7 +45,7 @@ module ActionView
# +html_options+ may be a hash of attributes for the <tt>\<script></tt>
# tag. Example:
# javascript_tag "alert('All is good')", :defer => 'defer'
- # # => <script defer="defer" type="text/javascript">alert('All is good')</script>
+ # # => <script defer="defer">alert('All is good')</script>
#
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +html_options+ as the first parameter.
@@ -65,46 +61,12 @@ module ActionView
content_or_options_with_block
end
- content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
+ content_tag(:script, javascript_cdata_section(content), html_options)
end
def javascript_cdata_section(content) #:nodoc:
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
end
-
- # Returns a button whose +onclick+ handler triggers the passed JavaScript.
- #
- # 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+.
- #
- # button_to_function "Greeting", "alert('Hello world!')", :class => "ok"
- # # => <input class="ok" onclick="alert('Hello world!');" type="button" value="Greeting" />
- #
- 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 whose +onclick+ handler triggers the passed JavaScript.
- #
- # 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 +href+ attribute of the tag is set to "#" unles +html_options+ has one.
- #
- # link_to_function "Greeting", "alert('Hello world!')", :class => "nav_link"
- # # => <a class="nav_link" href="#" onclick="alert('Hello world!'); return false;">Greeting</a>
- #
- def link_to_function(name, function, html_options={})
- onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
- href = html_options[:href] || '#'
-
- content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
- end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 7031694af4..dfc26acfad 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -1,7 +1,6 @@
# encoding: utf-8
require 'active_support/core_ext/big_decimal/conversions'
-require 'active_support/core_ext/float/rounding'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/output_safety'
@@ -29,37 +28,43 @@ module ActionView
end
end
- # Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format
- # in the +options+ hash.
+ # Formats a +number+ into a US phone number (e.g., (555)
+ # 123-9876). You can customize the format in the +options+ hash.
#
# ==== Options
- # * <tt>:area_code</tt> - Adds parentheses around the area code.
- # * <tt>:delimiter</tt> - Specifies the delimiter to use (defaults to "-").
- # * <tt>:extension</tt> - Specifies an extension to add to the end of the
- # generated number.
- # * <tt>:country_code</tt> - Sets the country code for the phone number.
+ #
+ # * <tt>:area_code</tt> - Adds parentheses around the area code.
+ # * <tt>:delimiter</tt> - Specifies the delimiter to use
+ # (defaults to "-").
+ # * <tt>:extension</tt> - Specifies an extension to add to the
+ # end of the generated number.
+ # * <tt>:country_code</tt> - Sets the country code for the phone
+ # number.
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
+ #
# number_to_phone(5551234) # => 555-1234
+ # number_to_phone("5551234") # => 555-1234
# number_to_phone(1235551234) # => 123-555-1234
# number_to_phone(1235551234, :area_code => true) # => (123) 555-1234
# number_to_phone(1235551234, :delimiter => " ") # => 123 555 1234
# number_to_phone(1235551234, :area_code => true, :extension => 555) # => (123) 555-1234 x 555
# number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234
+ # number_to_phone("123a456") # => 123a456
+ #
+ # number_to_phone("1234a567", :raise => true) # => InvalidNumberError
#
# number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".")
- # => +1.123.555.1234 x 1343
+ # # => +1.123.555.1234 x 1343
def number_to_phone(number, options = {})
return unless number
+ options = options.symbolize_keys
- begin
- Float(number)
- rescue ArgumentError, TypeError
- raise InvalidNumberError, number
- end if options[:raise]
+ parse_float(number, true) if options[:raise]
number = number.to_s.strip
- options = options.symbolize_keys
area_code = options[:area_code]
delimiter = options[:delimiter] || "-"
extension = options[:extension]
@@ -69,54 +74,66 @@ module ActionView
number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
else
number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.starts_with?(delimiter) && !delimiter.blank?
+ number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
end
- str = []
+ str = ''
str << "+#{country_code}#{delimiter}" unless country_code.blank?
str << number
str << " x #{extension}" unless extension.blank?
- ERB::Util.html_escape(str.join)
+ ERB::Util.html_escape(str)
end
- # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format
- # in the +options+ hash.
+ # Formats a +number+ into a currency string (e.g., $13.65). You
+ # can customize the format in the +options+ hash.
#
# ==== Options
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:precision</tt> - Sets the level of precision (defaults to 2).
- # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$").
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
- # * <tt>:format</tt> - Sets the format for non-negative numbers (defaults to "%u%n").
- # Fields are <tt>%u</tt> for the currency, and <tt>%n</tt>
- # for the number.
- # * <tt>:negative_format</tt> - Sets the format for negative numbers (defaults to prepending
- # an hyphen to the formatted number given by <tt>:format</tt>).
- # Accepts the same fields than <tt>:format</tt>, except
- # <tt>%n</tt> is here the absolute value of the number.
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the level of precision (defaults
+ # to 2).
+ # * <tt>:unit</tt> - Sets the denomination of the currency
+ # (defaults to "$").
+ # * <tt>:separator</tt> - Sets the separator between the units
+ # (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:format</tt> - Sets the format for non-negative numbers
+ # (defaults to "%u%n"). Fields are <tt>%u</tt> for the
+ # currency, and <tt>%n</tt> for the number.
+ # * <tt>:negative_format</tt> - Sets the format for negative
+ # numbers (defaults to prepending an hyphen to the formatted
+ # number given by <tt>:format</tt>). Accepts the same fields
+ # than <tt>:format</tt>, except <tt>%n</tt> is here the
+ # absolute value of the number.
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
+ #
# number_to_currency(1234567890.50) # => $1,234,567,890.50
# number_to_currency(1234567890.506) # => $1,234,567,890.51
# number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506
- # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,506 €
+ # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,51 €
+ # number_to_currency("123a456") # => $123a456
+ #
+ # number_to_currency("123a456", :raise => true) # => InvalidNumberError
#
# number_to_currency(-1234567890.50, :negative_format => "(%u%n)")
- # # => ($1,234,567,890.51)
+ # # => ($1,234,567,890.50)
# number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "")
# # => &pound;1234567890,50
# number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
return unless number
+ options = options.symbolize_keys
- options.symbolize_keys!
-
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {})
+ currency = translations_for('currency', options[:locale])
+ currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
- defaults = DEFAULT_CURRENCY_VALUES.merge(defaults).merge!(currency)
+ defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
defaults[:negative_format] = "-" + options[:format] if options[:format]
options = defaults.merge!(options)
@@ -130,108 +147,142 @@ module ActionView
begin
value = number_with_precision(number, options.merge(:raise => true))
- format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
+ format.gsub('%n', value).gsub('%u', unit).html_safe
rescue InvalidNumberError => e
if options[:raise]
raise
else
- formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit)
+ formatted_number = format.gsub('%n', e.number).gsub('%u', unit)
e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
end
end
-
end
- # Formats a +number+ as a percentage string (e.g., 65%). You can customize the
- # format in the +options+ hash.
+ # Formats a +number+ as a percentage string (e.g., 65%). You can
+ # customize the format in the +options+ hash.
#
# ==== Options
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+)
- # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
- # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+)
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ # * <tt>:format</tt> - Specifies the format of the percentage
+ # string The number field is <tt>%n</tt> (defaults to "%n%").
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
+ #
# number_to_percentage(100) # => 100.000%
+ # number_to_percentage("98") # => 98.000%
# number_to_percentage(100, :precision => 0) # => 100%
# number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000%
# number_to_percentage(302.24398923423, :precision => 5) # => 302.24399%
# number_to_percentage(1000, :locale => :fr) # => 1 000,000%
+ # number_to_percentage("98a") # => 98a%
+ # number_to_percentage(100, :format => "%n %") # => 100 %
+ #
+ # number_to_percentage("98a", :raise => true) # => InvalidNumberError
def number_to_percentage(number, options = {})
return unless number
+ options = options.symbolize_keys
- options.symbolize_keys!
-
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(percentage)
+ defaults = format_translations('percentage', options[:locale])
+ options = defaults.merge!(options)
- options = options.reverse_merge(defaults)
+ format = options[:format] || "%n%"
begin
- "#{number_with_precision(number, options.merge(:raise => true))}%".html_safe
+ value = number_with_precision(number, options.merge(:raise => true))
+ format.gsub(/%n/, value).html_safe
rescue InvalidNumberError => e
if options[:raise]
raise
else
- e.number.to_s.html_safe? ? "#{e.number}%".html_safe : "#{e.number}%"
+ formatted_number = format.gsub(/%n/, e.number)
+ e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
end
end
end
- # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can
- # customize the format in the +options+ hash.
+ # Formats a +number+ with grouped thousands using +delimiter+
+ # (e.g., 12,324). You can customize the format in the +options+
+ # hash.
#
# ==== Options
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
- # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to ",").
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
+ #
# number_with_delimiter(12345678) # => 12,345,678
+ # number_with_delimiter("123456") # => 123,456
# number_with_delimiter(12345678.05) # => 12,345,678.05
# number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678
- # number_with_delimiter(12345678, :separator => ",") # => 12,345,678
+ # number_with_delimiter(12345678, :delimiter => ",") # => 12,345,678
+ # number_with_delimiter(12345678.05, :separator => " ") # => 12,345,678 05
# number_with_delimiter(12345678.05, :locale => :fr) # => 12 345 678,05
+ # number_with_delimiter("112a") # => 112a
# number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",")
# # => 98 765 432,98
+ #
+ # number_with_delimiter("112a", :raise => true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ parse_float(number, options[:raise]) or return number
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- options = options.reverse_merge(defaults)
+ options = defaults_translations(options[:locale]).merge(options)
parts = number.to_s.to_str.split('.')
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- parts.join(options[:separator]).html_safe
-
+ safe_join(parts, options[:separator])
end
- # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision
- # of 2 if +:significant+ is +false+, and 5 if +:significant+ is +true+).
+ # Formats a +number+ with the specified level of
+ # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if
+ # +:significant+ is +false+, and 5 if +:significant+ is +true+).
# You can customize the format in the +options+ hash.
#
# ==== Options
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+)
- # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
- # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+)
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +false+).
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +false+).
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
+ #
# number_with_precision(111.2345) # => 111.235
# number_with_precision(111.2345, :precision => 2) # => 111.23
# number_with_precision(13, :precision => 5) # => 13.00000
@@ -240,29 +291,21 @@ module ActionView
# number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
# number_with_precision(13, :precision => 5, :significant => true) # => 13.000
# number_with_precision(111.234, :locale => :fr) # => 111,234
+ #
# number_with_precision(13, :precision => 5, :significant => true, :strip_insignificant_zeros => true)
# # => 13
+ #
# number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
# # => 1.111,23
def number_with_precision(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- number = begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ number = (parse_float(number, options[:raise]) or return number)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(precision_defaults)
+ defaults = format_translations('precision', options[:locale])
+ options = defaults.merge!(options)
- options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false
precision = options.delete :precision
significant = options.delete :significant
strip_insignificant_zeros = options.delete :strip_insignificant_zeros
@@ -279,6 +322,7 @@ module ActionView
precision = precision > 0 ? precision : 0 #don't let it be negative
else
rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
+ rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros
end
formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
if strip_insignificant_zeros
@@ -287,27 +331,41 @@ module ActionView
else
formatted_number
end
-
end
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
- # Formats the bytes in +number+ into a more understandable representation
- # (e.g., giving it 1500 yields 1.5 KB). This method is useful for
- # reporting file sizes to users. You can customize the
- # format in the +options+ hash.
+ # Formats the bytes in +number+ into a more understandable
+ # representation (e.g., giving it 1500 yields 1.5 KB). This
+ # method is useful for reporting file sizes to users. You can
+ # customize the format in the +options+ hash.
#
- # See <tt>number_to_human</tt> if you want to pretty-print a generic number.
+ # See <tt>number_to_human</tt> if you want to pretty-print a
+ # generic number.
#
# ==== Options
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
- # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
- # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
- # * <tt>:prefix</tt> - If +:si+ formats the number using the SI prefix (defaults to :binary)
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
+ # prefix (defaults to :binary)
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
+ #
# ==== Examples
+ #
# number_to_human_size(123) # => 123 Bytes
# number_to_human_size(1234) # => 1.21 KB
# number_to_human_size(12345) # => 12.1 KB
@@ -318,28 +376,20 @@ module ActionView
# number_to_human_size(483989, :precision => 2) # => 470 KB
# number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
#
- # Non-significant zeros after the fractional separator are stripped out by default (set
- # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
+ # Non-significant zeros after the fractional separator are
+ # stripped out by default (set
+ # <tt>:strip_insignificant_zeros</tt> to +false+ to change
+ # that):
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
# number_to_human_size(524288000, :precision => 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- number = begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ number = (parse_float(number, options[:raise]) or return number)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(human)
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
- options = options.reverse_merge(defaults)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
@@ -367,32 +417,55 @@ module ActionView
DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
-1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
- # Pretty prints (formats and approximates) a number in a way it is more readable by humans
- # (eg.: 1200000000 becomes "1.2 Billion"). This is useful for numbers that
- # can get very large (and too hard to read).
+ # Pretty prints (formats and approximates) a number in a way it
+ # is more readable by humans (eg.: 1200000000 becomes "1.2
+ # Billion"). This is useful for numbers that can get very large
+ # (and too hard to read).
#
- # See <tt>number_to_human_size</tt> if you want to print a file size.
+ # See <tt>number_to_human_size</tt> if you want to print a file
+ # size.
#
- # You can also define you own unit-quantifier names if you want to use other decimal units
- # (eg.: 1500 becomes "1.5 kilometers", 0.150 becomes "150 milliliters", etc). You may define
- # a wide range of unit quantifiers, even fractional ones (centi, deci, mili, etc).
+ # You can also define you own unit-quantifier names if you want
+ # to use other decimal units (eg.: 1500 becomes "1.5
+ # kilometers", 0.150 becomes "150 milliliters", etc). You may
+ # define a wide range of unit quantifiers, even fractional ones
+ # (centi, deci, mili, etc).
#
# ==== Options
- # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale).
- # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
- # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
- # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
- # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
- # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
- # * <tt>:units</tt> - A Hash of unit quantifier names. Or a string containing an i18n scope where to find this hash. It might have the following keys:
- # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt>
- # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt>
- # * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are:
- #
- # %u The quantifier (ex.: 'thousand')
- # %n The number
+ #
+ # * <tt>:locale</tt> - Sets the locale to be used for formatting
+ # (defaults to current locale).
+ # * <tt>:precision</tt> - Sets the precision of the number
+ # (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the #
+ # of significant_digits. If +false+, the # of fractional
+ # digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the
+ # fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults
+ # to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
+ # insignificant zeros after the decimal separator (defaults to
+ # +true+)
+ # * <tt>:units</tt> - A Hash of unit quantifier names. Or a
+ # string containing an i18n scope where to find this hash. It
+ # might have the following keys:
+ # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>,
+ # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>,
+ # *<tt>:billion</tt>, <tt>:trillion</tt>,
+ # *<tt>:quadrillion</tt>
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>,
+ # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>,
+ # *<tt>:pico</tt>, <tt>:femto</tt>
+ # * <tt>:format</tt> - Sets the format of the output string
+ # (defaults to "%n %u"). The field types are:
+ # * %u - The quantifier (ex.: 'thousand')
+ # * %n - The number
+ # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
+ # the argument is invalid.
#
# ==== Examples
+ #
# number_to_human(123) # => "123"
# number_to_human(1234) # => "1.23 Thousand"
# number_to_human(12345) # => "12.3 Thousand"
@@ -409,8 +482,9 @@ module ActionView
# :separator => ',',
# :significant => false) # => "1,2 Million"
#
- # Unsignificant zeros after the decimal separator are stripped out by default (set
- # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
+ # Non-significant zeros after the decimal separator are stripped
+ # out by default (set <tt>:strip_insignificant_zeros</tt> to
+ # +false+ to change that):
# number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion"
# number_to_human(500000000, :precision => 5) # => "500 Million"
#
@@ -442,23 +516,13 @@ module ActionView
# number_to_human(0.34, :units => :distance) # => "34 centimeters"
#
def number_to_human(number, options = {})
- options.symbolize_keys!
+ options = options.symbolize_keys
- number = begin
- Float(number)
- rescue ArgumentError, TypeError
- if options[:raise]
- raise InvalidNumberError, number
- else
- return number
- end
- end
+ number = (parse_float(number, options[:raise]) or return number)
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
- human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
- defaults = defaults.merge(human)
+ defaults = format_translations('human', options[:locale])
+ options = defaults.merge!(options)
- options = options.reverse_merge(defaults)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
@@ -494,6 +558,25 @@ module ActionView
decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe
end
+ private
+
+ def format_translations(namespace, locale)
+ defaults_translations(locale).merge(translations_for(namespace, locale))
+ end
+
+ def defaults_translations(locale)
+ I18n.translate(:'number.format', :locale => locale, :default => {})
+ end
+
+ def translations_for(namespace, locale)
+ I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ end
+
+ def parse_float(number, raise_error)
+ Float(number)
+ rescue ArgumentError, TypeError
+ raise InvalidNumberError, number if raise_error
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/output_safety_helper.rb b/actionpack/lib/action_view/helpers/output_safety_helper.rb
index a035dd70ad..2e7e9dc50c 100644
--- a/actionpack/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionpack/lib/action_view/helpers/output_safety_helper.rb
@@ -28,11 +28,10 @@ module ActionView #:nodoc:
# # => "<p>foo</p><br /><p>bar</p>"
#
def safe_join(array, sep=$,)
- sep ||= "".html_safe
sep = ERB::Util.html_escape(sep)
array.map { |i| ERB::Util.html_escape(i) }.join(sep).html_safe
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index b351302d01..9b35f076e5 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -81,13 +81,11 @@ module ActionView
# <li id="person_123" class="person bar">...
#
def content_tag_for(tag_name, single_or_multiple_records, prefix = nil, options = nil, &block)
- if single_or_multiple_records.respond_to?(:to_ary)
- single_or_multiple_records.to_ary.map do |single_record|
- capture { content_tag_for_single_record(tag_name, single_record, prefix, options, &block) }
- end.join("\n").html_safe
- else
- content_tag_for_single_record(tag_name, single_or_multiple_records, prefix, options, &block)
- end
+ options, prefix = prefix, nil if prefix.is_a?(Hash)
+
+ Array(single_or_multiple_records).map do |single_record|
+ content_tag_for_single_record(tag_name, single_record, prefix, options, &block)
+ end.join("\n").html_safe
end
private
@@ -95,14 +93,11 @@ module ActionView
# Called by <tt>content_tag_for</tt> internally to render a content tag
# for each record.
def content_tag_for_single_record(tag_name, record, prefix, options, &block)
- options, prefix = prefix, nil if prefix.is_a?(Hash)
- options ||= {}
- options.merge!({ :class => "#{dom_class(record, prefix)} #{options[:class]}".strip, :id => dom_id(record, prefix) })
- if block.arity == 0
- content_tag(tag_name, capture(&block), options)
- else
- content_tag(tag_name, capture(record, &block), options)
- end
+ options = options ? options.dup : {}
+ options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip
+ options[:id] = dom_id(record, prefix)
+
+ content_tag(tag_name, capture(record, &block), options)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index bcc8f6fbcb..a727b910e5 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/object/try'
require 'action_controller/vendor/html-scanner'
-require 'action_view/helpers/tag_helper'
module ActionView
# = Action View Sanitize Helpers
@@ -70,8 +69,6 @@ module ActionView
# html-scanner tokenizer and so its HTML parsing ability is limited by
# that of html-scanner.
#
- # ==== Examples
- #
# strip_tags("Strip <i>these</i> tags!")
# # => Strip these tags!
#
@@ -86,7 +83,6 @@ module ActionView
# Strips all link tags from +text+ leaving just the link text.
#
- # ==== Examples
# strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>')
# # => Ruby on Rails
#
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index 8c33ef09fa..498be596ad 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -14,9 +14,13 @@ module ActionView
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
autoplay controls loop selected hidden scoped async
defer reversed ismap seemless muted required
- autofocus novalidate formnovalidate open pubdate).to_set
+ autofocus novalidate formnovalidate open pubdate itemscope).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym })
+ PRE_CONTENT_STRINGS = {
+ :textarea => "\n"
+ }
+
# Returns an empty HTML tag of type +name+ which by default is XHTML
# compliant. Set +open+ to true to create an open tag compatible
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
@@ -99,57 +103,71 @@ module ActionView
# otherwise be recognized as markup. CDATA sections begin with the string
# <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>.
#
- # ==== Examples
# cdata_section("<hello world>")
# # => <![CDATA[<hello world>]]>
#
# cdata_section(File.read("hello_world.txt"))
# # => <![CDATA[<hello from a text file]]>
+ #
+ # cdata_section("hello]]>world")
+ # # => <![CDATA[hello]]]]><![CDATA[>world]]>
def cdata_section(content)
- "<![CDATA[#{content}]]>".html_safe
+ splitted = content.gsub(']]>', ']]]]><![CDATA[>')
+ "<![CDATA[#{splitted}]]>".html_safe
end
# Returns an escaped version of +html+ without affecting existing escaped entities.
#
- # ==== Examples
# escape_once("1 < 2 &amp; 3")
# # => "1 &lt; 2 &amp; 3"
#
# escape_once("&lt;&lt; Accept & Checkout")
# # => "&lt;&lt; Accept &amp; Checkout"
def escape_once(html)
- ActiveSupport::Multibyte.clean(html.to_s).gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
+ ERB::Util.html_escape_once(html)
end
private
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
- "<#{name}#{tag_options}>#{escape ? ERB::Util.h(content) : content}</#{name}>".html_safe
+ content = ERB::Util.h(content) if escape
+ "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe
end
def tag_options(options, escape = true)
- unless options.blank?
- attrs = []
- options.each_pair do |key, value|
- if key.to_s == 'data' && value.is_a?(Hash)
- value.each do |k, v|
- if !v.is_a?(String) && !v.is_a?(Symbol)
- v = v.to_json
- end
- v = ERB::Util.html_escape(v) if escape
- attrs << %(data-#{k.to_s.dasherize}="#{v}")
- end
- elsif BOOLEAN_ATTRIBUTES.include?(key)
- attrs << %(#{key}="#{key}") if value
- elsif !value.nil?
- final_value = value.is_a?(Array) ? value.join(" ") : value
- final_value = ERB::Util.html_escape(final_value) if escape
- attrs << %(#{key}="#{final_value}")
+ return if options.blank?
+ attrs = []
+ options.each_pair do |key, value|
+ if key.to_s == 'data' && value.is_a?(Hash)
+ value.each_pair do |k, v|
+ attrs << data_tag_option(k, v, escape)
end
+ elsif BOOLEAN_ATTRIBUTES.include?(key)
+ attrs << boolean_tag_option(key) if value
+ elsif !value.nil?
+ attrs << tag_option(key, value, escape)
end
- " #{attrs.sort * ' '}".html_safe unless attrs.empty?
end
+ " #{attrs.sort * ' '}".html_safe unless attrs.empty?
+ end
+
+ def data_tag_option(key, value, escape)
+ key = "data-#{key.to_s.dasherize}"
+ unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
+ value = value.to_json
+ end
+ tag_option(key, value, escape)
+ end
+
+ def boolean_tag_option(key)
+ %(#{key}="#{key}")
+ end
+
+ def tag_option(key, value, escape)
+ value = value.join(" ") if value.is_a?(Array)
+ value = ERB::Util.h(value) if escape
+ %(#{key}="#{value}")
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb
new file mode 100644
index 0000000000..5cd77c8ec3
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags.rb
@@ -0,0 +1,34 @@
+module ActionView
+ module Helpers
+ module Tags #:nodoc:
+ extend ActiveSupport::Autoload
+
+ autoload :Base
+ autoload :CheckBox
+ autoload :CollectionCheckBoxes
+ autoload :CollectionRadioButtons
+ autoload :CollectionSelect
+ autoload :DateField
+ autoload :DateSelect
+ autoload :DatetimeSelect
+ autoload :EmailField
+ autoload :FileField
+ autoload :GroupedCollectionSelect
+ autoload :HiddenField
+ autoload :Label
+ autoload :NumberField
+ autoload :PasswordField
+ autoload :RadioButton
+ autoload :RangeField
+ autoload :SearchField
+ autoload :Select
+ autoload :TelField
+ autoload :TextArea
+ autoload :TextField
+ autoload :TimeField
+ autoload :TimeSelect
+ autoload :TimeZoneSelect
+ autoload :UrlField
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb
new file mode 100644
index 0000000000..e077cd5b3c
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/base.rb
@@ -0,0 +1,150 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Base #:nodoc:
+ include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper
+ include FormOptionsHelper
+
+ attr_reader :object
+
+ def initialize(object_name, method_name, template_object, options = {})
+ @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
+ @template_object = template_object
+
+ @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
+ @object = retrieve_object(options.delete(:object))
+ @options = options
+ @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
+ end
+
+ # This is what child classes implement.
+ def render
+ raise NotImplementedError, "Subclasses must implement a render method"
+ end
+
+ private
+
+ def value(object)
+ object.send @method_name if object
+ end
+
+ def value_before_type_cast(object)
+ unless object.nil?
+ method_before_type_cast = @method_name + "_before_type_cast"
+
+ object.respond_to?(method_before_type_cast) ?
+ object.send(method_before_type_cast) :
+ value(object)
+ end
+ end
+
+ def retrieve_object(object)
+ if object
+ object
+ elsif @template_object.instance_variable_defined?("@#{@object_name}")
+ @template_object.instance_variable_get("@#{@object_name}")
+ end
+ rescue NameError
+ # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
+ nil
+ end
+
+ def retrieve_autoindex(pre_match)
+ object = self.object || @template_object.instance_variable_get("@#{pre_match}")
+ if object && object.respond_to?(:to_param)
+ object.to_param
+ else
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
+ end
+ end
+
+ def add_default_name_and_id_for_value(tag_value, options)
+ if tag_value.nil?
+ add_default_name_and_id(options)
+ else
+ specified_id = options["id"]
+ add_default_name_and_id(options)
+
+ if specified_id.blank? && options["id"].present?
+ options["id"] += "_#{sanitized_value(tag_value)}"
+ end
+ end
+ end
+
+ def add_default_name_and_id(options)
+ if options.has_key?("index")
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"]) }
+ options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
+ options.delete("index")
+ elsif defined?(@auto_index)
+ options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index) }
+ options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
+ else
+ options["name"] ||= options.fetch("name"){ options['multiple'] ? tag_name_multiple : tag_name }
+ options["id"] = options.fetch("id"){ tag_id }
+ end
+ options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence
+ end
+
+ def tag_name
+ "#{@object_name}[#{sanitized_method_name}]"
+ end
+
+ def tag_name_multiple
+ "#{tag_name}[]"
+ end
+
+ def tag_name_with_index(index)
+ "#{@object_name}[#{index}][#{sanitized_method_name}]"
+ end
+
+ def tag_id
+ "#{sanitized_object_name}_#{sanitized_method_name}"
+ end
+
+ def tag_id_with_index(index)
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ end
+
+ def sanitized_object_name
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
+ end
+
+ def sanitized_method_name
+ @sanitized_method_name ||= @method_name.sub(/\?$/,"")
+ end
+
+ def sanitized_value(value)
+ value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
+ end
+
+ def select_content_tag(option_tags, options, html_options)
+ html_options = html_options.stringify_keys
+ add_default_name_and_id(html_options)
+ options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options)
+ select = content_tag("select", add_options(option_tags, options, value(object)), html_options)
+
+ if html_options["multiple"] && options.fetch(:include_hidden, true)
+ tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
+ else
+ select
+ end
+ end
+
+ def select_not_required?(html_options)
+ !html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1
+ end
+
+ def add_options(option_tags, options, value = nil)
+ if options[:include_blank]
+ option_tags = content_tag('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags
+ end
+ if value.blank? && options[:prompt]
+ option_tags = content_tag('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags
+ end
+ option_tags
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb
new file mode 100644
index 0000000000..9d17a1dde3
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/check_box.rb
@@ -0,0 +1,62 @@
+require 'action_view/helpers/tags/checkable'
+
+module ActionView
+ module Helpers
+ module Tags
+ class CheckBox < Base #:nodoc:
+ include Checkable
+
+ def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options)
+ @checked_value = checked_value
+ @unchecked_value = unchecked_value
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ options = @options.stringify_keys
+ options["type"] = "checkbox"
+ options["value"] = @checked_value
+ options["checked"] = "checked" if input_checked?(object, options)
+
+ if options["multiple"]
+ add_default_name_and_id_for_value(@checked_value, options)
+ options.delete("multiple")
+ else
+ add_default_name_and_id(options)
+ end
+
+ include_hidden = options.delete("include_hidden") { true }
+ checkbox = tag("input", options)
+
+ if include_hidden
+ hidden = hidden_field_for_checkbox(options)
+ hidden + checkbox
+ else
+ checkbox
+ end
+ end
+
+ private
+
+ def checked?(value)
+ case value
+ when TrueClass, FalseClass
+ value == !!@checked_value
+ when NilClass
+ false
+ when String
+ value == @checked_value
+ when Array
+ value.include?(@checked_value)
+ else
+ value.to_i == @checked_value.to_i
+ end
+ end
+
+ def hidden_field_for_checkbox(options)
+ @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/checkable.rb b/actionpack/lib/action_view/helpers/tags/checkable.rb
new file mode 100644
index 0000000000..b97c0c68d7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/checkable.rb
@@ -0,0 +1,16 @@
+module ActionView
+ module Helpers
+ module Tags
+ module Checkable
+ def input_checked?(object, options)
+ if options.has_key?("checked")
+ checked = options.delete "checked"
+ checked == true || checked == "checked"
+ else
+ checked?(value(object))
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
new file mode 100644
index 0000000000..e23f5113fb
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -0,0 +1,37 @@
+require 'action_view/helpers/tags/collection_helpers'
+
+module ActionView
+ module Helpers
+ module Tags
+ class CollectionCheckBoxes < Base
+ include CollectionHelpers
+
+ class CheckBoxBuilder < Builder
+ def check_box(extra_html_options={})
+ html_options = extra_html_options.merge(@input_html_options)
+ @template_object.check_box(@object_name, @method_name, html_options, @value, nil)
+ end
+ end
+
+ def render
+ rendered_collection = render_collection do |item, value, text, default_html_options|
+ default_html_options[:multiple] = true
+ builder = instantiate_builder(CheckBoxBuilder, item, value, text, default_html_options)
+
+ if block_given?
+ yield builder
+ else
+ builder.check_box + builder.label
+ end
+ end
+
+ # Append a hidden field to make sure something will be sent back to the
+ # server if all check boxes are unchecked.
+ hidden = @template_object.hidden_field_tag(tag_name_multiple, "", :id => nil)
+
+ rendered_collection + hidden
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
new file mode 100644
index 0000000000..4e33e79a36
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
@@ -0,0 +1,82 @@
+module ActionView
+ module Helpers
+ module Tags
+ module CollectionHelpers
+ class Builder
+ attr_reader :object, :text, :value
+
+ def initialize(template_object, object_name, method_name, object,
+ sanitized_attribute_name, text, value, input_html_options)
+ @template_object = template_object
+ @object_name = object_name
+ @method_name = method_name
+ @object = object
+ @sanitized_attribute_name = sanitized_attribute_name
+ @text = text
+ @value = value
+ @input_html_options = input_html_options
+ end
+
+ def label(label_html_options={}, &block)
+ @template_object.label(@object_name, @sanitized_attribute_name, @text, label_html_options, &block)
+ end
+ end
+
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
+ @collection = collection
+ @value_method = value_method
+ @text_method = text_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ private
+
+ def instantiate_builder(builder_class, item, value, text, html_options)
+ builder_class.new(@template_object, @object_name, @method_name, item,
+ sanitize_attribute_name(value), text, value, html_options)
+ end
+
+ # Generate default options for collection helpers, such as :checked and
+ # :disabled.
+ def default_html_options_for_collection(item, value) #:nodoc:
+ html_options = @html_options.dup
+
+ [:checked, :selected, :disabled].each do |option|
+ next unless current_value = @options[option]
+
+ accept = if current_value.respond_to?(:call)
+ current_value.call(item)
+ else
+ Array(current_value).map(&:to_s).include?(value.to_s)
+ end
+
+ if accept
+ html_options[option] = true
+ elsif option == :checked
+ html_options[option] = false
+ end
+ end
+
+ html_options[:object] = @object
+ html_options
+ end
+
+ def sanitize_attribute_name(value) #:nodoc:
+ "#{sanitized_method_name}_#{sanitized_value(value)}"
+ end
+
+ def render_collection #:nodoc:
+ @collection.map do |item|
+ value = value_for_collection(item, @value_method)
+ text = value_for_collection(item, @text_method)
+ default_html_options = default_html_options_for_collection(item, value)
+
+ yield item, value, text, default_html_options
+ end.join.html_safe
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
new file mode 100644
index 0000000000..ba2035f074
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -0,0 +1,30 @@
+require 'action_view/helpers/tags/collection_helpers'
+
+module ActionView
+ module Helpers
+ module Tags
+ class CollectionRadioButtons < Base
+ include CollectionHelpers
+
+ class RadioButtonBuilder < Builder
+ def radio_button(extra_html_options={})
+ html_options = extra_html_options.merge(@input_html_options)
+ @template_object.radio_button(@object_name, @method_name, @value, html_options)
+ end
+ end
+
+ def render
+ render_collection do |item, value, text, default_html_options|
+ builder = instantiate_builder(RadioButtonBuilder, item, value, text, default_html_options)
+
+ if block_given?
+ yield builder
+ else
+ builder.radio_button + builder.label
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/collection_select.rb b/actionpack/lib/action_view/helpers/tags/collection_select.rb
new file mode 100644
index 0000000000..ec78e6e5f9
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/collection_select.rb
@@ -0,0 +1,28 @@
+module ActionView
+ module Helpers
+ module Tags
+ class CollectionSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options)
+ @collection = collection
+ @value_method = value_method
+ @text_method = text_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ option_tags_options = {
+ :selected => @options.fetch(:selected) { value(@object) },
+ :disabled => @options[:disabled]
+ }
+
+ select_content_tag(
+ options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options),
+ @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb
new file mode 100644
index 0000000000..0e79609d52
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/date_field.rb
@@ -0,0 +1,14 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DateField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["value"] = @options.fetch("value") { value(object).try(:to_date) }
+ @options = options
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb
new file mode 100644
index 0000000000..5d706087b0
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/date_select.rb
@@ -0,0 +1,70 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DateSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, options, html_options)
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe)
+ end
+
+ class << self
+ def select_type
+ @select_type ||= self.name.split("::").last.sub("Select", "").downcase
+ end
+ end
+
+ private
+
+ def select_type
+ self.class.select_type
+ end
+
+ def datetime_selector(options, html_options)
+ datetime = value(object) || default_datetime(options)
+ @auto_index ||= nil
+
+ options = options.dup
+ options[:field_name] = @method_name
+ options[:include_position] = true
+ options[:prefix] ||= @object_name
+ options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
+
+ DateTimeSelector.new(datetime, options, html_options)
+ end
+
+ def default_datetime(options)
+ return if options[:include_blank] || options[:prompt]
+
+ case options[:default]
+ when nil
+ Time.current
+ when Date, Time
+ options[:default]
+ else
+ default = options[:default].dup
+
+ # Rename :minute and :second to :min and :sec
+ default[:min] ||= default[:minute]
+ default[:sec] ||= default[:second]
+
+ time = Time.current
+
+ [:year, :month, :day, :hour, :min, :sec].each do |key|
+ default[key] ||= time.send(key)
+ end
+
+ Time.utc_time(
+ default[:year], default[:month], default[:day],
+ default[:hour], default[:min], default[:sec]
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_select.rb b/actionpack/lib/action_view/helpers/tags/datetime_select.rb
new file mode 100644
index 0000000000..a32c840bce
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/datetime_select.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DatetimeSelect < DateSelect #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/email_field.rb b/actionpack/lib/action_view/helpers/tags/email_field.rb
new file mode 100644
index 0000000000..45cde507d7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/email_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class EmailField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/file_field.rb b/actionpack/lib/action_view/helpers/tags/file_field.rb
new file mode 100644
index 0000000000..59f2ff71b4
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/file_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class FileField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
new file mode 100644
index 0000000000..507ba8835f
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb
@@ -0,0 +1,29 @@
+module ActionView
+ module Helpers
+ module Tags
+ class GroupedCollectionSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options)
+ @collection = collection
+ @group_method = group_method
+ @group_label_method = group_label_method
+ @option_key_method = option_key_method
+ @option_value_method = option_value_method
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ option_tags_options = {
+ :selected => @options.fetch(:selected) { value(@object) },
+ :disabled => @options[:disabled]
+ }
+
+ select_content_tag(
+ option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/hidden_field.rb b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
new file mode 100644
index 0000000000..a8d13dc1b1
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/hidden_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class HiddenField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb
new file mode 100644
index 0000000000..16135fcd5a
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/label.rb
@@ -0,0 +1,65 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Label < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil)
+ options ||= {}
+
+ content_is_options = content_or_options.is_a?(Hash)
+ if content_is_options
+ options.merge! content_or_options
+ @content = nil
+ else
+ @content = content_or_options
+ end
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render(&block)
+ options = @options.stringify_keys
+ tag_value = options.delete("value")
+ name_and_id = options.dup
+
+ if name_and_id["for"]
+ name_and_id["id"] = name_and_id["for"]
+ else
+ name_and_id.delete("id")
+ end
+
+ add_default_name_and_id_for_value(tag_value, name_and_id)
+ options.delete("index")
+ options.delete("namespace")
+ options["for"] = name_and_id["id"] unless options.key?("for")
+
+ if block_given?
+ content = @template_object.capture(&block)
+ else
+ content = if @content.blank?
+ @object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1')
+ method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name
+
+ if object.respond_to?(:to_model)
+ key = object.class.model_name.i18n_key
+ i18n_default = ["#{key}.#{method_and_value}".to_sym, ""]
+ end
+
+ i18n_default ||= ""
+ I18n.t("#{@object_name}.#{method_and_value}", :default => i18n_default, :scope => "helpers.label").presence
+ else
+ @content.to_s
+ end
+
+ content ||= if object && object.class.respond_to?(:human_attribute_name)
+ object.class.human_attribute_name(@method_name)
+ end
+
+ content ||= @method_name.humanize
+ end
+
+ label_tag(name_and_id["id"], content, options)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/number_field.rb b/actionpack/lib/action_view/helpers/tags/number_field.rb
new file mode 100644
index 0000000000..9cd04434f0
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/number_field.rb
@@ -0,0 +1,18 @@
+module ActionView
+ module Helpers
+ module Tags
+ class NumberField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+
+ if range = options.delete("in") || options.delete("within")
+ options.update("min" => range.min, "max" => range.max)
+ end
+
+ @options = options
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/password_field.rb b/actionpack/lib/action_view/helpers/tags/password_field.rb
new file mode 100644
index 0000000000..6e7a4d3c36
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/password_field.rb
@@ -0,0 +1,12 @@
+module ActionView
+ module Helpers
+ module Tags
+ class PasswordField < TextField #:nodoc:
+ def render
+ @options = {:value => nil}.merge!(@options)
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/radio_button.rb b/actionpack/lib/action_view/helpers/tags/radio_button.rb
new file mode 100644
index 0000000000..8a0421f061
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/radio_button.rb
@@ -0,0 +1,31 @@
+require 'action_view/helpers/tags/checkable'
+
+module ActionView
+ module Helpers
+ module Tags
+ class RadioButton < Base #:nodoc:
+ include Checkable
+
+ def initialize(object_name, method_name, template_object, tag_value, options)
+ @tag_value = tag_value
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ options = @options.stringify_keys
+ options["type"] = "radio"
+ options["value"] = @tag_value
+ options["checked"] = "checked" if input_checked?(object, options)
+ add_default_name_and_id_for_value(@tag_value, options)
+ tag("input", options)
+ end
+
+ private
+
+ def checked?(value)
+ value.to_s == @tag_value.to_s
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/range_field.rb b/actionpack/lib/action_view/helpers/tags/range_field.rb
new file mode 100644
index 0000000000..47db4680e7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/range_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class RangeField < NumberField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/search_field.rb b/actionpack/lib/action_view/helpers/tags/search_field.rb
new file mode 100644
index 0000000000..818fd4b887
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/search_field.rb
@@ -0,0 +1,24 @@
+module ActionView
+ module Helpers
+ module Tags
+ class SearchField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+
+ if options["autosave"]
+ if options["autosave"] == true
+ options["autosave"] = request.host.split(".").reverse.join(".")
+ end
+ options["results"] ||= 10
+ end
+
+ if options["onsearch"]
+ options["incremental"] = true unless options.has_key?("incremental")
+ end
+
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/select.rb b/actionpack/lib/action_view/helpers/tags/select.rb
new file mode 100644
index 0000000000..53a108b7e6
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/select.rb
@@ -0,0 +1,41 @@
+module ActionView
+ module Helpers
+ module Tags
+ class Select < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, choices, options, html_options)
+ @choices = choices
+ @choices = @choices.to_a if @choices.is_a?(Range)
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ option_tags_options = {
+ :selected => @options.fetch(:selected) { value(@object) },
+ :disabled => @options[:disabled]
+ }
+
+ option_tags = if grouped_choices?
+ grouped_options_for_select(@choices, option_tags_options)
+ else
+ options_for_select(@choices, option_tags_options)
+ end
+
+ select_content_tag(option_tags, @options, @html_options)
+ end
+
+ private
+
+ # Grouped choices look like this:
+ #
+ # [nil, []]
+ # { nil => [] }
+ #
+ def grouped_choices?
+ !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/tel_field.rb b/actionpack/lib/action_view/helpers/tags/tel_field.rb
new file mode 100644
index 0000000000..87c1f6b6b6
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/tel_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TelField < TextField #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/text_area.rb b/actionpack/lib/action_view/helpers/tags/text_area.rb
new file mode 100644
index 0000000000..f74652c5e7
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/text_area.rb
@@ -0,0 +1,18 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TextArea < Base #:nodoc:
+ def render
+ options = @options.stringify_keys
+ add_default_name_and_id(options)
+
+ if size = options.delete("size")
+ options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
+ end
+
+ content_tag("textarea", options.delete('value') || value_before_type_cast(object), options)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/text_field.rb b/actionpack/lib/action_view/helpers/tags/text_field.rb
new file mode 100644
index 0000000000..024a1a8af2
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/text_field.rb
@@ -0,0 +1,29 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TextField < Base #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["size"] = options["maxlength"] unless options.key?("size")
+ options["type"] ||= field_type
+ options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
+ options["value"] &&= ERB::Util.html_escape(options["value"])
+ add_default_name_and_id(options)
+ tag("input", options)
+ end
+
+ class << self
+ def field_type
+ @field_type ||= self.name.split("::").last.sub("Field", "").downcase
+ end
+ end
+
+ private
+
+ def field_type
+ self.class.field_type
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_field.rb b/actionpack/lib/action_view/helpers/tags/time_field.rb
new file mode 100644
index 0000000000..271dc00c54
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_field.rb
@@ -0,0 +1,14 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["value"] = @options.fetch("value") { value(object).try(:strftime, "%T.%L") }
+ @options = options
+ super
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_select.rb b/actionpack/lib/action_view/helpers/tags/time_select.rb
new file mode 100644
index 0000000000..9e97deb706
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_select.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeSelect < DateSelect #:nodoc:
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_zone_select.rb b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
new file mode 100644
index 0000000000..0a176157c3
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/time_zone_select.rb
@@ -0,0 +1,20 @@
+module ActionView
+ module Helpers
+ module Tags
+ class TimeZoneSelect < Base #:nodoc:
+ def initialize(object_name, method_name, template_object, priority_zones, options, html_options)
+ @priority_zones = priority_zones
+ @html_options = html_options
+
+ super(object_name, method_name, template_object, options)
+ end
+
+ def render
+ select_content_tag(
+ time_zone_options_for_select(value(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/url_field.rb b/actionpack/lib/action_view/helpers/tags/url_field.rb
new file mode 100644
index 0000000000..1ffdfe0b3c
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/url_field.rb
@@ -0,0 +1,8 @@
+module ActionView
+ module Helpers
+ module Tags
+ class UrlField < TextField #:nodoc:
+ 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 21074efe86..8cd7cf0052 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -1,6 +1,6 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/filters'
-require 'action_view/helpers/tag_helper'
+require 'active_support/core_ext/array/extract_options'
module ActionView
# = Action View Text Helpers
@@ -31,12 +31,12 @@ module ActionView
extend ActiveSupport::Concern
include SanitizeHelper
+ include TagHelper
# The preferred method of outputting text in your views is to use the
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
# do not operate as expected in an eRuby code block. If you absolutely must
# output text within a non-output code block (i.e., <% %>), you can use the concat method.
#
- # ==== Examples
# <%
# concat "hello"
# # is the equivalent of <%= "hello" %>
@@ -44,7 +44,7 @@ module ActionView
# if logged_in
# concat "Logged in!"
# else
- # concat link_to('login', :action => login)
+ # concat link_to('login', :action => :login)
# end
# # will either display "Logged in!" or a login link
# %>
@@ -66,8 +66,6 @@ module ActionView
# used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags
# or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags).
#
- # ==== Examples
- #
# truncate("Once upon a time in a world far far away")
# # => "Once upon a time in a world..."
#
@@ -83,18 +81,16 @@ module ActionView
# truncate("<p>Once upon a time in a world far far away</p>")
# # => "<p>Once upon a time in a wo..."
def truncate(text, options = {})
- options.reverse_merge!(:length => 30)
- text.truncate(options.delete(:length), options) if text
+ text.truncate(options.fetch(:length, 30), options) if text
end
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
- # as a single-quoted string with \1 where the phrase is to be inserted (defaults to
- # '<strong class="highlight">\1</strong>')
+ # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
+ # '<mark>\1</mark>')
#
- # ==== Examples
# highlight('You searched for: rails', 'rails')
- # # => You searched for: <strong class="highlight">rails</strong>
+ # # => You searched for: <mark>rails</mark>
#
# highlight('You searched for: ruby, rails, dhh', 'actionpack')
# # => You searched for: ruby, rails, dhh
@@ -104,23 +100,15 @@ module ActionView
#
# highlight('You searched for: rails', 'rails', :highlighter => '<a href="search?q=\1">\1</a>')
# # => You searched for: <a href="search?q=rails">rails</a>
- #
- # You can still use <tt>highlight</tt> with the old API that accepts the
- # +highlighter+ as its optional third parameter:
- # highlight('You searched for: rails', 'rails', '<a href="search?q=\1">\1</a>') # => You searched for: <a href="search?q=rails">rails</a>
- def highlight(text, phrases, *args)
- options = args.extract_options!
- unless args.empty?
- options[:highlighter] = args[0] || '<strong class="highlight">\1</strong>'
- end
- options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
-
- text = sanitize(text) unless options[:sanitize] == false
+ def highlight(text, phrases, options = {})
+ highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
+
+ text = sanitize(text) if options.fetch(:sanitize, true)
if text.blank? || phrases.blank?
text
else
match = Array(phrases).map { |p| Regexp.escape(p) }.join('|')
- text.gsub(/(#{match})(?!(?:[^<]*?)(?:["'])[^<>]*>)/i, options[:highlighter])
+ text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
end.html_safe
end
@@ -130,7 +118,6 @@ module ActionView
# then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The resulting string
# will be stripped in any case. If the +phrase+ isn't found, nil is returned.
#
- # ==== Examples
# excerpt('This is an example', 'an', :radius => 5)
# # => ...s is an exam...
#
@@ -145,39 +132,27 @@ module ActionView
#
# excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ')
# # => <chop> is also an example
- #
- # You can still use <tt>excerpt</tt> with the old API that accepts the
- # +radius+ as its optional third and the +ellipsis+ as its
- # optional forth parameter:
- # excerpt('This is an example', 'an', 5) # => ...s is an exam...
- # excerpt('This is also an example', 'an', 8, '<chop> ') # => <chop> is also an example
- def excerpt(text, phrase, *args)
+ def excerpt(text, phrase, options = {})
return unless text && phrase
-
- options = args.extract_options!
- unless args.empty?
- options[:radius] = args[0] || 100
- options[:omission] = args[1] || "..."
- end
- options.reverse_merge!(:radius => 100, :omission => "...")
+ radius = options.fetch(:radius, 100)
+ omission = options.fetch(:omission, "...")
phrase = Regexp.escape(phrase)
- return unless found_pos = text.mb_chars =~ /(#{phrase})/i
+ return unless found_pos = text =~ /(#{phrase})/i
- start_pos = [ found_pos - options[:radius], 0 ].max
- end_pos = [ [ found_pos + phrase.mb_chars.length + options[:radius] - 1, 0].max, text.mb_chars.length ].min
+ start_pos = [ found_pos - radius, 0 ].max
+ end_pos = [ [ found_pos + phrase.length + radius - 1, 0].max, text.length ].min
- prefix = start_pos > 0 ? options[:omission] : ""
- postfix = end_pos < text.mb_chars.length - 1 ? options[:omission] : ""
+ prefix = start_pos > 0 ? omission : ""
+ postfix = end_pos < text.length - 1 ? omission : ""
- prefix + text.mb_chars[start_pos..end_pos].strip + postfix
+ prefix + text[start_pos..end_pos].strip + postfix
end
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
# +plural+ is supplied, it will use that when count is > 1, otherwise
- # it will use the Inflector to determine the plural form
+ # it will use the Inflector to determine the plural form.
#
- # ==== Examples
# pluralize(1, 'person')
# # => 1 person
#
@@ -190,39 +165,35 @@ module ActionView
# pluralize(0, 'person')
# # => 0 people
def pluralize(count, singular, plural = nil)
- "#{count || 0} " + ((count == 1 || count =~ /^1(\.0+)?$/) ? singular : (plural || singular.pluralize))
+ word = if (count == 1 || count =~ /^1(\.0+)?$/)
+ singular
+ else
+ plural || singular.pluralize
+ end
+
+ "#{count || 0} #{word}"
end
# Wraps the +text+ into lines no longer than +line_width+ width. This method
# breaks on the first whitespace character that does not exceed +line_width+
# (which is 80 by default).
#
- # ==== Examples
- #
# word_wrap('Once upon a time')
# # => Once upon a time
#
# word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...')
- # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\n a successor to the throne turned out to be more trouble than anyone could have\n imagined...
+ # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined...
#
# word_wrap('Once upon a time', :line_width => 8)
- # # => Once upon\na time
+ # # => Once\nupon a\ntime
#
# word_wrap('Once upon a time', :line_width => 1)
# # => Once\nupon\na\ntime
- #
- # You can still use <tt>word_wrap</tt> with the old API that accepts the
- # +line_width+ as its optional second parameter:
- # word_wrap('Once upon a time', 8) # => Once upon\na time
- def word_wrap(text, *args)
- options = args.extract_options!
- unless args.blank?
- options[:line_width] = args[0] || 80
- end
- options.reverse_merge!(:line_width => 80)
+ def word_wrap(text, options = {})
+ line_width = options.fetch(:line_width, 80)
text.split("\n").collect do |line|
- line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line
end * "\n"
end
@@ -237,12 +208,16 @@ module ActionView
#
# ==== Options
# * <tt>:sanitize</tt> - If +false+, does not sanitize +text+.
+ # * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt>
#
# ==== Examples
# my_text = "Here is some basic text...\n...with a line break."
#
# simple_format(my_text)
# # => "<p>Here is some basic text...\n<br />...with a line break.</p>"
+ #
+ # simple_format(my_text, {}, :wrapper_tag => "div")
+ # # => "<div>Here is some basic text...\n<br />...with a line break.</div>"
#
# more_text = "We want to put a paragraph...\n\n...right there."
#
@@ -254,17 +229,19 @@ module ActionView
#
# simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false)
# # => "<p><span>I'm allowed!</span> It's true.</p>"
- def simple_format(text, html_options={}, options={})
- text = '' if text.nil?
- text = text.dup
- start_tag = tag('p', html_options, true)
- text = sanitize(text) unless options[:sanitize] == false
- text = text.to_str
- text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
- text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph
- text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
- text.insert 0, start_tag
- text.html_safe.safe_concat("</p>")
+ def simple_format(text, html_options = {}, options = {})
+ wrapper_tag = options.fetch(:wrapper_tag, :p)
+
+ text = sanitize(text) if options.fetch(:sanitize, true)
+ paragraphs = split_paragraphs(text)
+
+ if paragraphs.empty?
+ content_tag(wrapper_tag, nil, html_options)
+ else
+ paragraphs.map { |paragraph|
+ content_tag(wrapper_tag, paragraph, html_options, options[:sanitize])
+ }.join("\n\n").html_safe
+ end
end
# Creates a Cycle object whose _to_s_ method cycles through elements of an
@@ -276,7 +253,6 @@ module ActionView
# and passing the name of the cycle. The current cycle string can be obtained
# anytime using the current_cycle method.
#
- # ==== Examples
# # Alternate CSS classes for even and odd numbers...
# @items = [1,2,3,4]
# <table>
@@ -306,12 +282,9 @@ module ActionView
# </tr>
# <% end %>
def cycle(first_value, *values)
- if (values.last.instance_of? Hash)
- params = values.pop
- name = params[:name]
- else
- name = "default"
- end
+ options = values.extract_options!
+ name = options.fetch(:name, 'default')
+
values.unshift(first_value)
cycle = get_cycle(name)
@@ -325,7 +298,6 @@ module ActionView
# for complex table highlighting or any other design need which requires
# the current cycle string in more than one place.
#
- # ==== Example
# # Alternate background colors
# @items = [1,2,3,4]
# <% @items.each do |item| %>
@@ -341,7 +313,6 @@ module ActionView
# Resets a cycle so that it starts from the first element the next time
# it is called. Pass in +name+ to reset a named cycle.
#
- # ==== Example
# # Alternate CSS classes for even and odd numbers...
# @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]]
# <table>
@@ -412,6 +383,14 @@ module ActionView
@_cycles = Hash.new unless defined?(@_cycles)
@_cycles[name] = cycle_object
end
+
+ def split_paragraphs(text)
+ return [] if text.blank?
+
+ text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t|
+ t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index be64dc823e..8171bea8ed 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -45,16 +45,27 @@ module ActionView
# you know what kind of output to expect when you call translate in a template.
def translate(key, options = {})
options.merge!(:rescue_format => :html) unless options.key?(:rescue_format)
- translation = I18n.translate(scope_key_by_partial(key), options)
- if html_safe_translation_key?(key) && translation.respond_to?(:html_safe)
- translation.html_safe
+ options[:default] = wrap_translate_defaults(options[:default]) if options[:default]
+ if html_safe_translation_key?(key)
+ html_safe_options = options.dup
+ options.except(*I18n::RESERVED_KEYS).each do |name, value|
+ unless name == :count && value.is_a?(Numeric)
+ html_safe_options[name] = ERB::Util.html_escape(value.to_s)
+ end
+ end
+ translation = I18n.translate(scope_key_by_partial(key), html_safe_options)
+
+ translation.respond_to?(:html_safe) ? translation.html_safe : translation
else
- translation
+ I18n.translate(scope_key_by_partial(key), options)
end
end
alias :t :translate
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
+ #
+ # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
+ # for more information.
def localize(*args)
I18n.localize(*args)
end
@@ -76,6 +87,21 @@ module ActionView
def html_safe_translation_key?(key)
key.to_s =~ /(\b|_|\.)html$/
end
+
+ def wrap_translate_defaults(defaults)
+ new_defaults = []
+ defaults = Array(defaults)
+ while key = defaults.shift
+ if key.is_a?(Symbol)
+ new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) }
+ break
+ else
+ new_defautls << key
+ end
+ end
+
+ new_defaults
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 0c2e1aa3a9..7e69547dab 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -23,20 +23,25 @@ module ActionView
include ActionDispatch::Routing::UrlFor
include TagHelper
- def _routes_context
- controller
- end
+ # We need to override url_optoins, _routes_context
+ # and optimize_routes_generation? to consider the controller.
- # Need to map default url options to controller one.
- # def default_url_options(*args) #:nodoc:
- # controller.send(:default_url_options, *args)
- # end
- #
- def url_options
+ def url_options #:nodoc:
return super unless controller.respond_to?(:url_options)
controller.url_options
end
+ def _routes_context #:nodoc:
+ controller
+ end
+ protected :_routes_context
+
+ def optimize_routes_generation? #:nodoc:
+ controller.respond_to?(:optimize_routes_generation?) ?
+ controller.optimize_routes_generation? : super
+ end
+ protected :optimize_routes_generation?
+
# Returns the URL for the set of +options+ provided. This takes the
# same options as +url_for+ in Action Controller (see the
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
@@ -55,7 +60,7 @@ module ActionView
#
# ==== Relying on named routes
#
- # Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will
+ # Passing a record (like an Active Record) instead of a Hash as the options parameter will
# trigger the named route for that record. The lookup will happen on the name of the class. So passing a
# Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as
# +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route).
@@ -97,13 +102,13 @@ module ActionView
# <%= url_for(:back) %>
# # if request.env["HTTP_REFERER"] is not set or is blank
# # => javascript:history.back()
- def url_for(options = {})
- options ||= {}
+ def url_for(options = nil)
case options
when String
options
- when Hash
- options = options.symbolize_keys.reverse_merge!(:only_path => options[:host].nil?)
+ when nil, Hash
+ options ||= {}
+ options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys)
super
when :back
controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
@@ -146,12 +151,12 @@ module ActionView
# create an HTML form and immediately submit the form for processing using
# the HTTP verb specified. Useful for having links perform a POST operation
# in dangerous actions like deleting a record (which search bots can follow
- # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt> and <tt>:put</tt>.
+ # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>.
# Note that if the user has JavaScript disabled, the request will fall back
# to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript
# disabled clicking the link will have no effect. If you are relying on the
# POST behavior, you should check for it in your controller's action by using
- # the request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>.
+ # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>.
# * <tt>:remote => true</tt> - This will allow the unobtrusive JavaScript
# driver to make an Ajax request to the URL in question instead of following
# the link. The drivers each provide mechanisms for listening for the
@@ -272,7 +277,7 @@ module ActionView
#
# There are a few special +html_options+:
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
- # <tt>:delete</tt> and <tt>:put</tt>. By default it will be <tt>:post</tt>.
+ # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
# * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to
# prompt with the question specified. If the user accepts, the link is
@@ -298,60 +303,57 @@ module ActionView
#
# <%= button_to "Create", :action => "create", :remote => true, :form => { "data-type" => "json" } %>
# # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
- # # <div><input value="Create" type="submit" /></div>
+ # # <div>
+ # # <input value="Create" type="submit" />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
+ # # </div>
# # </form>"
#
- #
+ #
# <%= button_to "Delete Image", { :action => "delete", :id => @image.id },
# :confirm => "Are you sure?", :method => :delete %>
# # => "<form method="post" action="/images/delete/1" class="button_to">
# # <div>
# # <input type="hidden" name="_method" value="delete" />
- # # <input data-confirm='Are you sure?' value="Delete" type="submit" />
+ # # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </div>
# # </form>"
#
#
# <%= button_to('Destroy', 'http://www.example.com', :confirm => 'Are you sure?',
- # :method => "delete", :remote => true, :disable_with => 'loading...') %>
+ # :method => "delete", :remote => true) %>
# # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
# # <div>
# # <input name='_method' value='delete' type='hidden' />
- # # <input value='Destroy' type='submit' disable_with='loading...' data-confirm='Are you sure?' />
+ # # <input value='Destroy' type='submit' data-confirm='Are you sure?' />
+ # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
# # </div>
# # </form>"
# #
def button_to(name, options = {}, html_options = {})
html_options = html_options.stringify_keys
- convert_boolean_attributes!(html_options, %w( disabled ))
+ convert_boolean_attributes!(html_options, %w(disabled))
- method_tag = ''
- if (method = html_options.delete('method')) && %w{put delete}.include?(method.to_s)
- method_tag = tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
- end
+ url = options.is_a?(String) ? options : url_for(options)
+ remote = html_options.delete('remote')
+
+ method = html_options.delete('method').to_s
+ method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ''.html_safe
- form_method = method.to_s == 'get' ? 'get' : 'post'
+ form_method = method == 'get' ? 'get' : 'post'
form_options = html_options.delete('form') || {}
form_options[:class] ||= html_options.delete('form_class') || 'button_to'
-
- remote = html_options.delete('remote')
-
- request_token_tag = ''
- if form_method == 'post' && protect_against_forgery?
- request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
- end
+ form_options.merge!(:method => form_method, :action => url)
+ form_options.merge!("data-remote" => "true") if remote
- url = options.is_a?(String) ? options : self.url_for(options)
- name ||= url
+ request_token_tag = form_method == 'post' ? token_tag : ''
html_options = convert_options_to_data_attributes(options, html_options)
+ html_options.merge!("type" => "submit", "value" => name || url)
- html_options.merge!("type" => "submit", "value" => name)
-
- form_options.merge!(:method => form_method, :action => url)
- form_options.merge!("data-remote" => "true") if remote
-
- "#{tag(:form, form_options, true)}<div>#{method_tag}#{tag("input", html_options)}#{request_token_tag}</div></form>".html_safe
+ inner_tags = method_tag.safe_concat tag('input', html_options).safe_concat request_token_tag
+ content_tag('form', content_tag('div', inner_tags), form_options)
end
@@ -476,7 +478,7 @@ module ActionView
# string given as the value.
# * <tt>:subject</tt> - Preset the subject line of the email.
# * <tt>:body</tt> - Preset the body of the email.
- # * <tt>:cc</tt> - Carbon Copy addition recipients on the email.
+ # * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
#
# ==== Examples
@@ -484,7 +486,7 @@ module ActionView
# # => <a href="mailto:me@domain.com">me@domain.com</a>
#
# mail_to "me@domain.com", "My email", :encode => "javascript"
- # # => <script type="text/javascript">eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
+ # # => <script>eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
#
# mail_to "me@domain.com", "My email", :encode => "hex"
# # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
@@ -503,7 +505,7 @@ module ActionView
extras = %w{ cc bcc body subject }.map { |item|
option = html_options.delete(item) || next
- "#{item}=#{Rack::Utils.escape(option).gsub("+", "%20")}"
+ "#{item}=#{Rack::Utils.escape_path(option)}"
}.compact
extras = extras.empty? ? '' : '?' + ERB::Util.html_escape(extras.join('&'))
@@ -518,7 +520,7 @@ module ActionView
"document.write('#{html}');".each_byte do |c|
string << sprintf("%%%x", c)
end
- "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
+ "<script>eval(decodeURIComponent('#{string}'))</script>".html_safe
when "hex"
email_address_encoded = email_address_obfuscated.unpack('C*').map {|c|
sprintf("&#%d;", c)
@@ -599,11 +601,7 @@ module ActionView
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function
# work with things like ?order=asc
- if url_string.index("?")
- request_uri = request.fullpath
- else
- request_uri = request.path
- end
+ request_uri = url_string.index("?") ? request.fullpath : request.path
if url_string =~ /^\w+:\/\//
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
@@ -618,13 +616,11 @@ module ActionView
html_options = html_options.stringify_keys
html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options)
- disable_with = html_options.delete("disable_with")
confirm = html_options.delete('confirm')
method = html_options.delete('method')
- html_options["data-disable-with"] = disable_with if disable_with
html_options["data-confirm"] = confirm if confirm
- add_method_to_attributes!(html_options, method) if method
+ add_method_to_attributes!(html_options, method) if method
html_options
else
@@ -633,32 +629,16 @@ module ActionView
end
def link_to_remote_options?(options)
- options.is_a?(Hash) && options.key?('remote') && options.delete('remote')
+ options.is_a?(Hash) && options.delete('remote')
end
def add_method_to_attributes!(html_options, method)
if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/
- html_options["rel"] = "#{html_options["rel"]} nofollow".strip
+ html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip
end
html_options["data-method"] = method
end
- def options_for_javascript(options)
- if options.empty?
- '{}'
- else
- "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
- end
- end
-
- def array_or_string_for_javascript(option)
- if option.kind_of?(Array)
- "['#{option.join('\',\'')}']"
- elsif !option.nil?
- "'#{option}'"
- end
- end
-
# Processes the +html_options+ hash, converting the boolean
# attributes from true/false form into the form required by
# HTML/XHTML. (An attribute is considered to be boolean if
@@ -686,6 +666,19 @@ module ActionView
bool_attrs.each { |x| html_options[x] = x if html_options.delete(x) }
html_options
end
+
+ def token_tag(token=nil)
+ if token != false && protect_against_forgery?
+ token ||= form_authenticity_token
+ tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
+ else
+ ''
+ end
+ end
+
+ def method_tag(method)
+ tag('input', :type => 'hidden', :name => '_method', :value => method.to_s)
+ end
end
end
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index eb816b9446..8e9db634fb 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -37,6 +37,7 @@
# precision:
# significant: false
# strip_insignificant_zeros: false
+ format: "%n%"
# Used in number_to_precision()
precision:
@@ -146,9 +147,8 @@
# Default value for :prompt => true in FormOptionsHelper
prompt: "Please select"
- # Default translation keys for submit FormHelper
+ # Default translation keys for submit and button FormHelper
submit:
create: 'Create %{model}'
update: 'Update %{model}'
submit: 'Save %{model}'
-
diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb
index bf90d012bf..cc3a871576 100644
--- a/actionpack/lib/action_view/log_subscriber.rb
+++ b/actionpack/lib/action_view/log_subscriber.rb
@@ -12,9 +12,8 @@ module ActionView
alias :render_partial :render_template
alias :render_collection :render_template
- # TODO: Ideally, ActionView should have its own logger so it does not depend on AC.logger
def logger
- ActionController::Base.logger if defined?(ActionController::Base)
+ ActionView::Base.logger
end
protected
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index fa4bf70f77..b7945a23be 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/module/remove_method'
@@ -10,7 +9,7 @@ module ActionView
# generate a key, given to view paths, used in the resolver cache lookup. Since
# this key is generated just once during the request, it speeds up all cache accesses.
class LookupContext #:nodoc:
- attr_accessor :prefixes
+ attr_accessor :prefixes, :rendered_format
mattr_accessor :fallbacks
@@fallbacks = FallbackFileSystemResolver.instances
@@ -20,7 +19,7 @@ module ActionView
def self.register_detail(name, options = {}, &block)
self.registered_details << name
- initialize = registered_details.map { |n| "self.#{n} = details[:#{n}]" }
+ initialize = registered_details.map { |n| "@details[:#{n}] = details[:#{n}] || default_#{n}" }
Accessors.send :define_method, :"default_#{name}", &block
Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
@@ -29,7 +28,7 @@ module ActionView
end
def #{name}=(value)
- value = Array.wrap(value.presence || default_#{name})
+ value = value.present? ? Array(value) : default_#{name}
_set_detail(:#{name}, value) if value != @details[:#{name}]
end
@@ -44,7 +43,7 @@ module ActionView
module Accessors #:nodoc:
end
- register_detail(:locale) { [I18n.locale, I18n.default_locale] }
+ register_detail(:locale) { [I18n.locale, I18n.default_locale].uniq }
register_detail(:formats) { Mime::SET.symbols }
register_detail(:handlers){ Template::Handlers.extensions }
@@ -56,7 +55,11 @@ module ActionView
@details_keys = Hash.new
def self.get(details)
- @details_keys[details.freeze] ||= new
+ @details_keys[details] ||= new
+ end
+
+ def self.clear
+ @details_keys.clear
end
def initialize
@@ -85,9 +88,9 @@ module ActionView
protected
def _set_detail(key, value)
+ @details = @details.dup if @details_key
@details_key = nil
- @details = @details.dup if @details.frozen?
- @details[key] = value.freeze
+ @details[key] = value
end
end
@@ -98,7 +101,7 @@ module ActionView
# Whenever setting view paths, makes a copy so we can manipulate then in
# instance objects as we wish.
def view_paths=(paths)
- @view_paths = ActionView::PathSet.new(Array.wrap(paths))
+ @view_paths = ActionView::PathSet.new(Array(paths))
end
def find(name, prefixes = [], partial = false, keys = [], options = {})
@@ -147,27 +150,18 @@ module ActionView
# as well as incorrectly putting part of the path in the template
# name instead of the prefix.
def normalize_name(name, prefixes) #:nodoc:
- name = name.to_s.sub(handlers_regexp) do |match|
- ActiveSupport::Deprecation.warn "Passing a template handler in the template name is deprecated. " \
- "You can simply remove the handler name or pass render :handlers => [:#{match[1..-1]}] instead.", caller
- ""
- end
+ prefixes = nil if prefixes.blank?
+ parts = name.to_s.split('/')
+ parts.shift if parts.first.empty?
+ name = parts.pop
- parts = name.split('/')
- name = parts.pop
+ return name, prefixes || [""] if parts.empty?
- prefixes = if prefixes.blank?
- [parts.join('/')]
- else
- prefixes.map { |prefix| [prefix, *parts].compact.join('/') }
- end
+ parts = parts.join('/')
+ prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
return name, prefixes
end
-
- def handlers_regexp #:nodoc:
- @@handlers_regexp ||= /\.(?:#{default_handlers.join('|')})$/
- end
end
include Accessors
@@ -176,23 +170,15 @@ module ActionView
def initialize(view_paths, details = {}, prefixes = [])
@details, @details_key = {}, nil
- @frozen_formats, @skip_default_locale = false, false
+ @skip_default_locale = false
@cache = true
@prefixes = prefixes
+ @rendered_format = nil
self.view_paths = view_paths
initialize_details(details)
end
- # Freeze the current formats in the lookup context. By freezing them, you
- # that next template lookups are not going to modify the formats. The con
- # use this, to ensure that formats won't be further modified (as it does
- def freeze_formats(formats, unless_frozen=false) #:nodoc:
- return if unless_frozen && @frozen_formats
- self.formats = formats
- @frozen_formats = true
- end
-
# Override formats= to expand ["*/*"] values and automatically
# add :html as fallback to :js.
def formats=(values)
@@ -227,7 +213,6 @@ module ActionView
end
# A method which only uses the first format in the formats array for layout lookup.
- # This method plays straight with instance variables for performance reasons.
def with_layout_format
if formats.size == 1
yield
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 80391d72cc..9f5e3be454 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -7,6 +7,18 @@ module ActionView
config.action_view = ActiveSupport::OrderedOptions.new
config.action_view.stylesheet_expansions = {}
config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) }
+ config.action_view.embed_authenticity_token_in_remote_forms = false
+
+ initializer "action_view.embed_authenticity_token_in_remote_forms" do |app|
+ ActiveSupport.on_load(:action_view) do
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms =
+ app.config.action_view.delete(:embed_authenticity_token_in_remote_forms)
+ end
+ end
+
+ initializer "action_view.logger" do
+ ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger }
+ end
initializer "action_view.cache_asset_ids" do |app|
unless app.config.cache_classes
diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb
index c0936441ac..72616b7463 100644
--- a/actionpack/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb
@@ -1,7 +1,7 @@
module ActionView
class AbstractRenderer #:nodoc:
delegate :find_template, :template_exists?, :with_fallbacks, :update_details,
- :with_layout_format, :formats, :freeze_formats, :to => :@lookup_context
+ :with_layout_format, :formats, :to => :@lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
@@ -12,28 +12,14 @@ module ActionView
end
protected
-
+
def extract_details(options)
- details = {}
- @lookup_context.registered_details.each do |key|
+ @lookup_context.registered_details.each_with_object({}) do |key, details|
next unless value = options[key]
- details[key] = Array.wrap(value)
- end
- details
- end
-
- def extract_format(value, details)
- if value.is_a?(String) && value.sub!(formats_regexp, "")
- ActiveSupport::Deprecation.warn "Passing the format in the template name is deprecated. " \
- "Please pass render with :formats => [:#{$1}] instead.", caller
- details[:formats] ||= [$1.to_sym]
+ details[key] = Array(value)
end
end
- def formats_regexp
- @@formats_regexp ||= /\.(#{Mime::SET.symbols.join('|')})$/
- end
-
def instrument(name, options={})
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
end
diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb
index 15cb9d0f76..9100545718 100644
--- a/actionpack/lib/action_view/renderer/partial_renderer.rb
+++ b/actionpack/lib/action_view/renderer/partial_renderer.rb
@@ -32,7 +32,7 @@ module ActionView
#
# <%= render :partial => "account", :object => @buyer %>
#
- # would provide the +@buyer+ object to the partial, available under the local variable +account+ and is
+ # would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is
# equivalent to:
#
# <%= render :partial => "account", :locals => { :account => @buyer } %>
@@ -82,16 +82,17 @@ module ActionView
#
# This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from.
#
- # == Rendering objects with the RecordIdentifier
+ # == Rendering objects that respond to `to_partial_path`
#
- # Instead of explicitly naming the location of a partial, you can also let the RecordIdentifier do the work if
- # you're following its conventions for RecordIdentifier#partial_path. Examples:
+ # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work
+ # and pick the proper path by checking `to_partial_path` method.
#
- # # @account is an Account instance, so it uses the RecordIdentifier to replace
+ # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
# # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
# <%= render :partial => @account %>
#
- # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
+ # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
+ # # that's why we can replace:
# # <%= render :partial => "posts/post", :collection => @posts %>
# <%= render :partial => @posts %>
#
@@ -106,13 +107,14 @@ module ActionView
# # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
# <%= render "account", :account => @buyer %>
#
- # # @account is an Account instance, so it uses the RecordIdentifier to replace
- # # <%= render :partial => "accounts/account", :locals => { :account => @account } %>
- # <%= render(@account) %>
+ # # @account.to_partial_path returns 'accounts/account', so it can be used to replace:
+ # # <%= render :partial => "accounts/account", :locals => { :account => @account} %>
+ # <%= render @account %>
#
- # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace
+ # # @posts is an array of Post instances, so every post record returns 'posts/post' on `to_partial_path`,
+ # # that's why we can replace:
# # <%= render :partial => "posts/post", :collection => @posts %>
- # <%= render(@posts) %>
+ # <%= render @posts %>
#
# == Rendering partials with layouts
#
@@ -156,6 +158,47 @@ module ActionView
# Name: <%= user.name %>
# </div>
#
+ # If a collection is given, the layout will be rendered once for each item in
+ # the collection. Just think these two snippets have the same output:
+ #
+ # <%# app/views/users/_user.html.erb %>
+ # Name: <%= user.name %>
+ #
+ # <%# app/views/users/index.html.erb %>
+ # <%# This does not use layouts %>
+ # <ul>
+ # <% users.each do |user| -%>
+ # <li>
+ # <%= render :partial => "user", :locals => { :user => user } %>
+ # </li>
+ # <% end -%>
+ # </ul>
+ #
+ # <%# app/views/users/_li_layout.html.erb %>
+ # <li>
+ # <%= yield %>
+ # </li>
+ #
+ # <%# app/views/users/index.html.erb %>
+ # <ul>
+ # <%= render :partial => "user", :layout => "li_layout", :collection => users %>
+ # </ul>
+ #
+ # Given two users whose names are Alice and Bob, these snippets return:
+ #
+ # <ul>
+ # <li>
+ # Name: Alice
+ # </li>
+ # <li>
+ # Name: Bob
+ # </li>
+ # </ul>
+ #
+ # The current object being rendered, as well as the object_counter, will be
+ # available as local variables inside the layout template under the same names
+ # as available in the partial.
+ #
# You can also apply a layout to a block within any template:
#
# <%# app/views/users/_chief.html.erb &>
@@ -205,19 +248,26 @@ module ActionView
# Deadline: <%= user.deadline %>
# <%- end -%>
# <% end %>
- class PartialRenderer < AbstractRenderer #:nodoc:
- PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
+ class PartialRenderer < AbstractRenderer
+ PREFIXED_PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} }
def initialize(*)
super
@context_prefix = @lookup_context.prefixes.first
- @partial_names = PARTIAL_NAMES[@context_prefix]
end
def render(context, options, block)
setup(context, options, block)
identifier = (@template = find_partial) ? @template.identifier : @path
+ @lookup_context.rendered_format ||= begin
+ if @template && @template.formats.present?
+ @template.formats.first
+ else
+ formats.first
+ end
+ end
+
if @collection
instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
render_collection
@@ -233,7 +283,7 @@ module ActionView
return nil if @collection.blank?
if @options.key?(:spacer_template)
- spacer = find_template(@options[:spacer_template]).render(@view, @locals)
+ spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
end
result = @template ? collection_with_template : collection_without_template
@@ -241,11 +291,11 @@ module ActionView
end
def render_partial
- locals, view, block = @locals, @view, @block
+ view, locals, block = @view, @locals, @block
object, as = @object, @variable
if !block && (layout = @options[:layout])
- layout = find_template(layout)
+ layout = find_template(layout, @template_keys)
end
object ||= locals[as]
@@ -287,17 +337,17 @@ module ActionView
if @path
@variable, @variable_counter = retrieve_variable(@path)
+ @template_keys = retrieve_template_keys
else
paths.map! { |path| retrieve_variable(path).unshift(path) }
end
if String === partial && @variable.to_s !~ /^[a-z_][a-zA-Z_0-9]*$/
raise ArgumentError.new("The partial name (#{partial}) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a letter or underscore, " +
- "and is followed by any combinations of letters, numbers, or underscores.")
+ "make sure your partial name starts with a lowercase letter or underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.")
end
- extract_format(@path, @details)
self
end
@@ -309,55 +359,55 @@ module ActionView
end
def collection_from_object
- if @object.respond_to?(:to_ary)
- @object.to_ary
- end
+ @object.to_ary if @object.respond_to?(:to_ary)
end
def find_partial
if path = @path
- locals = @locals.keys
- locals << @variable
- locals << @variable_counter if @collection
- find_template(path, locals)
+ find_template(path, @template_keys)
end
end
- def find_template(path=@path, locals=@locals.keys)
+ def find_template(path, locals)
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
@lookup_context.find_template(path, prefixes, true, locals, @details)
end
def collection_with_template
- segments, locals, template = [], @locals, @template
+ view, locals, template = @view, @locals, @template
as, counter = @variable, @variable_counter
- locals[counter] = -1
-
- @collection.each do |object|
- locals[counter] += 1
- locals[as] = object
- segments << template.render(@view, locals)
+ if layout = @options[:layout]
+ layout = find_template(layout, @template_keys)
end
- segments
+ index = -1
+ @collection.map do |object|
+ locals[as] = object
+ locals[counter] = (index += 1)
+
+ content = template.render(view, locals)
+ content = layout.render(view, locals) { content } if layout
+ content
+ end
end
def collection_without_template
- segments, locals, collection_data = [], @locals, @collection_data
- index, template, cache = -1, nil, {}
- keys = @locals.keys
+ view, locals, collection_data = @view, @locals, @collection_data
+ cache = {}
+ keys = @locals.keys
- @collection.each_with_index do |object, i|
- path, *data = collection_data[i]
- template = (cache[path] ||= find_template(path, keys + data))
- locals[data[0]] = object
- locals[data[1]] = (index += 1)
- segments << template.render(@view, locals)
- end
+ index = -1
+ @collection.map do |object|
+ index += 1
+ path, as, counter = collection_data[index]
+
+ locals[as] = object
+ locals[counter] = index
- @template = template
- segments
+ template = (cache[path] ||= find_template(path, keys + [as, counter]))
+ template.render(view, locals)
+ end
end
def partial_path(object = @object)
@@ -366,32 +416,46 @@ module ActionView
path = if object.respond_to?(:to_partial_path)
object.to_partial_path
else
- ActiveSupport::Deprecation.warn "ActiveModel-compatible objects whose classes return a #model_name that responds to #partial_path are deprecated. Please respond to #to_partial_path directly instead."
- object.class.model_name.partial_path
+ raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
end
- @partial_names[path] ||= path.dup.tap do |object_path|
- merge_prefix_into_object_path(@context_prefix, object_path)
+ if @view.prefix_partial_path_with_controller_namespace
+ prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
+ else
+ path
end
end
+ def prefixed_partial_names
+ @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
+ end
+
def merge_prefix_into_object_path(prefix, object_path)
if prefix.include?(?/) && object_path.include?(?/)
- overlap = []
+ prefixes = []
prefix_array = File.dirname(prefix).split('/')
object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
prefix_array.each_with_index do |dir, index|
- overlap << dir if dir == object_path_array[index]
+ break if dir == object_path_array[index]
+ prefixes << dir
end
- object_path.gsub!(/^#{overlap.join('/')}\//,'')
- object_path.insert(0, "#{File.dirname(prefix)}/")
+ (prefixes << object_path).join("/")
+ else
+ object_path
end
end
+ def retrieve_template_keys
+ keys = @locals.keys
+ keys << @variable
+ keys << @variable_counter if @collection
+ keys
+ end
+
def retrieve_variable(path)
- variable = @options[:as].try(:to_sym) || path[%r'_?(\w+)(\.\w+)*$', 1].to_sym
+ variable = @options.fetch(:as) { path[%r'_?(\w+)(\.\w+)*$', 1] }.try(:to_sym)
variable_counter = :"#{variable}_counter" if @collection
[variable, variable_counter]
end
diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
index 1ccf5a8ddb..f46aabd2be 100644
--- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb
@@ -1,7 +1,4 @@
-# 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)
+require 'fiber'
module ActionView
# == TODO
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index ac91d333ba..ae923de24e 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -1,14 +1,18 @@
require 'active_support/core_ext/object/try'
-require 'active_support/core_ext/array/wrap'
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
def render(context, options)
@view = context
@details = extract_details(options)
- extract_format(options[:file] || options[:template], @details)
template = determine_template(options)
- freeze_formats(template.formats, true)
+ context = @lookup_context
+
+ unless context.rendered_format
+ context.formats = template.formats unless template.formats.empty?
+ context.rendered_format = context.formats.first
+ end
+
render_template(template, options[:layout], options[:locals])
end
@@ -26,6 +30,8 @@ module ActionView
elsif options.key?(:template)
options[:template].respond_to?(:render) ?
options[:template] : find_template(options[:template], options[:prefixes], false, keys, @details)
+ else
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option."
end
end
@@ -58,14 +64,28 @@ module ActionView
# context object. If no layout is found, it checks if at least a layout with
# the given name exists across all details before raising the error.
def find_layout(layout, keys)
- begin
- with_layout_format do
- layout =~ /^\// ?
- with_fallbacks { find_template(layout, nil, false, keys, @details) } : find_template(layout, nil, false, keys, @details)
+ with_layout_format { resolve_layout(layout, keys) }
+ end
+
+ def resolve_layout(layout, keys)
+ case layout
+ when String
+ begin
+ if layout =~ /^\//
+ with_fallbacks { find_template(layout, nil, false, keys, @details) }
+ else
+ find_template(layout, nil, false, keys, @details)
+ end
+ rescue ActionView::MissingTemplate
+ all_details = @details.merge(:formats => @lookup_context.default_formats)
+ raise unless template_exists?(layout, nil, false, keys, all_details)
end
- rescue ActionView::MissingTemplate
- all_details = @details.merge(:formats => @lookup_context.default_formats)
- raise unless template_exists?(layout, nil, false, keys, all_details)
+ when Proc
+ resolve_layout(layout.call, keys)
+ when FalseClass
+ nil
+ else
+ layout
end
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 10797c010f..edb3d427d5 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/kernel/singleton_class'
@@ -122,7 +121,7 @@ module ActionView
@locals = details[:locals] || []
@virtual_path = details[:virtual_path]
@updated_at = details[:updated_at] || Time.now
- @formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f }
+ @formats = Array(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f }
end
# Returns if the underlying handler supports streaming. If so,
@@ -173,6 +172,50 @@ module ActionView
@inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier
end
+ # This method is responsible for properly setting the encoding of the
+ # source. Until this point, we assume that the source is BINARY data.
+ # If no additional information is supplied, we assume the encoding is
+ # the same as <tt>Encoding.default_external</tt>.
+ #
+ # The user can also specify the encoding via a comment on the first
+ # line of the template (# encoding: NAME-OF-ENCODING). This will work
+ # with any template engine, as we process out the encoding comment
+ # before passing the source on to the template engine, leaving a
+ # blank line in its stead.
+ def encode!
+ return unless source.encoding == Encoding::BINARY
+
+ # Look for # encoding: *. If we find one, we'll encode the
+ # String in that encoding, otherwise, we'll use the
+ # default external encoding.
+ if source.sub!(/\A#{ENCODING_FLAG}/, '')
+ encoding = magic_encoding = $1
+ else
+ encoding = Encoding.default_external
+ end
+
+ # Tag the source with the default external encoding
+ # or the encoding specified in the file
+ source.force_encoding(encoding)
+
+ # If the user didn't specify an encoding, and the handler
+ # handles encodings, we simply pass the String as is to
+ # the handler (with the default_external tag)
+ if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
+ source
+ # Otherwise, if the String is valid in the encoding,
+ # encode immediately to default_internal. This means
+ # that if a handler doesn't handle encodings, it will
+ # always get Strings in the default_internal
+ elsif source.valid_encoding?
+ source.encode!
+ # Otherwise, since the String is invalid in the encoding
+ # specified, raise an exception
+ else
+ raise WrongEncodingError.new(source, encoding)
+ end
+ end
+
protected
# Compile a template. This method ensures a template is compiled
@@ -195,15 +238,7 @@ module ActionView
end
# Among other things, this method is responsible for properly setting
- # the encoding of the source. Until this point, we assume that the
- # source is BINARY data. If no additional information is supplied,
- # we assume the encoding is the same as <tt>Encoding.default_external</tt>.
- #
- # The user can also specify the encoding via a comment on the first
- # line of the template (# encoding: NAME-OF-ENCODING). This will work
- # with any template engine, as we process out the encoding comment
- # before passing the source on to the template engine, leaving a
- # blank line in its stead.
+ # the encoding of the compiled template.
#
# If the template engine handles encodings, we send the encoded
# String to the engine without further processing. This allows
@@ -215,40 +250,8 @@ module ActionView
# In general, this means that templates will be UTF-8 inside of Rails,
# regardless of the original source encoding.
def compile(view, mod) #:nodoc:
+ encode!
method_name = self.method_name
-
- if source.encoding_aware?
- # Look for # encoding: *. If we find one, we'll encode the
- # String in that encoding, otherwise, we'll use the
- # default external encoding.
- if source.sub!(/\A#{ENCODING_FLAG}/, '')
- encoding = magic_encoding = $1
- else
- encoding = Encoding.default_external
- end
-
- # Tag the source with the default external encoding
- # or the encoding specified in the file
- source.force_encoding(encoding)
-
- # If the user didn't specify an encoding, and the handler
- # handles encodings, we simply pass the String as is to
- # the handler (with the default_external tag)
- if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding?
- source
- # Otherwise, if the String is valid in the encoding,
- # encode immediately to default_internal. This means
- # that if a handler doesn't handle encodings, it will
- # always get Strings in the default_internal
- elsif source.valid_encoding?
- source.encode!
- # Otherwise, since the String is invalid in the encoding
- # specified, raise an exception
- else
- raise WrongEncodingError.new(source, encoding)
- end
- end
-
code = @handler.call(self)
# Make sure that the resulting String to be evalled is in the
@@ -261,20 +264,18 @@ module ActionView
end
end_src
- if source.encoding_aware?
- # Make sure the source is in the encoding of the returned code
- source.force_encoding(code.encoding)
+ # Make sure the source is in the encoding of the returned code
+ source.force_encoding(code.encoding)
- # In case we get back a String from a handler that is not in
- # BINARY or the default_internal, encode it to the default_internal
- source.encode!
+ # In case we get back a String from a handler that is not in
+ # BINARY or the default_internal, encode it to the default_internal
+ source.encode!
- # Now, validate that the source we got back from the template
- # handler is valid in the default_internal. This is for handlers
- # that handle encoding but screw up
- unless source.valid_encoding?
- raise WrongEncodingError.new(@source, Encoding.default_internal)
- end
+ # Now, validate that the source we got back from the template
+ # handler is valid in the default_internal. This is for handlers
+ # that handle encoding but screw up
+ unless source.valid_encoding?
+ raise WrongEncodingError.new(@source, Encoding.default_internal)
end
begin
@@ -287,7 +288,7 @@ module ActionView
logger.debug "Backtrace: #{e.backtrace.join("\n")}"
end
- raise ActionView::Template::Error.new(self, {}, e)
+ raise ActionView::Template::Error.new(self, e)
end
end
@@ -296,9 +297,12 @@ module ActionView
e.sub_template_of(self)
raise e
else
- assigns = view.respond_to?(:assigns) ? view.assigns : {}
- template = @virtual_path ? refresh(view) : self
- raise Template::Error.new(template, assigns, e)
+ template = self
+ unless template.source
+ template = refresh(view)
+ template.encode!
+ end
+ raise Template::Error.new(template, e)
end
end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index 587e37a84f..d8258f7b11 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -1,4 +1,3 @@
-require "active_support/core_ext/array/wrap"
require "active_support/core_ext/enumerable"
module ActionView
@@ -30,7 +29,7 @@ module ActionView
def initialize(paths, path, prefixes, partial, details, *)
@path = path
- prefixes = Array.wrap(prefixes)
+ prefixes = Array(prefixes)
template_type = if partial
"partial"
elsif path =~ /layouts/i
@@ -56,9 +55,9 @@ module ActionView
attr_reader :original_exception, :backtrace
- def initialize(template, assigns, original_exception)
+ def initialize(template, original_exception)
super(original_exception.message)
- @template, @assigns, @original_exception = template, assigns.dup, original_exception
+ @template, @original_exception = template, original_exception
@sub_templates = nil
@backtrace = original_exception.backtrace
end
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index aa693335e3..41b14373a3 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -4,10 +4,12 @@ module ActionView #:nodoc:
module Handlers #:nodoc:
autoload :ERB, 'action_view/template/handlers/erb'
autoload :Builder, 'action_view/template/handlers/builder'
+ autoload :Raw, 'action_view/template/handlers/raw'
def self.extended(base)
base.register_default_template_handler :erb, ERB.new
base.register_template_handler :builder, Builder.new
+ base.register_template_handler :raw, Raw.new
end
@@template_handlers = {}
@@ -17,15 +19,13 @@ module ActionView #:nodoc:
@@template_extensions ||= @@template_handlers.keys
end
- # Register a class that knows how to handle template files with the given
+ # Register an object that knows how to handle template files with the given
# extension. This can be used to implement new template types.
- # The constructor for the class must take the ActiveView::Base instance
- # as a parameter, and the class must implement a +render+ method that
- # takes the contents of the template to render as well as the Hash of
- # local assigns available to the template. The +render+ method ought to
- # return the rendered template as a string.
- def register_template_handler(extension, klass)
- @@template_handlers[extension.to_sym] = klass
+ # The handler must respond to `:call`, which will be passed the template
+ # and should return the rendered template as a String.
+ def register_template_handler(extension, handler)
+ @@template_handlers[extension.to_sym] = handler
+ @@template_extensions = nil
end
def template_handler_extensions
diff --git a/actionpack/lib/action_view/template/handlers/builder.rb b/actionpack/lib/action_view/template/handlers/builder.rb
index 2c52cfd90e..34397c3bcf 100644
--- a/actionpack/lib/action_view/template/handlers/builder.rb
+++ b/actionpack/lib/action_view/template/handlers/builder.rb
@@ -6,12 +6,21 @@ module ActionView
self.default_format = Mime::XML
def call(template)
- require 'builder'
+ require_engine
"xml = ::Builder::XmlMarkup.new(:indent => 2);" +
"self.output_buffer = xml.target!;" +
template.source +
";xml.target!;"
end
+
+ protected
+
+ def require_engine
+ @required ||= begin
+ require "builder"
+ true
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 77720e2bc8..19b9112afd 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -1,5 +1,5 @@
require 'action_dispatch/http/mime_type'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/core_ext/class/attribute'
require 'erubis'
module ActionView
@@ -44,10 +44,6 @@ module ActionView
class_attribute :erb_trim_mode
self.erb_trim_mode = '-'
- # Default format used by ERB.
- class_attribute :default_format
- self.default_format = Mime::HTML
-
# Default implementation used.
class_attribute :erb_implementation
self.erb_implementation = Erubis
@@ -67,23 +63,19 @@ module ActionView
end
def call(template)
- if template.source.encoding_aware?
- # First, convert to BINARY, so in case the encoding is
- # wrong, we can still find an encoding tag
- # (<%# encoding %>) inside the String using a regular
- # expression
- template_source = template.source.dup.force_encoding("BINARY")
+ # First, convert to BINARY, so in case the encoding is
+ # wrong, we can still find an encoding tag
+ # (<%# encoding %>) inside the String using a regular
+ # expression
+ template_source = template.source.dup.force_encoding("BINARY")
- erb = template_source.gsub(ENCODING_TAG, '')
- encoding = $2
+ erb = template_source.gsub(ENCODING_TAG, '')
+ encoding = $2
- erb.force_encoding valid_encoding(template.source.dup, encoding)
+ erb.force_encoding valid_encoding(template.source.dup, encoding)
- # Always make sure we return a String in the default_internal
- erb.encode!
- else
- erb = template.source.dup
- end
+ # Always make sure we return a String in the default_internal
+ erb.encode!
self.class.erb_implementation.new(
erb,
diff --git a/actionpack/lib/action_view/template/handlers/raw.rb b/actionpack/lib/action_view/template/handlers/raw.rb
new file mode 100644
index 0000000000..0c0d1fffcb
--- /dev/null
+++ b/actionpack/lib/action_view/template/handlers/raw.rb
@@ -0,0 +1,11 @@
+module ActionView
+ module Template::Handlers
+ class Raw
+ def call(template)
+ escaped = template.source.gsub(':', '\:')
+
+ '%q:' + escaped + ':;'
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index f855ea257c..fa2038f78d 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -1,6 +1,6 @@
require "pathname"
require "active_support/core_ext/class"
-require "active_support/core_ext/io"
+require "active_support/core_ext/class/attribute_accessors"
require "action_view/template"
module ActionView
@@ -171,13 +171,15 @@ module ActionView
def extract_handler_and_format(path, default_formats)
pieces = File.basename(path).split(".")
pieces.shift
- handler = Template.handler_for_extension(pieces.pop)
+ extension = pieces.pop
+ ActiveSupport::Deprecation.warn "The file #{path} did not specify a template handler. The default is currently ERB, but will change to RAW in the future." unless extension
+ handler = Template.handler_for_extension(extension)
format = pieces.last && Mime[pieces.last]
[handler, format]
end
end
- # A resolver that loads files from the filesystem. It allows to set your own
+ # A resolver that loads files from the filesystem. It allows setting your own
# resolving pattern. Such pattern can be a glob string supported by some variables.
#
# ==== Examples
@@ -193,7 +195,7 @@ module ActionView
#
# FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
#
- # If you don't specify pattern then the default will be used.
+ # If you don't specify a pattern then the default will be used.
#
# In order to use any of the customized resolvers above in a Rails application, you just need
# to configure ActionController::Base.view_paths in an initializer, for example:
@@ -205,10 +207,10 @@ module ActionView
#
# ==== Pattern format and variables
#
- # Pattern have to be a valid glob string, and it allows you to use the
+ # Pattern has to be a valid glob string, and it allows you to use the
# following variables:
#
- # * <tt>:prefix</tt> - usualy the controller path
+ # * <tt>:prefix</tt> - usually the controller path
# * <tt>:action</tt> - name of the action
# * <tt>:locale</tt> - possible locale versions
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 9ebe498192..fece499c94 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -59,8 +59,10 @@ module ActionView
end
def determine_default_helper_class(name)
- mod = name.sub(/Test$/, '').safe_constantize
+ mod = name.sub(/Test$/, '').constantize
mod.is_a?(Class) ? nil : mod
+ rescue NameError
+ nil
end
def helper_method(*methods)
@@ -183,32 +185,32 @@ module ActionView
alias_method :_view, :view
- INTERNAL_IVARS = %w{
- @__name__
- @__io__
- @_assertion_wrapped
- @_assertions
- @_result
- @_routes
- @controller
- @layouts
- @locals
- @method_name
- @output_buffer
- @partials
- @passed
- @rendered
- @request
- @routes
- @templates
- @options
- @test_passed
- @view
- @view_context_class
- }
+ INTERNAL_IVARS = [
+ :@__name__,
+ :@__io__,
+ :@_assertion_wrapped,
+ :@_assertions,
+ :@_result,
+ :@_routes,
+ :@controller,
+ :@layouts,
+ :@locals,
+ :@method_name,
+ :@output_buffer,
+ :@partials,
+ :@passed,
+ :@rendered,
+ :@request,
+ :@routes,
+ :@templates,
+ :@options,
+ :@test_passed,
+ :@view,
+ :@view_context_class
+ ]
def _user_defined_ivars
- instance_variables.map(&:to_s) - INTERNAL_IVARS
+ instance_variables - INTERNAL_IVARS
end
# Returns a Hash of instance variables and their values, as defined by
@@ -216,8 +218,8 @@ module ActionView
# rendered. This is generally intended for internal use and extension
# frameworks.
def view_assigns
- Hash[_user_defined_ivars.map do |var|
- [var[1, var.length].to_sym, instance_variable_get(var)]
+ Hash[_user_defined_ivars.map do |ivar|
+ [ivar[1..-1].to_sym, instance_variable_get(ivar)]
end]
end
@@ -233,10 +235,8 @@ module ActionView
super
end
end
-
end
include Behavior
-
end
end
diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake
deleted file mode 100644
index a61a121d55..0000000000
--- a/actionpack/lib/sprockets/assets.rake
+++ /dev/null
@@ -1,95 +0,0 @@
-require "fileutils"
-
-namespace :assets do
- def ruby_rake_task(task)
- env = ENV['RAILS_ENV'] || 'production'
- groups = ENV['RAILS_GROUPS'] || 'assets'
- args = [$0, task,"RAILS_ENV=#{env}","RAILS_GROUPS=#{groups}"]
- args << "--trace" if Rake.application.options.trace
- ruby(*args)
- end
-
- # We are currently running with no explicit bundler group
- # and/or no explicit environment - we have to reinvoke rake to
- # execute this task.
- def invoke_or_reboot_rake_task(task)
- if ENV['RAILS_GROUPS'].to_s.empty? || ENV['RAILS_ENV'].to_s.empty?
- ruby_rake_task task
- else
- Rake::Task[task].invoke
- end
- end
-
- desc "Compile all the assets named in config.assets.precompile"
- task :precompile do
- invoke_or_reboot_rake_task "assets:precompile:all"
- end
-
- namespace :precompile do
- def internal_precompile(digest=nil)
- unless Rails.application.config.assets.enabled
- warn "Cannot precompile assets if sprockets is disabled. Please set config.assets.enabled to true"
- exit
- end
-
- # Ensure that action view is loaded and the appropriate
- # sprockets hooks get executed
- _ = ActionView::Base
-
- config = Rails.application.config
- config.assets.compile = true
- config.assets.digest = digest unless digest.nil?
- config.assets.digests = {}
-
- env = Rails.application.assets
- target = File.join(Rails.public_path, config.assets.prefix)
- compiler = Sprockets::StaticCompiler.new(env,
- target,
- config.assets.precompile,
- :manifest_path => config.assets.manifest,
- :digest => config.assets.digest,
- :manifest => digest.nil?)
- compiler.compile
- end
-
- task :all do
- Rake::Task["assets:precompile:primary"].invoke
- # We need to reinvoke in order to run the secondary digestless
- # asset compilation run - a fresh Sprockets environment is
- # required in order to compile digestless assets as the
- # environment has already cached the assets on the primary
- # run.
- ruby_rake_task "assets:precompile:nondigest" if Rails.application.config.assets.digest
- end
-
- task :primary => ["assets:environment", "tmp:cache:clear"] do
- internal_precompile
- end
-
- task :nondigest => ["assets:environment", "tmp:cache:clear"] do
- internal_precompile(false)
- end
- end
-
- desc "Remove compiled assets"
- task :clean do
- invoke_or_reboot_rake_task "assets:clean:all"
- end
-
- namespace :clean do
- task :all => ["assets:environment", "tmp:cache:clear"] do
- config = Rails.application.config
- public_asset_path = File.join(Rails.public_path, config.assets.prefix)
- rm_rf public_asset_path, :secure => true
- end
- end
-
- task :environment do
- if Rails.application.config.assets.initialize_on_precompile
- Rake::Task["environment"].invoke
- else
- Rails.application.initialize!(:assets)
- Sprockets::Bootstrap.new(Rails.application).run
- end
- end
-end
diff --git a/actionpack/lib/sprockets/bootstrap.rb b/actionpack/lib/sprockets/bootstrap.rb
deleted file mode 100644
index 395b264fe7..0000000000
--- a/actionpack/lib/sprockets/bootstrap.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module Sprockets
- class Bootstrap
- def initialize(app)
- @app = app
- end
-
- # TODO: Get rid of config.assets.enabled
- def run
- app, config = @app, @app.config
- return unless app.assets
-
- config.assets.paths.each { |path| app.assets.append_path(path) }
-
- if config.assets.compress
- # temporarily hardcode default JS compressor to uglify. Soon, it will work
- # the same as SCSS, where a default plugin sets the default.
- unless config.assets.js_compressor == false
- app.assets.js_compressor = LazyCompressor.new { Sprockets::Compressors.registered_js_compressor(config.assets.js_compressor || :uglifier) }
- end
-
- unless config.assets.css_compressor == false
- app.assets.css_compressor = LazyCompressor.new { Sprockets::Compressors.registered_css_compressor(config.assets.css_compressor) }
- end
- end
-
- if config.assets.compile
- app.routes.prepend do
- mount app.assets => config.assets.prefix
- end
- end
-
- if config.assets.digest
- app.assets = app.assets.index
- end
- end
- end
-end
diff --git a/actionpack/lib/sprockets/compressors.rb b/actionpack/lib/sprockets/compressors.rb
deleted file mode 100644
index cb3e13314b..0000000000
--- a/actionpack/lib/sprockets/compressors.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-module Sprockets
- module Compressors
- @@css_compressors = {}
- @@js_compressors = {}
- @@default_css_compressor = nil
- @@default_js_compressor = nil
-
- def self.register_css_compressor(name, klass, options = {})
- @@default_css_compressor = name.to_sym if options[:default] || @@default_css_compressor.nil?
- @@css_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]}
- end
-
- def self.register_js_compressor(name, klass, options = {})
- @@default_js_compressor = name.to_sym if options[:default] || @@default_js_compressor.nil?
- @@js_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]}
- end
-
- def self.registered_css_compressor(name)
- if name.respond_to?(:to_sym)
- compressor = @@css_compressors[name.to_sym] || @@css_compressors[@@default_css_compressor]
- require compressor[:require] if compressor[:require]
- compressor[:klass].constantize.new
- else
- name
- end
- end
-
- def self.registered_js_compressor(name)
- if name.respond_to?(:to_sym)
- compressor = @@js_compressors[name.to_sym] || @@js_compressors[@@default_js_compressor]
- require compressor[:require] if compressor[:require]
- compressor[:klass].constantize.new
- else
- name
- end
- end
-
- # The default compressors must be registered in default plugins (ex. Sass-Rails)
- register_css_compressor(:scss, 'Sass::Rails::Compressor', :require => 'sass/rails/compressor', :default => true)
- register_js_compressor(:uglifier, 'Uglifier', :require => 'uglifier', :default => true)
-
- # Automaticaly register some compressors
- register_css_compressor(:yui, 'YUI::CssCompressor', :require => 'yui/compressor')
- register_js_compressor(:closure, 'Closure::Compiler', :require => 'closure-compiler')
- register_js_compressor(:yui, 'YUI::JavaScriptCompressor', :require => 'yui/compressor')
- end
-
- # An asset compressor which does nothing.
- #
- # This compressor simply returns the asset as-is, without any compression
- # whatsoever. It is useful in development mode, when compression isn't
- # needed but using the same asset pipeline as production is desired.
- class NullCompressor #:nodoc:
- def compress(content)
- content
- end
- end
-
- # An asset compressor which only initializes the underlying compression
- # engine when needed.
- #
- # This postpones the initialization of the compressor until
- # <code>#compress</code> is called the first time.
- class LazyCompressor #:nodoc:
- # Initializes a new LazyCompressor.
- #
- # The block should return a compressor when called, i.e. an object
- # which responds to <code>#compress</code>.
- def initialize(&block)
- @block = block
- end
-
- def compress(content)
- compressor.compress(content)
- end
-
- private
-
- def compressor
- @compressor ||= (@block.call || NullCompressor.new)
- end
- end
-end
diff --git a/actionpack/lib/sprockets/helpers.rb b/actionpack/lib/sprockets/helpers.rb
deleted file mode 100644
index fee48386e0..0000000000
--- a/actionpack/lib/sprockets/helpers.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-module Sprockets
- module Helpers
- autoload :RailsHelper, "sprockets/helpers/rails_helper"
- autoload :IsolatedHelper, "sprockets/helpers/isolated_helper"
- end
-end
diff --git a/actionpack/lib/sprockets/helpers/isolated_helper.rb b/actionpack/lib/sprockets/helpers/isolated_helper.rb
deleted file mode 100644
index 3adb928c45..0000000000
--- a/actionpack/lib/sprockets/helpers/isolated_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-module Sprockets
- module Helpers
- module IsolatedHelper
- def controller
- nil
- end
-
- def config
- Rails.application.config.action_controller
- end
- end
- end
-end
diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb
deleted file mode 100644
index ddf9b08b54..0000000000
--- a/actionpack/lib/sprockets/helpers/rails_helper.rb
+++ /dev/null
@@ -1,166 +0,0 @@
-require "action_view"
-
-module Sprockets
- module Helpers
- module RailsHelper
- extend ActiveSupport::Concern
- include ActionView::Helpers::AssetTagHelper
-
- def asset_paths
- @asset_paths ||= begin
- paths = RailsHelper::AssetPaths.new(config, controller)
- paths.asset_environment = asset_environment
- paths.asset_digests = asset_digests
- paths.compile_assets = compile_assets?
- paths.digest_assets = digest_assets?
- paths
- end
- end
-
- def javascript_include_tag(*sources)
- options = sources.extract_options!
- debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
- body = options.key?(:body) ? options.delete(:body) : false
- digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
-
- sources.collect do |source|
- if debug && asset = asset_paths.asset_for(source, 'js')
- asset.to_a.map { |dep|
- super(dep.to_s, { :src => asset_path(dep, :ext => 'js', :body => true, :digest => digest) }.merge!(options))
- }
- else
- super(source.to_s, { :src => asset_path(source, :ext => 'js', :body => body, :digest => digest) }.merge!(options))
- end
- end.join("\n").html_safe
- end
-
- def stylesheet_link_tag(*sources)
- options = sources.extract_options!
- debug = options.key?(:debug) ? options.delete(:debug) : debug_assets?
- body = options.key?(:body) ? options.delete(:body) : false
- digest = options.key?(:digest) ? options.delete(:digest) : digest_assets?
-
- sources.collect do |source|
- if debug && asset = asset_paths.asset_for(source, 'css')
- asset.to_a.map { |dep|
- super(dep.to_s, { :href => asset_path(dep, :ext => 'css', :body => true, :protocol => :request, :digest => digest) }.merge!(options))
- }
- else
- super(source.to_s, { :href => asset_path(source, :ext => 'css', :body => body, :protocol => :request, :digest => digest) }.merge!(options))
- end
- end.join("\n").html_safe
- end
-
- def asset_path(source, options = {})
- source = source.logical_path if source.respond_to?(:logical_path)
- path = asset_paths.compute_public_path(source, asset_prefix, options.merge(:body => true))
- options[:body] ? "#{path}?body=1" : path
- end
-
- def image_path(source)
- asset_path(source)
- end
- alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
-
- def javascript_path(source)
- asset_path(source)
- end
- alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with an javascript_path named route
-
- def stylesheet_path(source)
- asset_path(source)
- end
- alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with an stylesheet_path named route
-
- private
- def debug_assets?
- compile_assets? && (Rails.application.config.assets.debug || params[:debug_assets])
- rescue NoMethodError
- false
- end
-
- # Override to specify an alternative prefix for asset path generation.
- # When combined with a custom +asset_environment+, this can be used to
- # implement themes that can take advantage of the asset pipeline.
- #
- # If you only want to change where the assets are mounted, refer to
- # +config.assets.prefix+ instead.
- def asset_prefix
- Rails.application.config.assets.prefix
- end
-
- def asset_digests
- Rails.application.config.assets.digests
- end
-
- def compile_assets?
- Rails.application.config.assets.compile
- end
-
- def digest_assets?
- Rails.application.config.assets.digest
- end
-
- # Override to specify an alternative asset environment for asset
- # path generation. The environment should already have been mounted
- # at the prefix returned by +asset_prefix+.
- def asset_environment
- Rails.application.assets
- end
-
- class AssetPaths < ::ActionView::AssetPaths #:nodoc:
- attr_accessor :asset_environment, :asset_prefix, :asset_digests, :compile_assets, :digest_assets
-
- class AssetNotPrecompiledError < StandardError; end
-
- # Return the filesystem path for the source
- def compute_source_path(source, ext)
- asset_for(source, ext)
- end
-
- def asset_for(source, ext)
- source = source.to_s
- return nil if is_uri?(source)
- source = rewrite_extension(source, nil, ext)
- asset_environment[source]
- rescue Sprockets::FileOutsidePaths
- nil
- end
-
- def digest_for(logical_path)
- if digest_assets && asset_digests && (digest = asset_digests[logical_path])
- return digest
- end
-
- if compile_assets
- if digest_assets && asset = asset_environment[logical_path]
- return asset.digest_path
- end
- return logical_path
- else
- raise AssetNotPrecompiledError.new("#{logical_path} isn't precompiled")
- end
- end
-
- def rewrite_asset_path(source, dir, options = {})
- if source[0] == ?/
- source
- else
- source = digest_for(source) unless options[:digest] == false
- source = File.join(dir, source)
- source = "/#{source}" unless source =~ /^\//
- source
- end
- end
-
- def rewrite_extension(source, dir, ext)
- if ext && File.extname(source).empty?
- "#{source}.#{ext}"
- else
- source
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb
deleted file mode 100644
index 3d330bd91a..0000000000
--- a/actionpack/lib/sprockets/railtie.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require "action_controller/railtie"
-
-module Sprockets
- autoload :Bootstrap, "sprockets/bootstrap"
- autoload :Helpers, "sprockets/helpers"
- autoload :Compressors, "sprockets/compressors"
- autoload :LazyCompressor, "sprockets/compressors"
- autoload :NullCompressor, "sprockets/compressors"
- autoload :StaticCompiler, "sprockets/static_compiler"
-
- # TODO: Get rid of config.assets.enabled
- class Railtie < ::Rails::Railtie
- config.action_controller.default_asset_host_protocol = :relative
-
- rake_tasks do
- load "sprockets/assets.rake"
- end
-
- initializer "sprockets.environment", :group => :all do |app|
- config = app.config
- next unless config.assets.enabled
-
- require 'sprockets'
-
- app.assets = Sprockets::Environment.new(app.root.to_s) do |env|
- env.logger = ::Rails.logger
- env.version = ::Rails.env + "-#{config.assets.version}"
-
- if config.assets.cache_store != false
- env.cache = ActiveSupport::Cache.lookup_store(config.assets.cache_store) || ::Rails.cache
- end
- end
-
- if config.assets.manifest
- path = File.join(config.assets.manifest, "manifest.yml")
- else
- path = File.join(Rails.public_path, config.assets.prefix, "manifest.yml")
- end
-
- if File.exist?(path)
- config.assets.digests = YAML.load_file(path)
- end
-
- ActiveSupport.on_load(:action_view) do
- include ::Sprockets::Helpers::RailsHelper
- app.assets.context_class.instance_eval do
- include ::Sprockets::Helpers::IsolatedHelper
- include ::Sprockets::Helpers::RailsHelper
- end
- 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, and so that other Railties have an opportunity to
- # register compressors.
- config.after_initialize do |app|
- Sprockets::Bootstrap.new(app).run
- end
- end
-end
diff --git a/actionpack/lib/sprockets/static_compiler.rb b/actionpack/lib/sprockets/static_compiler.rb
deleted file mode 100644
index 32a9d66e6e..0000000000
--- a/actionpack/lib/sprockets/static_compiler.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'fileutils'
-
-module Sprockets
- class StaticCompiler
- attr_accessor :env, :target, :paths
-
- def initialize(env, target, paths, options = {})
- @env = env
- @target = target
- @paths = paths
- @digest = options.key?(:digest) ? options.delete(:digest) : true
- @manifest = options.key?(:manifest) ? options.delete(:manifest) : true
- @manifest_path = options.delete(:manifest_path) || target
- end
-
- def compile
- manifest = {}
- env.each_logical_path do |logical_path|
- next unless compile_path?(logical_path)
- if asset = env.find_asset(logical_path)
- manifest[logical_path] = write_asset(asset)
- end
- end
- write_manifest(manifest) if @manifest
- end
-
- def write_manifest(manifest)
- FileUtils.mkdir_p(@manifest_path)
- File.open("#{@manifest_path}/manifest.yml", 'wb') do |f|
- YAML.dump(manifest, f)
- end
- end
-
- def write_asset(asset)
- path_for(asset).tap do |path|
- filename = File.join(target, path)
- FileUtils.mkdir_p File.dirname(filename)
- asset.write_to(filename)
- asset.write_to("#{filename}.gz") if filename.to_s =~ /\.(css|js)$/
- end
- end
-
- def compile_path?(logical_path)
- paths.each do |path|
- case path
- when Regexp
- return true if path.match(logical_path)
- when Proc
- return true if path.call(logical_path)
- else
- return true if File.fnmatch(path.to_s, logical_path)
- end
- end
- false
- end
-
- def path_for(asset)
- @digest ? asset.digest_path : asset.logical_path
- end
- end
-end
diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb
index bf068aedcd..62f82a4c7a 100644
--- a/actionpack/test/abstract/abstract_controller_test.rb
+++ b/actionpack/test/abstract/abstract_controller_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'set'
module AbstractController
module Testing
@@ -28,7 +29,7 @@ module AbstractController
# Test Render mixin
# ====
class RenderingController < AbstractController::Base
- include ::AbstractController::Rendering
+ include AbstractController::Rendering
def _prefixes
[]
@@ -152,7 +153,7 @@ module AbstractController
# ====
# self._layout is used when defined
class WithLayouts < PrefixedViews
- include Layouts
+ include AbstractController::Layouts
private
def self.layout(formats)
@@ -254,7 +255,7 @@ module AbstractController
class TestActionMethodsReloading < ActiveSupport::TestCase
test "action_methods should be reloaded after defining a new method" do
- assert_equal ["index"], Me6.action_methods
+ assert_equal Set.new(["index"]), Me6.action_methods
end
end
diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb
index 2ebcebbbb7..c14d24905b 100644
--- a/actionpack/test/abstract/collector_test.rb
+++ b/actionpack/test/abstract/collector_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
module AbstractController
module Testing
class MyCollector
- include Collector
+ include AbstractController::Collector
attr_accessor :responses
def initialize
@@ -54,4 +54,4 @@ module AbstractController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb
index b28a5b5afb..9a7445de7b 100644
--- a/actionpack/test/abstract/helper_test.rb
+++ b/actionpack/test/abstract/helper_test.rb
@@ -7,7 +7,7 @@ module AbstractController
class ControllerWithHelpers < AbstractController::Base
include AbstractController::Rendering
- include Helpers
+ include AbstractController::Helpers
def with_module
render :inline => "Module <%= included_method %>"
@@ -44,7 +44,7 @@ module AbstractController
class AbstractHelpersBlock < ControllerWithHelpers
helper do
- include ::AbstractController::Testing::HelperyTest
+ include AbstractController::Testing::HelperyTest
end
end
diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb
index 86208899f8..558a45b87f 100644
--- a/actionpack/test/abstract/layouts_test.rb
+++ b/actionpack/test/abstract/layouts_test.rb
@@ -14,7 +14,10 @@ module AbstractControllerTests
"layouts/overwrite.erb" => "Overwrite <%= yield %>",
"layouts/with_false_layout.erb" => "False Layout <%= yield %>",
"abstract_controller_tests/layouts/with_string_implied_child.erb" =>
- "With Implied <%= yield %>"
+ "With Implied <%= yield %>",
+ "abstract_controller_tests/layouts/with_grand_child_of_implied.erb" =>
+ "With Grand Child <%= yield %>"
+
)]
end
@@ -57,14 +60,15 @@ module AbstractControllerTests
layout "hello_override"
end
- class WithNilChild < WithString
+ class WithStringImpliedChild < WithString
layout nil
end
- class WithStringImpliedChild < WithString
+ class WithChildOfImplied < WithStringImpliedChild
end
- class WithChildOfImplied < WithStringImpliedChild
+ class WithGrandChildOfImplied < WithStringImpliedChild
+ layout nil
end
class WithProc < Base
@@ -75,6 +79,27 @@ module AbstractControllerTests
end
end
+ class WithZeroArityProc < Base
+ layout proc { "overwrite" }
+
+ def index
+ render :template => ActionView::Template::Text.new("Hello zero arity proc!")
+ end
+ end
+
+ class WithProcInContextOfInstance < Base
+ def an_instance_method; end
+
+ layout proc {
+ break unless respond_to? :an_instance_method
+ "overwrite"
+ }
+
+ def index
+ render :template => ActionView::Template::Text.new("Hello again zero arity proc!")
+ end
+ end
+
class WithSymbol < Base
layout :hello
@@ -141,6 +166,30 @@ module AbstractControllerTests
end
end
+ class WithOnlyConditional < WithStringImpliedChild
+ layout "overwrite", :only => :show
+
+ def index
+ render :template => ActionView::Template::Text.new("Hello index!")
+ end
+
+ def show
+ render :template => ActionView::Template::Text.new("Hello show!")
+ end
+ end
+
+ class WithExceptConditional < WithStringImpliedChild
+ layout "overwrite", :except => :show
+
+ def index
+ render :template => ActionView::Template::Text.new("Hello index!")
+ end
+
+ def show
+ render :template => ActionView::Template::Text.new("Hello show!")
+ end
+ end
+
class TestBase < ActiveSupport::TestCase
test "when no layout is specified, and no default is available, render without a layout" do
controller = Blank.new
@@ -200,6 +249,18 @@ module AbstractControllerTests
assert_equal "Overwrite Hello proc!", controller.response_body
end
+ test "when layout is specified as a proc without parameters it works just the same" do
+ controller = WithZeroArityProc.new
+ controller.process(:index)
+ assert_equal "Overwrite Hello zero arity proc!", controller.response_body
+ end
+
+ test "when layout is specified as a proc without parameters the block is evaluated in the context of an instance" do
+ controller = WithProcInContextOfInstance.new
+ controller.process(:index)
+ assert_equal "Overwrite Hello again zero arity proc!", controller.response_body
+ end
+
test "when layout is specified as a symbol, call the requested method and use the layout returned" do
controller = WithSymbol.new
controller.process(:index)
@@ -238,12 +299,6 @@ module AbstractControllerTests
assert_equal "With Implied Hello string!", controller.response_body
end
- test "when a child controller specifies layout nil, do not use the parent layout" do
- controller = WithNilChild.new
- controller.process(:index)
- assert_equal "Hello string!", controller.response_body
- end
-
test "when a grandchild has no layout specified, the child has an implied layout, and the " \
"parent has specified a layout, use the child controller layout" do
controller = WithChildOfImplied.new
@@ -251,6 +306,13 @@ module AbstractControllerTests
assert_equal "With Implied Hello string!", controller.response_body
end
+ test "when a grandchild has nil layout specified, the child has an implied layout, and the " \
+ "parent has specified a layout, use the child controller layout" do
+ controller = WithGrandChildOfImplied.new
+ controller.process(:index)
+ assert_equal "With Grand Child Hello string!", controller.response_body
+ end
+
test "raises an exception when specifying layout true" do
assert_raises ArgumentError do
Object.class_eval do
@@ -260,6 +322,42 @@ module AbstractControllerTests
end
end
end
+
+ test "when specify an :only option which match current action name" do
+ controller = WithOnlyConditional.new
+ controller.process(:show)
+ assert_equal "Overwrite Hello show!", controller.response_body
+ end
+
+ test "when specify an :only option which does not match current action name" do
+ controller = WithOnlyConditional.new
+ controller.process(:index)
+ assert_equal "With Implied Hello index!", controller.response_body
+ end
+
+ test "when specify an :except option which match current action name" do
+ controller = WithExceptConditional.new
+ controller.process(:show)
+ assert_equal "With Implied Hello show!", controller.response_body
+ end
+
+ test "when specify an :except option which does not match current action name" do
+ controller = WithExceptConditional.new
+ controller.process(:index)
+ assert_equal "Overwrite Hello index!", controller.response_body
+ end
+
+ test "layout for anonymous controller" do
+ klass = Class.new(WithString) do
+ def index
+ render :text => 'index', :layout => true
+ end
+ end
+
+ controller = klass.new
+ controller.process(:index)
+ assert_equal "With String index", controller.response_body
+ end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb
index 8ec50fd57a..0194ee943f 100644
--- a/actionpack/test/abstract/translation_test.rb
+++ b/actionpack/test/abstract/translation_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
# class TranslatingController < ActionController::Base
# end
-class TranslationControllerTest < Test::Unit::TestCase
+class TranslationControllerTest < ActiveSupport::TestCase
def setup
@controller = ActionController::Base.new
end
@@ -23,4 +23,4 @@ class TranslationControllerTest < Test::Unit::TestCase
def test_action_controller_base_responds_to_l
assert_respond_to @controller, :l
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 24d071df39..ba06bcae51 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -1,11 +1,5 @@
require File.expand_path('../../../load_paths', __FILE__)
-lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
-$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
-
-activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-
$:.unshift(File.dirname(__FILE__) + '/lib')
$:.unshift(File.dirname(__FILE__) + '/fixtures/helpers')
$:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers')
@@ -14,17 +8,14 @@ ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp')
require 'active_support/core_ext/kernel/reporting'
-require 'active_support/core_ext/string/encoding'
-if "ruby".encoding_aware?
- # These are the normal settings that will be set up by Railties
- # TODO: Have these tests support other combinations of these values
- silence_warnings do
- Encoding.default_internal = "UTF-8"
- Encoding.default_external = "UTF-8"
- end
+# These are the normal settings that will be set up by Railties
+# TODO: Have these tests support other combinations of these values
+silence_warnings do
+ Encoding.default_internal = "UTF-8"
+ Encoding.default_external = "UTF-8"
end
-require 'test/unit'
+require 'minitest/autorun'
require 'abstract_controller'
require 'action_controller'
require 'action_view'
@@ -73,7 +64,17 @@ module RackTestUtils
end
module RenderERBUtils
+ def view
+ @view ||= begin
+ path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
+ view_paths = ActionView::PathSet.new([path])
+ ActionView::Base.new(view_paths)
+ end
+ end
+
def render_erb(string)
+ @virtual_path = nil
+
template = ActionView::Template.new(
string.strip,
"test template",
@@ -118,11 +119,11 @@ module ActiveSupport
# have been loaded.
setup_once do
SharedTestRoutes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
ActionDispatch::IntegrationTest.app.routes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
end
end
@@ -164,7 +165,8 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase
def self.build_app(routes = nil)
RoutedRackApp.new(routes || ActionDispatch::Routing::RouteSet.new) do |middleware|
- middleware.use "ActionDispatch::ShowExceptions"
+ middleware.use "ActionDispatch::ShowExceptions", ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
+ middleware.use "ActionDispatch::DebugExceptions"
middleware.use "ActionDispatch::Callbacks"
middleware.use "ActionDispatch::ParamsParser"
middleware.use "ActionDispatch::Cookies"
@@ -246,7 +248,7 @@ class Rack::TestCase < ActionDispatch::IntegrationTest
end
def assert_body(body)
- assert_equal body, Array.wrap(response.body).join
+ assert_equal body, Array(response.body).join
end
def assert_status(code)
@@ -254,7 +256,7 @@ class Rack::TestCase < ActionDispatch::IntegrationTest
end
def assert_response(body, status = 200, headers = {})
- assert_body body
+ assert_body body
assert_status status
headers.each do |header, value|
assert_header header, value
@@ -326,18 +328,33 @@ class Workshop
end
module ActionDispatch
- class ShowExceptions
+ class DebugExceptions
private
- remove_method :public_path
- def public_path
- "#{FIXTURE_LOAD_PATH}/public"
- end
+ remove_method :stderr_logger
+ # Silence logger
+ def stderr_logger
+ nil
+ end
+ end
+end
- remove_method :logger
- # Silence logger
- def logger
- nil
- end
+module ActionDispatch
+ module RoutingVerbs
+ def get(uri_or_host, path = nil, port = nil)
+ host = uri_or_host.host unless path
+ path ||= uri_or_host.path
+
+ params = {'PATH_INFO' => path,
+ 'REQUEST_METHOD' => 'GET',
+ 'HTTP_HOST' => host}
+
+ routes.call(params)[2].join
+ end
end
end
+module RoutingTestHelpers
+ def url_for(set, options, recall = nil)
+ set.send(:url_for, options.merge(:only_path => true, :_path_segments => recall))
+ end
+end
diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb
index 768ac713ca..275f25f597 100644
--- a/actionpack/test/activerecord/active_record_store_test.rb
+++ b/actionpack/test/activerecord/active_record_store_test.rb
@@ -33,8 +33,6 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
session[:foo] = "baz"
head :ok
end
-
- def rescue_action(e) raise end
end
def setup
@@ -225,16 +223,16 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
assert_equal session_id, cookies['_session_id']
end
end
-
+
def test_incoming_invalid_session_id_via_cookie_should_be_ignored
with_test_route_set do
open_session do |sess|
sess.cookies['_session_id'] = 'INVALID'
-
+
sess.get '/set_session_value'
new_session_id = sess.cookies['_session_id']
assert_not_equal 'INVALID', new_session_id
-
+
sess.get '/get_session_value'
new_session_id_2 = sess.cookies['_session_id']
assert_equal new_session_id, new_session_id_2
@@ -248,7 +246,7 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
sess.get '/set_session_value', :_session_id => 'INVALID'
new_session_id = sess.cookies['_session_id']
assert_not_equal 'INVALID', new_session_id
-
+
sess.get '/get_session_value'
new_session_id_2 = sess.cookies['_session_id']
assert_equal new_session_id, new_session_id_2
@@ -256,12 +254,19 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest
end
end
+ def test_session_store_with_all_domains
+ with_test_route_set(:domain => :all) do
+ get '/set_session_value'
+ assert_response :success
+ end
+ end
+
private
def with_test_route_set(options = {})
with_routing do |set|
set.draw do
- match ':action', :to => 'active_record_store_test/test'
+ get ':action', :to => 'active_record_store_test/test'
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb
index 20d11377f6..90e7f4ae59 100644
--- a/actionpack/test/activerecord/polymorphic_routes_test.rb
+++ b/actionpack/test/activerecord/polymorphic_routes_test.rb
@@ -2,42 +2,40 @@ require 'active_record_unit'
require 'fixtures/project'
class Task < ActiveRecord::Base
- set_table_name 'projects'
+ self.table_name = 'projects'
end
class Step < ActiveRecord::Base
- set_table_name 'projects'
+ self.table_name = 'projects'
end
class Bid < ActiveRecord::Base
- set_table_name 'projects'
+ self.table_name = 'projects'
end
class Tax < ActiveRecord::Base
- set_table_name 'projects'
+ self.table_name = 'projects'
end
class Fax < ActiveRecord::Base
- set_table_name 'projects'
+ self.table_name = 'projects'
end
class Series < ActiveRecord::Base
- set_table_name 'projects'
+ self.table_name = 'projects'
end
module Blog
class Post < ActiveRecord::Base
- set_table_name 'projects'
+ self.table_name = 'projects'
end
class Blog < ActiveRecord::Base
- set_table_name 'projects'
+ self.table_name = 'projects'
end
- def self._railtie
- o = Object.new
- def o.railtie_name; "blog" end
- o
+ def self.use_relative_model_naming?
+ true
end
end
diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
index 97be5a5bb0..409370104d 100644
--- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
+++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
@@ -16,7 +16,7 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
end
def render_with_has_many_through_association
- @developer = Developer.find(:first)
+ @developer = Developer.first
render :partial => @developer.topics
end
@@ -31,7 +31,7 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
end
def render_with_record
- @developer = Developer.find(:first)
+ @developer = Developer.first
render :partial => @developer
end
@@ -130,14 +130,40 @@ class RenderPartialWithRecordIdentificationAndNestedControllersTest < ActiveReco
def test_render_with_record_in_nested_controller
get :render_with_record_in_nested_controller
- assert_template 'fun/games/_game'
- assert_equal 'Pong', @response.body
+ assert_template %r{\Afun/games/_game\Z}
+ assert_equal "Fun Pong\n", @response.body
end
def test_render_with_record_collection_in_nested_controller
get :render_with_record_collection_in_nested_controller
- assert_template 'fun/games/_game'
- assert_equal 'PongTank', @response.body
+ assert_template %r{\Afun/games/_game\Z}
+ assert_equal "Fun Pong\nFun Tank\n", @response.body
+ end
+end
+
+class RenderPartialWithRecordIdentificationAndNestedControllersWithoutPrefixTest < ActiveRecordTestCase
+ tests Fun::NestedController
+
+ def test_render_with_record_in_nested_controller
+ old_config = ActionView::Base.prefix_partial_path_with_controller_namespace
+ ActionView::Base.prefix_partial_path_with_controller_namespace = false
+
+ get :render_with_record_in_nested_controller
+ assert_template %r{\Agames/_game\Z}
+ assert_equal "Just Pong\n", @response.body
+ ensure
+ ActionView::Base.prefix_partial_path_with_controller_namespace = old_config
+ end
+
+ def test_render_with_record_collection_in_nested_controller
+ old_config = ActionView::Base.prefix_partial_path_with_controller_namespace
+ ActionView::Base.prefix_partial_path_with_controller_namespace = false
+
+ get :render_with_record_collection_in_nested_controller
+ assert_template %r{\Agames/_game\Z}
+ assert_equal "Just Pong\nJust Tank\n", @response.body
+ ensure
+ ActionView::Base.prefix_partial_path_with_controller_namespace = old_config
end
end
@@ -146,13 +172,39 @@ class RenderPartialWithRecordIdentificationAndNestedDeeperControllersTest < Acti
def test_render_with_record_in_deeper_nested_controller
get :render_with_record_in_deeper_nested_controller
- assert_template 'fun/serious/games/_game'
- assert_equal 'Chess', @response.body
+ assert_template %r{\Afun/serious/games/_game\Z}
+ assert_equal "Serious Chess\n", @response.body
end
def test_render_with_record_collection_in_deeper_nested_controller
get :render_with_record_collection_in_deeper_nested_controller
- assert_template 'fun/serious/games/_game'
- assert_equal 'ChessSudokuSolitaire', @response.body
+ assert_template %r{\Afun/serious/games/_game\Z}
+ assert_equal "Serious Chess\nSerious Sudoku\nSerious Solitaire\n", @response.body
+ end
+end
+
+class RenderPartialWithRecordIdentificationAndNestedDeeperControllersWithoutPrefixTest < ActiveRecordTestCase
+ tests Fun::Serious::NestedDeeperController
+
+ def test_render_with_record_in_deeper_nested_controller
+ old_config = ActionView::Base.prefix_partial_path_with_controller_namespace
+ ActionView::Base.prefix_partial_path_with_controller_namespace = false
+
+ get :render_with_record_in_deeper_nested_controller
+ assert_template %r{\Agames/_game\Z}
+ assert_equal "Just Chess\n", @response.body
+ ensure
+ ActionView::Base.prefix_partial_path_with_controller_namespace = old_config
+ end
+
+ def test_render_with_record_collection_in_deeper_nested_controller
+ old_config = ActionView::Base.prefix_partial_path_with_controller_namespace
+ ActionView::Base.prefix_partial_path_with_controller_namespace = false
+
+ get :render_with_record_collection_in_deeper_nested_controller
+ assert_template %r{\Agames/_game\Z}
+ assert_equal "Just Chess\nJust Sudoku\nJust Solitaire\n", @response.body
+ ensure
+ ActionView::Base.prefix_partial_path_with_controller_namespace = old_config
end
end
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
new file mode 100644
index 0000000000..ca1d58765d
--- /dev/null
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -0,0 +1,55 @@
+require 'abstract_unit'
+require 'action_dispatch/testing/assertions/response'
+
+module ActionDispatch
+ module Assertions
+ class ResponseAssertionsTest < ActiveSupport::TestCase
+ include ResponseAssertions
+
+ FakeResponse = Struct.new(:response_code) do
+ [:success, :missing, :redirect, :error].each do |sym|
+ define_method("#{sym}?") do
+ sym == response_code
+ end
+ end
+ end
+
+ def test_assert_response_predicate_methods
+ [:success, :missing, :redirect, :error].each do |sym|
+ @response = FakeResponse.new sym
+ assert_response sym
+
+ assert_raises(MiniTest::Assertion) {
+ assert_response :unauthorized
+ }
+ end
+ end
+
+ def test_assert_response_fixnum
+ @response = FakeResponse.new 400
+ assert_response 400
+
+ assert_raises(MiniTest::Assertion) {
+ assert_response :unauthorized
+ }
+
+ assert_raises(MiniTest::Assertion) {
+ assert_response 500
+ }
+ end
+
+ def test_assert_response_sym_status
+ @response = FakeResponse.new 401
+ assert_response :unauthorized
+
+ assert_raises(MiniTest::Assertion) {
+ assert_response :ok
+ }
+
+ assert_raises(MiniTest::Assertion) {
+ assert_response :success
+ }
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index a714e8bbcc..9b0d4d0f4c 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -71,6 +71,16 @@ class ActionPackAssertionsController < ActionController::Base
render :text => "Hello!", :content_type => Mime::RSS
end
+ def render_with_layout
+ @variable_for_layout = nil
+ render "test/hello_world", :layout => "layouts/standard"
+ end
+
+ def render_with_layout_and_partial
+ @variable_for_layout = nil
+ render "test/hello_world_with_partial", :layout => "layouts/standard"
+ end
+
def session_stuffing
session['xmas'] = 'turkey'
render :text => "ho ho ho"
@@ -154,24 +164,10 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
assert_equal @response.body, 'request method: GET'
end
- def test_redirect_to_named_route
- with_routing do |set|
- set.draw do
- match 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one
- match ':controller/:action'
- end
- set.install_helpers
-
- process :redirect_to_named_route
- assert_redirected_to 'http://test.host/route_one'
- assert_redirected_to route_one_url
- end
- end
-
def test_string_constraint
with_routing do |set|
set.draw do
- match "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"}
+ get "photos", :to => 'action_pack_assertions#nothing', :constraints => {:subdomain => "admin"}
end
end
end
@@ -179,15 +175,18 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
def test_assert_redirect_to_named_route_failure
with_routing do |set|
set.draw do
- match 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one
- match 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two
- match ':controller/:action'
+ get 'route_one', :to => 'action_pack_assertions#nothing', :as => :route_one
+ get 'route_two', :to => 'action_pack_assertions#nothing', :id => 'two', :as => :route_two
+ get ':controller/:action'
end
process :redirect_to_named_route
assert_raise(ActiveSupport::TestCase::Assertion) do
assert_redirected_to 'http://test.host/route_two'
end
assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_redirected_to %r(^http://test.host/route_two)
+ end
+ assert_raise(ActiveSupport::TestCase::Assertion) do
assert_redirected_to :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two'
end
assert_raise(ActiveSupport::TestCase::Assertion) do
@@ -201,8 +200,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
with_routing do |set|
set.draw do
- match 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module
- match ':controller/:action'
+ get 'admin/inner_module', :to => 'admin/inner_module#index', :as => :admin_inner_module
+ get ':controller/:action'
end
process :redirect_to_index
# redirection is <{"action"=>"index", "controller"=>"admin/admin/inner_module"}>
@@ -215,12 +214,13 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
with_routing do |set|
set.draw do
- match '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level
- match ':controller/:action'
+ get '/action_pack_assertions/:id', :to => 'action_pack_assertions#index', :as => :top_level
+ get ':controller/:action'
end
process :redirect_to_top_level_named_route
# assert_redirected_to "http://test.host/action_pack_assertions/foo" would pass because of exact match early return
assert_redirected_to "/action_pack_assertions/foo"
+ assert_redirected_to %r(/action_pack_assertions/foo)
end
end
@@ -230,8 +230,8 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
with_routing do |set|
set.draw do
# this controller exists in the admin namespace as well which is the only difference from previous test
- match '/user/:id', :to => 'user#index', :as => :top_level
- match ':controller/:action'
+ get '/user/:id', :to => 'user#index', :as => :top_level
+ get ':controller/:action'
end
process :redirect_to_top_level_named_route
# assert_redirected_to top_level_url('foo') would pass because of exact match early return
@@ -347,7 +347,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
end
def test_render_based_on_parameters
- process :render_based_on_parameters, "name" => "David"
+ process :render_based_on_parameters, "GET", "name" => "David"
assert_equal "Mr. David", @response.body
end
@@ -471,6 +471,50 @@ class AssertTemplateTest < ActionController::TestCase
end
end
+ def test_fails_with_wrong_layout
+ get :render_with_layout
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :layout => "application"
+ end
+ end
+
+ def test_fails_expecting_no_layout
+ get :render_with_layout
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :layout => nil
+ end
+ end
+
+ def test_passes_with_correct_layout
+ get :render_with_layout
+ assert_template :layout => "layouts/standard"
+ end
+
+ def test_passes_with_layout_and_partial
+ get :render_with_layout_and_partial
+ assert_template :layout => "layouts/standard"
+ end
+
+ def test_passed_with_no_layout
+ get :hello_world
+ assert_template :layout => nil
+ end
+
+ def test_passed_with_no_layout_false
+ get :hello_world
+ assert_template :layout => false
+ end
+
+ def test_passes_with_correct_layout_without_layouts_prefix
+ get :render_with_layout
+ assert_template :layout => "standard"
+ end
+
+ def test_passes_with_correct_layout_symbol
+ get :render_with_layout
+ assert_template :layout => :standard
+ end
+
def test_assert_template_reset_between_requests
get :hello_world
assert_template 'test/hello_world'
diff --git a/actionpack/test/controller/addresses_render_test.rb b/actionpack/test/controller/addresses_render_test.rb
deleted file mode 100644
index c1cd22113d..0000000000
--- a/actionpack/test/controller/addresses_render_test.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'abstract_unit'
-require 'logger'
-require 'controller/fake_controllers'
-
-class Address
- def Address.count(conditions = nil, join = nil)
- nil
- end
-
- def Address.find_all(arg1, arg2, arg3, arg4)
- []
- end
-
- def self.find(*args)
- []
- end
-end
-
-class AddressesTest < ActionController::TestCase
- tests AddressesController
-
- def setup
- super
- # 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".
- @controller.logger = Logger.new(nil)
-
- @request.host = "www.nextangle.com"
- end
-
- def test_list
- get :list
- assert_equal "We only need to get this far!", @response.body.chomp
- end
-end
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index 5eef8a32d7..3d667f0a2f 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -47,10 +47,6 @@ class AssertSelectTest < ActionController::TestCase
render :text=>@content, :layout=>false, :content_type=>Mime::XML
@content = nil
end
-
- def rescue_action(e)
- raise e
- end
end
tests AssertSelectController
@@ -135,6 +131,13 @@ class AssertSelectTest < ActionController::TestCase
assert_raise(Assertion) { assert_select "pre", :html=>text }
end
+ def test_strip_textarea
+ render_html %Q{<textarea>\n\nfoo\n</textarea>}
+ assert_select "textarea", "\nfoo\n"
+ render_html %Q{<textarea>\nfoo</textarea>}
+ assert_select "textarea", "foo"
+ end
+
def test_counts
render_html %Q{<div id="1">foo</div><div id="2">foo</div>}
assert_nothing_raised { assert_select "div", 2 }
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 9c22a4e7e0..b9513ccff4 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require 'logger'
+require 'active_support/logger'
require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
# Provide some controller to run the tests on.
@@ -40,35 +40,12 @@ class NonEmptyController < ActionController::Base
end
end
-class MethodMissingController < ActionController::Base
- hide_action :shouldnt_be_called
- def shouldnt_be_called
- raise "NO WAY!"
- end
-
-protected
-
- def method_missing(selector)
- render :text => selector.to_s
- end
-end
-
-class AnotherMethodMissingController < ActionController::Base
- cattr_accessor :_exception
- rescue_from Exception, :with => :_exception=
-
- protected
- def method_missing(*attrs, &block)
- super
- end
-end
-
class DefaultUrlOptionsController < ActionController::Base
def from_view
render :inline => "<%= #{params[:route]} %>"
end
- def default_url_options(options = nil)
+ def default_url_options
{ :host => 'www.override.com', :action => 'new', :locale => 'en' }
end
end
@@ -79,7 +56,7 @@ class UrlOptionsController < ActionController::Base
end
def url_options
- super.merge(:host => 'www.override.com', :action => 'new', :locale => 'en')
+ super.merge(:host => 'www.override.com')
end
end
@@ -106,7 +83,7 @@ class ControllerClassTests < ActiveSupport::TestCase
end
end
-class ControllerInstanceTests < Test::Unit::TestCase
+class ControllerInstanceTests < ActiveSupport::TestCase
def setup
@empty = EmptyController.new
@contained = Submodule::ContainedEmptyController.new
@@ -116,6 +93,12 @@ class ControllerInstanceTests < Test::Unit::TestCase
Submodule::ContainedNonEmptyController.new]
end
+ def test_performed?
+ assert !@empty.performed?
+ @empty.response_body = ["sweet"]
+ assert @empty.performed?
+ end
+
def test_action_methods
@empty_controllers.each do |c|
assert_equal Set.new, c.class.action_methods, "#{c.controller_path} should be empty!"
@@ -142,13 +125,11 @@ class PerformActionTest < ActionController::TestCase
# 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".
- @controller.logger = Logger.new(nil)
+ @controller.logger = ActiveSupport::Logger.new(nil)
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@request.host = "www.nextangle.com"
-
- rescue_action_in_public!
end
def test_process_should_be_precise
@@ -159,32 +140,10 @@ class PerformActionTest < ActionController::TestCase
assert_equal exception.message, "The action 'non_existent' could not be found for EmptyController"
end
- def test_get_on_priv_should_show_selector
- use_controller MethodMissingController
- get :shouldnt_be_called
- assert_response :success
- assert_equal 'shouldnt_be_called', @response.body
- end
-
- def test_method_missing_is_not_an_action_name
- use_controller MethodMissingController
- assert !@controller.__send__(:action_method?, 'method_missing')
-
- get :method_missing
- assert_response :success
- assert_equal 'method_missing', @response.body
- end
-
- def test_method_missing_should_recieve_symbol
- use_controller AnotherMethodMissingController
- get :some_action
- assert_kind_of NameError, @controller._exception
- end
-
def test_get_on_hidden_should_fail
use_controller NonEmptyController
- assert_raise(ActionController::UnknownAction) { get :hidden_action }
- assert_raise(ActionController::UnknownAction) { get :another_hidden_action }
+ assert_raise(AbstractController::ActionNotFound) { get :hidden_action }
+ assert_raise(AbstractController::ActionNotFound) { get :another_hidden_action }
end
end
@@ -194,31 +153,45 @@ class UrlOptionsTest < ActionController::TestCase
def setup
super
@request.host = 'www.example.com'
- rescue_action_in_public!
+ end
+
+ def test_url_for_query_params_included
+ rs = ActionDispatch::Routing::RouteSet.new
+ rs.draw do
+ get 'home' => 'pages#home'
+ end
+
+ options = {
+ :action => "home",
+ :controller => "pages",
+ :only_path => true,
+ :params => { "token" => "secret" }
+ }
+
+ assert_equal '/home?token=secret', rs.url_for(options)
end
def test_url_options_override
with_routing do |set|
set.draw do
- match 'from_view', :to => 'url_options#from_view', :as => :from_view
- match ':controller/:action'
+ get 'from_view', :to => 'url_options#from_view', :as => :from_view
+ get ':controller/:action'
end
get :from_view, :route => "from_view_url"
- assert_equal 'http://www.override.com/from_view?locale=en', @response.body
- assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url)
- assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options')
+ assert_equal 'http://www.override.com/from_view', @response.body
+ assert_equal 'http://www.override.com/from_view', @controller.send(:from_view_url)
+ assert_equal 'http://www.override.com/default_url_options/index', @controller.url_for(:controller => 'default_url_options')
end
end
def test_url_helpers_does_not_become_actions
with_routing do |set|
set.draw do
- match "account/overview"
+ get "account/overview"
end
- @controller.class.send(:include, set.url_helpers)
assert !@controller.class.action_methods.include?("account_overview_path")
end
end
@@ -230,14 +203,13 @@ class DefaultUrlOptionsTest < ActionController::TestCase
def setup
super
@request.host = 'www.example.com'
- rescue_action_in_public!
end
def test_default_url_options_override
with_routing do |set|
set.draw do
- match 'from_view', :to => 'default_url_options#from_view', :as => :from_view
- match ':controller/:action'
+ get 'from_view', :to => 'default_url_options#from_view', :as => :from_view
+ get ':controller/:action'
end
get :from_view, :route => "from_view_url"
@@ -254,7 +226,7 @@ class DefaultUrlOptionsTest < ActionController::TestCase
scope("/:locale") do
resources :descriptions
end
- match ':controller/:action'
+ get ':controller/:action'
end
get :from_view, :route => "description_path(1)"
@@ -281,7 +253,6 @@ class EmptyUrlOptionsTest < ActionController::TestCase
def setup
super
@request.host = 'www.example.com'
- rescue_action_in_public!
end
def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 618e7b77b2..9efe328d62 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -14,10 +14,14 @@ class CachingController < ActionController::Base
end
class PageCachingTestController < CachingController
+ self.page_cache_compression = :best_compression
+
caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? }
caches_page :found, :not_found
caches_page :about_me
-
+ caches_page :default_gzip
+ caches_page :no_gzip, :gzip => false
+ caches_page :gzip_level, :gzip => :best_speed
def ok
head :ok
@@ -40,6 +44,18 @@ class PageCachingTestController < CachingController
cache_page("Super soaker", "/index.html")
end
+ def default_gzip
+ render :text => "Text"
+ end
+
+ def no_gzip
+ render :text => "PNG"
+ end
+
+ def gzip_level
+ render :text => "Big text"
+ end
+
def expire_custom_path
expire_page("/index.html")
head :ok
@@ -86,8 +102,8 @@ class PageCachingTest < ActionController::TestCase
def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
with_routing do |set|
set.draw do
- match 'posts.:format', :to => 'posts#index', :as => :formatted_posts
- match '/', :to => 'posts#index', :as => :main
+ get 'posts.:format', :to => 'posts#index', :as => :formatted_posts
+ get '/', :to => 'posts#index', :as => :main
end
@params[:format] = 'rss'
assert_equal '/posts.rss', @routes.url_for(@params)
@@ -115,6 +131,30 @@ class PageCachingTest < ActionController::TestCase
assert !File.exist?("#{FILE_STORE_PATH}/index.html")
end
+ def test_should_gzip_cache
+ get :custom_path
+ assert File.exist?("#{FILE_STORE_PATH}/index.html.gz")
+
+ get :expire_custom_path
+ assert !File.exist?("#{FILE_STORE_PATH}/index.html.gz")
+ end
+
+ def test_should_allow_to_disable_gzip
+ get :no_gzip
+ assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html")
+ assert !File.exist?("#{FILE_STORE_PATH}/page_caching_test/no_gzip.html.gz")
+ end
+
+ def test_should_use_config_gzip_by_default
+ @controller.expects(:cache_page).with(nil, nil, Zlib::BEST_COMPRESSION)
+ get :default_gzip
+ end
+
+ def test_should_set_gzip_level
+ @controller.expects(:cache_page).with(nil, nil, Zlib::BEST_SPEED)
+ get :gzip_level
+ end
+
def test_should_cache_without_trailing_slash_on_url
@controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash'
assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html")
@@ -140,7 +180,7 @@ class PageCachingTest < ActionController::TestCase
end
[:ok, :no_content, :found, :not_found].each do |status|
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
unless method == :get && status == :ok
define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
send(method, status)
@@ -194,8 +234,11 @@ class ActionCachingTestController < CachingController
caches_action :show, :cache_path => 'http://test.host/custom/show'
caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }
caches_action :with_layout
+ caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } }
caches_action :layout_false, :layout => false
+ caches_action :with_layout_proc_param, :layout => Proc.new { |c| c.params[:layout] }
caches_action :record_not_found, :four_oh_four, :simple_runtime_error
+ caches_action :streaming
layout 'talk_from_action'
@@ -219,6 +262,11 @@ class ActionCachingTestController < CachingController
render :text => @cache_this, :layout => true
end
+ def with_format_and_http_param
+ @cache_this = MockTime.now.to_f.to_s
+ render :text => @cache_this
+ end
+
def record_not_found
raise ActiveRecord::RecordNotFound, "oops!"
end
@@ -235,6 +283,7 @@ class ActionCachingTestController < CachingController
alias_method :edit, :index
alias_method :destroy, :index
alias_method :layout_false, :with_layout
+ alias_method :with_layout_proc_param, :with_layout
def expire
expire_action :controller => 'action_caching_test', :action => 'index'
@@ -245,6 +294,15 @@ class ActionCachingTestController < CachingController
expire_action :controller => 'action_caching_test', :action => 'index', :format => 'xml'
render :nothing => true
end
+
+ def expire_with_url_string
+ expire_action url_for(:controller => 'action_caching_test', :action => 'index')
+ render :nothing => true
+ end
+
+ def streaming
+ render :text => "streaming", :stream => true
+ end
end
class MockTime < Time
@@ -347,11 +405,40 @@ class ActionCacheTest < ActionController::TestCase
get :layout_false
assert_response :success
assert_not_equal cached_time, @response.body
-
body = body_to_string(read_fragment('hostname.com/action_caching_test/layout_false'))
assert_equal cached_time, body
end
+ def test_action_cache_with_layout_and_layout_cache_false_via_proc
+ get :with_layout_proc_param, :layout => false
+ assert_response :success
+ cached_time = content_to_cache
+ assert_not_equal cached_time, @response.body
+ assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
+ reset!
+
+ get :with_layout_proc_param, :layout => false
+ assert_response :success
+ assert_not_equal cached_time, @response.body
+ body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param'))
+ assert_equal cached_time, body
+ end
+
+ def test_action_cache_with_layout_and_layout_cache_true_via_proc
+ get :with_layout_proc_param, :layout => true
+ assert_response :success
+ cached_time = content_to_cache
+ assert_not_equal cached_time, @response.body
+ assert fragment_exist?('hostname.com/action_caching_test/with_layout_proc_param')
+ reset!
+
+ get :with_layout_proc_param, :layout => true
+ assert_response :success
+ assert_not_equal cached_time, @response.body
+ body = body_to_string(read_fragment('hostname.com/action_caching_test/with_layout_proc_param'))
+ assert_equal @response.body, body
+ end
+
def test_action_cache_conditional_options
@request.env['HTTP_ACCEPT'] = 'application/json'
get :index
@@ -359,6 +446,13 @@ class ActionCacheTest < ActionController::TestCase
assert !fragment_exist?('hostname.com/action_caching_test')
end
+ def test_action_cache_with_format_and_http_param
+ get :with_format_and_http_param, :format => 'json'
+ assert_response :success
+ assert !fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value.json')
+ assert fragment_exist?('hostname.com/action_caching_test/with_format_and_http_param.json?key=value')
+ end
+
def test_action_cache_with_store_options
MockTime.expects(:now).returns(12345).once
@controller.expects(:read_fragment).with('hostname.com/action_caching_test', :expires_in => 1.hour).once
@@ -432,6 +526,21 @@ class ActionCacheTest < ActionController::TestCase
assert_not_equal cached_time, @response.body
end
+ def test_cache_expiration_with_url_string
+ get :index
+ cached_time = content_to_cache
+ reset!
+
+ @request.request_uri = "/action_caching_test/expire_with_url_string"
+ get :expire_with_url_string
+ assert_response :success
+ reset!
+
+ get :index
+ assert_response :success
+ assert_not_equal cached_time, @response.body
+ end
+
def test_cache_is_scoped_by_subdomain
@request.host = 'jamis.hostname.com'
get :index
@@ -482,7 +591,7 @@ class ActionCacheTest < ActionController::TestCase
def test_xml_version_of_resource_is_treated_as_different_cache
with_routing do |set|
set.draw do
- match ':controller(/:action(.:format))'
+ get ':controller(/:action(.:format))'
end
get :index, :format => 'xml'
@@ -574,6 +683,13 @@ class ActionCacheTest < ActionController::TestCase
assert_response 500
end
+ def test_action_caching_plus_streaming
+ get :streaming
+ assert_response :success
+ assert_match(/streaming/, @response.body)
+ assert fragment_exist?('hostname.com/action_caching_test/streaming')
+ end
+
private
def content_to_cache
assigns(:cache_this)
@@ -613,8 +729,6 @@ class FragmentCachingTest < ActionController::TestCase
@controller.params = @params
@controller.request = @request
@controller.response = @response
- @controller.send(:initialize_template_class, @response)
- @controller.send(:assign_shortcuts, @request, @response)
end
def test_fragment_cache_key
@@ -722,10 +836,6 @@ class FunctionalCachingController < CachingController
format.xml
end
end
-
- def rescue_action(e)
- raise e
- end
end
class FunctionalFragmentCachingTest < ActionController::TestCase
diff --git a/actionpack/test/controller/capture_test.rb b/actionpack/test/controller/capture_test.rb
index d78acb8ce8..72263156d9 100644
--- a/actionpack/test/controller/capture_test.rb
+++ b/actionpack/test/controller/capture_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require 'logger'
+require 'active_support/logger'
class CaptureController < ActionController::Base
def self.controller_name; "test"; end
@@ -28,8 +28,6 @@ class CaptureController < ActionController::Base
def proper_block_detection
@todo = "some todo"
end
-
- def rescue_action(e) raise end
end
class CaptureTest < ActionController::TestCase
@@ -39,7 +37,7 @@ class CaptureTest < ActionController::TestCase
super
# 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".
- @controller.logger = Logger.new(nil)
+ @controller.logger = ActiveSupport::Logger.new(nil)
@request.host = "www.nextangle.com"
end
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index d51882066d..03d5d27cf4 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -48,8 +48,6 @@ class OldContentTypeController < ActionController::Base
format.rss { render :text => "hello world!", :content_type => Mime::XML }
end
end
-
- def rescue_action(e) raise end
end
class ContentTypeTest < ActionController::TestCase
@@ -59,7 +57,7 @@ class ContentTypeTest < ActionController::TestCase
super
# 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".
- @controller.logger = Logger.new(nil)
+ @controller.logger = ActiveSupport::Logger.new(nil)
end
# :ported:
@@ -70,12 +68,12 @@ class ContentTypeTest < ActionController::TestCase
end
def test_render_changed_charset_default
- OldContentTypeController.default_charset = "utf-16"
+ ActionDispatch::Response.default_charset = "utf-16"
get :render_defaults
assert_equal "utf-16", @response.charset
assert_equal Mime::HTML, @response.content_type
ensure
- OldContentTypeController.default_charset = "utf-8"
+ ActionDispatch::Response.default_charset = "utf-8"
end
# :ported:
@@ -107,12 +105,12 @@ class ContentTypeTest < ActionController::TestCase
end
def test_nil_default_for_erb
- OldContentTypeController.default_charset = nil
+ ActionDispatch::Response.default_charset = nil
get :render_default_for_erb
assert_equal Mime::HTML, @response.content_type
assert_nil @response.charset, @response.headers.inspect
ensure
- OldContentTypeController.default_charset = "utf-8"
+ ActionDispatch::Response.default_charset = "utf-8"
end
def test_default_for_erb
diff --git a/actionpack/test/controller/default_url_options_with_filter_test.rb b/actionpack/test/controller/default_url_options_with_filter_test.rb
index 3bbb981040..ef028e8cdb 100644
--- a/actionpack/test/controller/default_url_options_with_filter_test.rb
+++ b/actionpack/test/controller/default_url_options_with_filter_test.rb
@@ -21,7 +21,7 @@ end
class ControllerWithBeforeFilterAndDefaultUrlOptionsTest < ActionController::TestCase
- # This test has it´s roots in issue #1872
+ # This test has its roots in issue #1872
test "should redirect with correct locale :de" do
get :redirect, :locale => "de"
assert_redirected_to "/controller_with_before_filter_and_default_url_options/target?locale=de"
diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb
index d5e3da4d88..ef7fbca675 100644
--- a/actionpack/test/controller/filters_test.rb
+++ b/actionpack/test/controller/filters_test.rb
@@ -16,7 +16,7 @@ class ActionController::Base
def assigns(key = nil)
assigns = {}
- instance_variable_names.each do |ivar|
+ instance_variables.each do |ivar|
next if ActionController::Base.protected_instance_variables.include?(ivar)
assigns[ivar[1..-1]] = instance_variable_get(ivar)
end
@@ -165,8 +165,6 @@ class FilterTest < ActionController::TestCase
@ran_filter ||= []
@ran_filter << "clean_up_tmp"
end
-
- def rescue_action(e) raise(e) end
end
class ConditionalCollectionFilterController < ConditionalFilterController
@@ -454,11 +452,6 @@ class FilterTest < ActionController::TestCase
def show
raise ErrorToRescue.new("Something made the bad noise.")
end
-
- private
- def rescue_action(exception)
- raise exception
- end
end
class NonYieldingAroundFilterController < ActionController::Base
@@ -472,9 +465,6 @@ class FilterTest < ActionController::TestCase
render :inline => "index"
end
- #make sure the controller complains
- def rescue_action(e); raise e; end
-
private
def filter_one
@@ -520,6 +510,13 @@ class FilterTest < ActionController::TestCase
end
end
+ def test_sweeper_should_not_ignore_no_method_error
+ sweeper = ActionController::Caching::Sweeper.send(:new)
+ assert_raise NoMethodError do
+ sweeper.send_not_defined
+ end
+ end
+
def test_sweeper_should_not_block_rendering
response = test_process(SweeperTestController)
assert_equal 'hello world', response.body
@@ -825,11 +822,7 @@ class FilterTest < ActionController::TestCase
end
end
-
-
class PostsController < ActionController::Base
- def rescue_action(e); raise e; end
-
module AroundExceptions
class Error < StandardError ; end
class Before < Error ; end
@@ -951,9 +944,7 @@ class ControllerWithAllTypesOfFilters < PostsController
end
class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters
- $vbf = true
skip_filter :around_again
- $vbf = false
skip_filter :after
end
diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb
index 9b69a2648e..ccca0dac17 100644
--- a/actionpack/test/controller/flash_hash_test.rb
+++ b/actionpack/test/controller/flash_hash_test.rb
@@ -75,6 +75,7 @@ module ActionDispatch
def test_discard_no_args
@hash['hello'] = 'world'
@hash.discard
+
@hash.sweep
assert_equal({}, @hash.to_hash)
end
@@ -83,8 +84,88 @@ module ActionDispatch
@hash['hello'] = 'world'
@hash['omg'] = 'world'
@hash.discard 'hello'
+
@hash.sweep
assert_equal({'omg' => 'world'}, @hash.to_hash)
end
+
+ def test_keep_sweep
+ @hash['hello'] = 'world'
+
+ @hash.sweep
+ assert_equal({'hello' => 'world'}, @hash.to_hash)
+ end
+
+ def test_update_sweep
+ @hash['hello'] = 'world'
+ @hash.update({'hi' => 'mom'})
+
+ @hash.sweep
+ assert_equal({'hello' => 'world', 'hi' => 'mom'}, @hash.to_hash)
+ end
+
+ def test_update_delete_sweep
+ @hash['hello'] = 'world'
+ @hash.delete 'hello'
+ @hash.update({'hello' => 'mom'})
+
+ @hash.sweep
+ assert_equal({'hello' => 'mom'}, @hash.to_hash)
+ end
+
+ def test_delete_sweep
+ @hash['hello'] = 'world'
+ @hash['hi'] = 'mom'
+ @hash.delete 'hi'
+
+ @hash.sweep
+ assert_equal({'hello' => 'world'}, @hash.to_hash)
+ end
+
+ def test_clear_sweep
+ @hash['hello'] = 'world'
+ @hash.clear
+
+ @hash.sweep
+ assert_equal({}, @hash.to_hash)
+ end
+
+ def test_replace_sweep
+ @hash['hello'] = 'world'
+ @hash.replace({'hi' => 'mom'})
+
+ @hash.sweep
+ assert_equal({'hi' => 'mom'}, @hash.to_hash)
+ end
+
+ def test_discard_then_add
+ @hash['hello'] = 'world'
+ @hash['omg'] = 'world'
+ @hash.discard 'hello'
+ @hash['hello'] = 'world'
+
+ @hash.sweep
+ assert_equal({'omg' => 'world', 'hello' => 'world'}, @hash.to_hash)
+ end
+
+ def test_keep_all_sweep
+ @hash['hello'] = 'world'
+ @hash['omg'] = 'world'
+ @hash.discard 'hello'
+ @hash.keep
+
+ @hash.sweep
+ assert_equal({'omg' => 'world', 'hello' => 'world'}, @hash.to_hash)
+ end
+
+ def test_double_sweep
+ @hash['hello'] = 'world'
+ @hash.sweep
+
+ assert_equal({'hello' => 'world'}, @hash.to_hash)
+
+ @hash.sweep
+ assert_equal({}, @hash.to_hash)
+ end
end
end
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index e19612eace..e4b34125ad 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -51,10 +51,6 @@ class FlashTest < ActionController::TestCase
render :inline => "hello"
end
- def rescue_action(e)
- raise unless ActionView::MissingTemplate === e
- end
-
# methods for test_sweep_after_halted_filter_chain
before_filter :halt_and_redir, :only => "filter_halting_action"
@@ -254,16 +250,6 @@ 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_does_not_raise_in_following_requests
with_test_route_set do
env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new }
@@ -280,36 +266,6 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
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
@@ -321,7 +277,7 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- match ':action', :to => FlashIntegrationTest::TestController
+ get ':action', :to => FlashIntegrationTest::TestController
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
index 43b20fdead..5b423c8151 100644
--- a/actionpack/test/controller/force_ssl_test.rb
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -26,6 +26,29 @@ class ForceSSLExceptAction < ForceSSLController
force_ssl :except => :banana
end
+class ForceSSLIfCondition < ForceSSLController
+ force_ssl :if => :use_force_ssl?
+
+ def use_force_ssl?
+ action_name == 'cheeseburger'
+ end
+end
+
+class ForceSSLFlash < ForceSSLController
+ force_ssl :except => [:banana, :set_flash, :use_flash]
+
+ def set_flash
+ flash["that"] = "hello"
+ redirect_to '/force_ssl_flash/cheeseburger'
+ end
+
+ def use_flash
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ render :inline => "hello"
+ end
+end
+
class ForceSSLControllerLevelTest < ActionController::TestCase
tests ForceSSLControllerLevel
@@ -35,6 +58,12 @@ class ForceSSLControllerLevelTest < ActionController::TestCase
assert_equal "https://test.host/force_ssl_controller_level/banana", redirect_to_url
end
+ def test_banana_redirects_to_https_with_extra_params
+ get :banana, :token => "secret"
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_controller_level/banana?token=secret", redirect_to_url
+ end
+
def test_cheeseburger_redirects_to_https
get :cheeseburger
assert_response 301
@@ -50,7 +79,7 @@ class ForceSSLCustomDomainTest < ActionController::TestCase
assert_response 301
assert_equal "https://secure.test.host/force_ssl_custom_domain/banana", redirect_to_url
end
-
+
def test_cheeseburger_redirects_to_https_with_custom_host
get :cheeseburger
assert_response 301
@@ -88,16 +117,35 @@ class ForceSSLExceptActionTest < ActionController::TestCase
end
end
-class ForceSSLExcludeDevelopmentTest < ActionController::TestCase
- tests ForceSSLControllerLevel
-
- def setup
- Rails.env.stubs(:development?).returns(false)
- end
+class ForceSSLIfConditionTest < ActionController::TestCase
+ tests ForceSSLIfCondition
- def test_development_environment_not_redirects_to_https
- Rails.env.stubs(:development?).returns(true)
+ def test_banana_not_redirects_to_https
get :banana
assert_response 200
end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_if_condition/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLFlashTest < ActionController::TestCase
+ tests ForceSSLFlash
+
+ def test_cheeseburger_redirects_to_https
+ get :set_flash
+ assert_response 302
+ assert_equal "http://test.host/force_ssl_flash/cheeseburger", redirect_to_url
+
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_flash/cheeseburger", redirect_to_url
+
+ get :use_flash
+ assert_equal "hello", assigns["flash_copy"]["that"]
+ assert_equal "hello", assigns["flashy"]
+ end
end
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index 35a87c1aae..757661d8d0 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -7,16 +7,12 @@ module Fun
def render_hello_world
render :inline => "hello: <%= stratego %>"
end
-
- def rescue_action(e) raise end
end
class PdfController < ActionController::Base
def test
render :inline => "test: <%= foobar %>"
end
-
- def rescue_action(e) raise end
end
end
@@ -60,7 +56,6 @@ class HelperTest < ActiveSupport::TestCase
class TestController < ActionController::Base
attr_accessor :delegate_attr
def delegate_method() end
- def rescue_action(e) raise end
end
def setup
@@ -201,8 +196,6 @@ class IsolatedHelpersTest < ActiveSupport::TestCase
def index
render :inline => '<%= shout %>'
end
-
- def rescue_action(e) raise end
end
class B < A
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 364e96d4f6..7286b249c7 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -132,6 +132,6 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
private
def encode_credentials(username, password)
- "Basic #{ActiveSupport::Base64.encode64("#{username}:#{password}")}"
+ "Basic #{::Base64.encode64("#{username}:#{password}")}"
end
end
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index b011536717..828ea5b0fb 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -139,7 +139,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
test "authentication request with request-uri that doesn't match credentials digest-uri" do
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
- @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/altered/uri"
+ @request.env['ORIGINAL_FULLPATH'] = "/http_digest_authentication_test/dummy_digest/altered/uri"
get :display
assert_response :unauthorized
@@ -208,6 +208,44 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
assert !ActionController::HttpAuthentication::Digest.validate_digest_response(@request, "SuperSecret"){nil}
end
+ test "authentication request with request-uri ending in '/'" do
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/"
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+
+ # simulate normalizing PATH_INFO
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
+ get :display
+
+ assert_response :success
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with request-uri ending in '?'" do
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/?"
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+
+ # simulate normalizing PATH_INFO
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
+ get :display
+
+ assert_response :success
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with absolute uri in credentials (as in IE) ending with /" do
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest/"
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/",
+ :username => 'pretty', :password => 'please')
+
+ # simulate normalizing PATH_INFO
+ @request.env['PATH_INFO'] = "/http_digest_authentication_test/dummy_digest"
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
private
def encode_credentials(options)
@@ -228,11 +266,14 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
credentials = decode_credentials(@response.headers['WWW-Authenticate'])
credentials.merge!(options)
- credentials.merge!(:uri => @request.env['PATH_INFO'].to_s)
+ path_info = @request.env['PATH_INFO'].to_s
+ uri = options[:uri] || path_info
+ credentials.merge!(:uri => uri)
+ @request.env["ORIGINAL_FULLPATH"] = path_info
ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1])
end
def decode_credentials(header)
- ActionController::HttpAuthentication::Digest.decode_credentials(@response.headers['WWW-Authenticate'])
+ ActionController::HttpAuthentication::Digest.decode_credentials(header)
end
end
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index 2ad95f5c29..fb41dcb33a 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
require 'controller/fake_controllers'
require 'action_controller/vendor/html-scanner'
-class SessionTest < Test::Unit::TestCase
+class SessionTest < ActiveSupport::TestCase
StubApp = lambda { |env|
[200, {"Content-Type" => "text/html", "Content-Length" => "13"}, ["Hello, World!"]]
}
@@ -63,6 +63,12 @@ class SessionTest < Test::Unit::TestCase
@session.post_via_redirect(path, args, headers)
end
+ def test_patch_via_redirect
+ path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
+ @session.expects(:request_via_redirect).with(:patch, path, args, headers)
+ @session.patch_via_redirect(path, args, headers)
+ end
+
def test_put_via_redirect
path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" }
@session.expects(:request_via_redirect).with(:put, path, args, headers)
@@ -87,6 +93,12 @@ class SessionTest < Test::Unit::TestCase
@session.post(path,params,headers)
end
+ def test_patch
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:patch,path,params,headers)
+ @session.patch(path,params,headers)
+ end
+
def test_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
@session.expects(:process).with(:put,path,params,headers)
@@ -105,6 +117,12 @@ class SessionTest < Test::Unit::TestCase
@session.head(path,params,headers)
end
+ def test_options
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ @session.expects(:process).with(:options,path,params,headers)
+ @session.options(path,params,headers)
+ end
+
def test_xml_http_request_get
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@@ -125,6 +143,16 @@ class SessionTest < Test::Unit::TestCase
@session.xml_http_request(:post,path,params,headers)
end
+ def test_xml_http_request_patch
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:patch,path,params,headers_after_xhr)
+ @session.xml_http_request(:patch,path,params,headers)
+ end
+
def test_xml_http_request_put
path = "/index"; params = "blah"; headers = {:location => 'blah'}
headers_after_xhr = headers.merge(
@@ -155,6 +183,16 @@ class SessionTest < Test::Unit::TestCase
@session.xml_http_request(:head,path,params,headers)
end
+ def test_xml_http_request_options
+ path = "/index"; params = "blah"; headers = {:location => 'blah'}
+ headers_after_xhr = headers.merge(
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
+ "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*"
+ )
+ @session.expects(:process).with(:options,path,params,headers_after_xhr)
+ @session.xml_http_request(:options,path,params,headers)
+ end
+
def test_xml_http_request_override_accept
path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"}
headers_after_xhr = headers.merge(
@@ -165,7 +203,7 @@ class SessionTest < Test::Unit::TestCase
end
end
-class IntegrationTestTest < Test::Unit::TestCase
+class IntegrationTestTest < ActiveSupport::TestCase
def setup
@test = ::ActionDispatch::IntegrationTest.new(:app)
@test.class.stubs(:fixture_table_names).returns([])
@@ -212,7 +250,7 @@ class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest
@integration_session.stubs(:generic_url_rewriter)
@integration_session.stubs(:process)
- %w( get post head put delete ).each do |verb|
+ %w( get post head patch put delete options ).each do |verb|
assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') }
end
end
@@ -367,6 +405,15 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
end
+ def test_request_with_bad_format
+ with_test_route_set do
+ xhr :get, '/get.php'
+ assert_equal 406, status
+ assert_response 406
+ assert_response :not_acceptable
+ end
+ end
+
def test_get_with_query_string
with_test_route_set do
get '/get_with_params?foo=bar'
@@ -428,7 +475,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
end
set.draw do
- match ':action', :to => controller
+ match ':action', :to => controller, :via => [:get, :post]
get 'get/:action', :to => controller
end
@@ -492,10 +539,10 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
end
routes.draw do
- match '', :to => 'application_integration_test/test#index', :as => :empty_string
+ get '', :to => 'application_integration_test/test#index', :as => :empty_string
- match 'foo', :to => 'application_integration_test/test#index', :as => :foo
- match 'bar', :to => 'application_integration_test/test#index', :as => :bar
+ get 'foo', :to => 'application_integration_test/test#index', :as => :foo
+ get 'bar', :to => 'application_integration_test/test#index', :as => :bar
end
def app
@@ -535,3 +582,116 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
assert_equal old_env, env
end
end
+
+class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def post
+ render :text => "Created", :status => 201
+ end
+ end
+
+ def self.call(env)
+ env["action_dispatch.parameter_filter"] = [:password]
+ routes.call(env)
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ routes.draw do
+ match '/post', :to => 'environment_filter_integration_test/test#post', :via => :post
+ end
+
+ def app
+ self.class
+ end
+
+ test "filters rack request form vars" do
+ post "/post", :username => 'cjolly', :password => 'secret'
+
+ assert_equal 'cjolly', request.filtered_parameters['username']
+ assert_equal '[FILTERED]', request.filtered_parameters['password']
+ assert_equal '[FILTERED]', request.filtered_env['rack.request.form_vars']
+ end
+end
+
+class UrlOptionsIntegrationTest < ActionDispatch::IntegrationTest
+ class FooController < ActionController::Base
+ def index
+ render :text => "foo#index"
+ end
+
+ def show
+ render :text => "foo#show"
+ end
+
+ def edit
+ render :text => "foo#show"
+ end
+ end
+
+ class BarController < ActionController::Base
+ def default_url_options
+ { :host => "bar.com" }
+ end
+
+ def index
+ render :text => "foo#index"
+ end
+ end
+
+ def self.routes
+ @routes ||= ActionDispatch::Routing::RouteSet.new
+ end
+
+ def self.call(env)
+ routes.call(env)
+ end
+
+ def app
+ self.class
+ end
+
+ routes.draw do
+ default_url_options :host => "foo.com"
+
+ scope :module => "url_options_integration_test" do
+ get "/foo" => "foo#index", :as => :foos
+ get "/foo/:id" => "foo#show", :as => :foo
+ get "/foo/:id/edit" => "foo#edit", :as => :edit_foo
+ get "/bar" => "bar#index", :as => :bars
+ end
+ end
+
+ test "session uses default url options from routes" do
+ assert_equal "http://foo.com/foo", foos_url
+ end
+
+ test "current host overrides default url options from routes" do
+ get "/foo"
+ assert_response :success
+ assert_equal "http://www.example.com/foo", foos_url
+ end
+
+ test "controller can override default url options from request" do
+ get "/bar"
+ assert_response :success
+ assert_equal "http://bar.com/foo", foos_url
+ end
+
+ test "test can override default url options" do
+ default_url_options[:host] = "foobar.com"
+ assert_equal "http://foobar.com/foo", foos_url
+
+ get "/bar"
+ assert_response :success
+ assert_equal "http://foobar.com/foo", foos_url
+ end
+
+ test "current request path parameters are recalled" do
+ get "/foo/1"
+ assert_response :success
+ assert_equal "/foo/1/edit", url_for(:action => 'edit', :only_path => true)
+ end
+end
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index 25299eb8b8..c73b36f05e 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -78,6 +78,13 @@ end
class DefaultLayoutController < LayoutTest
end
+class StreamingLayoutController < LayoutTest
+ def render(*args)
+ options = args.extract_options! || {}
+ super(*args, options.merge(:stream => true))
+ end
+end
+
class AbsolutePathLayoutController < LayoutTest
layout File.expand_path(File.expand_path(__FILE__) + '/../../fixtures/layout_tests/layouts/layout_test')
end
@@ -122,6 +129,12 @@ class LayoutSetInResponseTest < ActionController::TestCase
assert_template :layout => "layouts/layout_test"
end
+ def test_layout_set_when_using_streaming_layout
+ @controller = StreamingLayoutController.new
+ get :hello
+ assert_template :hello
+ end
+
def test_layout_set_when_set_in_controller
@controller = HasOwnLayoutController.new
get :hello
@@ -167,7 +180,7 @@ class LayoutSetInResponseTest < ActionController::TestCase
def test_layout_is_picked_from_the_controller_instances_view_path
@controller = PrependsViewPathController.new
get :hello
- assert_template :layout => /layouts\/alt\.\w+/
+ assert_template :layout => /layouts\/alt/
end
def test_absolute_pathed_layout
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index ccdfcb0b2c..700fd788fa 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -13,6 +13,11 @@ module Another
head :status => 406
end
+ before_filter :redirector, :only => :never_executed
+
+ def never_executed
+ end
+
def show
render :nothing => true
end
@@ -49,7 +54,6 @@ module Another
def with_rescued_exception
raise SpecialException
end
-
end
end
@@ -86,6 +90,13 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_equal "Processing by Another::LogSubscribersController#show as HTML", logs.first
end
+ def test_halted_callback
+ get :never_executed
+ wait
+ assert_equal 4, logs.size
+ assert_equal "Filter chain halted as :redirector rendered or redirected", logs.third
+ end
+
def test_process_action
get :show
wait
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 2aa73f1650..bdcd5561a8 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -132,7 +132,6 @@ class RespondToController < ActionController::Base
end
end
-
def iphone_with_html_response_type
request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone"
@@ -151,10 +150,6 @@ class RespondToController < ActionController::Base
end
end
- def rescue_action(e)
- raise
- end
-
protected
def set_layout
if action_name.in?(["all_types_with_layout", "iphone_with_html_response_type"])
@@ -212,8 +207,9 @@ class RespondToControllerTest < ActionController::TestCase
get :html_or_xml
assert_equal 'HTML', @response.body
- get :just_xml
- assert_response 406
+ assert_raises(ActionController::UnknownFormat) do
+ get :just_xml
+ end
end
def test_all
@@ -244,8 +240,10 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal 'HTML', @response.body
@request.accept = "text/javascript, text/html"
- xhr :get, :just_xml
- assert_response 406
+
+ assert_raises(ActionController::UnknownFormat) do
+ xhr :get, :just_xml
+ end
end
def test_json_or_yaml_with_leading_star_star
@@ -498,11 +496,11 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
assert_equal "text/html", @response.content_type
end
-
+
def test_invalid_format
- get :using_defaults, :format => "invalidformat"
- assert_equal " ", @response.body
- assert_equal "text/html", @response.content_type
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_defaults, :format => "invalidformat"
+ end
end
end
@@ -597,6 +595,19 @@ class RenderJsonRespondWithController < RespondWithController
format.json { render :json => RenderJsonTestException.new('boom') }
end
end
+
+ def create
+ resource = ValidatedCustomer.new(params[:name], 1)
+ respond_with(resource) do |format|
+ format.json do
+ if resource.errors.empty?
+ render :json => { :valid => true }
+ else
+ render :json => { :valid => false }
+ end
+ end
+ end
+ end
end
class EmptyRespondWithController < ActionController::Base
@@ -656,7 +667,9 @@ class RespondWithControllerTest < ActionController::TestCase
@request.accept = "application/json"
get :using_hash_resource
assert_equal "application/json", @response.content_type
- assert_equal %Q[{"result":{"name":"david","id":13}}], @response.body
+ assert @response.body.include?("result")
+ assert @response.body.include?('"name":"david"')
+ assert @response.body.include?('"id":13')
end
def test_using_hash_resource_with_post
@@ -691,12 +704,14 @@ class RespondWithControllerTest < ActionController::TestCase
def test_not_acceptable
@request.accept = "application/xml"
- get :using_resource_with_block
- assert_equal 406, @response.status
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_resource_with_block
+ end
@request.accept = "text/javascript"
- get :using_resource_with_overwrite_block
- assert_equal 406, @response.status
+ assert_raises(ActionController::UnknownFormat) do
+ get :using_resource_with_overwrite_block
+ end
end
def test_using_resource_for_post_with_html_redirects_on_success
@@ -759,6 +774,41 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
+ def test_using_resource_for_patch_with_html_redirects_on_success
+ with_test_route_set do
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers/13", @response.location
+ assert @response.redirect?
+ end
+ end
+
+ def test_using_resource_for_patch_with_html_rerender_on_failure
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
+ def test_using_resource_for_patch_with_html_rerender_on_failure_even_on_method_override
+ with_test_route_set do
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ @request.env["rack.methodoverride.original_method"] = "POST"
+ patch :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+ end
+
def test_using_resource_for_put_with_html_redirects_on_success
with_test_route_set do
put :using_resource
@@ -794,21 +844,21 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
- def test_using_resource_for_put_with_xml_yields_ok_on_success
+ def test_using_resource_for_put_with_xml_yields_no_content_on_success
@request.accept = "application/xml"
put :using_resource
assert_equal "application/xml", @response.content_type
- assert_equal 200, @response.status
+ assert_equal 204, @response.status
assert_equal " ", @response.body
end
- def test_using_resource_for_put_with_json_yields_ok_on_success
+ def test_using_resource_for_put_with_json_yields_no_content_on_success
Customer.any_instance.stubs(:to_json).returns('{"name": "David"}')
@request.accept = "application/json"
put :using_resource
assert_equal "application/json", @response.content_type
- assert_equal 200, @response.status
- assert_equal "{}", @response.body
+ assert_equal 204, @response.status
+ assert_equal " ", @response.body
end
def test_using_resource_for_put_with_xml_yields_unprocessable_entity_on_failure
@@ -844,23 +894,23 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
- def test_using_resource_for_delete_with_xml_yields_ok_on_success
+ def test_using_resource_for_delete_with_xml_yields_no_content_on_success
Customer.any_instance.stubs(:destroyed?).returns(true)
@request.accept = "application/xml"
delete :using_resource
assert_equal "application/xml", @response.content_type
- assert_equal 200, @response.status
+ assert_equal 204, @response.status
assert_equal " ", @response.body
end
- def test_using_resource_for_delete_with_json_yields_ok_on_success
+ def test_using_resource_for_delete_with_json_yields_no_content_on_success
Customer.any_instance.stubs(:to_json).returns('{"name": "David"}')
Customer.any_instance.stubs(:destroyed?).returns(true)
@request.accept = "application/json"
delete :using_resource
assert_equal "application/json", @response.content_type
- assert_equal 200, @response.status
- assert_equal "{}", @response.body
+ assert_equal 204, @response.status
+ assert_equal " ", @response.body
end
def test_using_resource_for_delete_with_html_redirects_on_failure
@@ -939,8 +989,9 @@ class RespondWithControllerTest < ActionController::TestCase
def test_clear_respond_to
@controller = InheritedRespondWithController.new
@request.accept = "text/html"
- get :index
- assert_equal 406, @response.status
+ assert_raises(ActionController::UnknownFormat) do
+ get :index
+ end
end
def test_first_in_respond_to_has_higher_priority
@@ -966,6 +1017,18 @@ class RespondWithControllerTest < ActionController::TestCase
assert_match(/"error":"RenderJsonTestException"/, @response.body)
end
+ def test_api_response_with_valid_resource_respect_override_block
+ @controller = RenderJsonRespondWithController.new
+ post :create, :name => "sikachu", :format => :json
+ assert_equal '{"valid":true}', @response.body
+ end
+
+ def test_api_response_with_invalid_resource_respect_override_block
+ @controller = RenderJsonRespondWithController.new
+ post :create, :name => "david", :format => :json
+ assert_equal '{"valid":false}', @response.body
+ end
+
def test_no_double_render_is_raised
@request.accept = "text/html"
assert_raise ActionView::MissingTemplate do
@@ -1061,7 +1124,7 @@ class RespondWithControllerTest < ActionController::TestCase
resources :quiz_stores do
resources :customers
end
- match ":controller/:action"
+ get ":controller/:action"
end
yield
end
@@ -1077,7 +1140,7 @@ class PostController < AbstractPostController
around_filter :with_iphone
def index
- respond_to(:html, :iphone)
+ respond_to(:html, :iphone, :js)
end
protected
@@ -1124,4 +1187,45 @@ class MimeControllerLayoutsTest < ActionController::TestCase
get :index
assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body
end
+
+ def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout
+ get :index, :format => :js
+ assert_equal "Hello Firefox", @response.body
+ end
+end
+
+class FlashResponder < ActionController::Responder
+ def initialize(controller, resources, options={})
+ super
+ end
+
+ def to_html
+ controller.flash[:notice] = 'Success'
+ super
+ end
+end
+
+class FlashResponderController < ActionController::Base
+ self.responder = FlashResponder
+ respond_to :html
+
+ def index
+ respond_with Object.new do |format|
+ format.html { render :text => 'HTML' }
+ end
+ end
+end
+
+class FlashResponderControllerTest < ActionController::TestCase
+ tests FlashResponderController
+
+ def test_respond_with_block_executed
+ get :index
+ assert_equal 'HTML', @response.body
+ end
+
+ def test_flash_responder_executed
+ get :index
+ assert_equal 'Success', flash[:notice]
+ end
end
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index 3ca29f1bcf..5bcd79ebec 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -23,6 +23,12 @@ module BareMetalTest
assert_equal "Hello world", string
end
+
+ test "response_body value is wrapped in an array when the value is a String" do
+ controller = BareController.new
+ controller.index
+ assert_equal ["Hello world"], controller.response_body
+ end
end
class HeadController < ActionController::Metal
@@ -31,6 +37,36 @@ module BareMetalTest
def index
head :not_found
end
+
+ def continue
+ self.content_type = "text/html"
+ head 100
+ end
+
+ def switching_protocols
+ self.content_type = "text/html"
+ head 101
+ end
+
+ def processing
+ self.content_type = "text/html"
+ head 102
+ end
+
+ def no_content
+ self.content_type = "text/html"
+ head 204
+ end
+
+ def reset_content
+ self.content_type = "text/html"
+ head 205
+ end
+
+ def not_modified
+ self.content_type = "text/html"
+ head 304
+ end
end
class HeadTest < ActiveSupport::TestCase
@@ -38,6 +74,42 @@ module BareMetalTest
status = HeadController.action(:index).call(Rack::MockRequest.env_for("/")).first
assert_equal 404, status
end
+
+ test "head :continue (100) does not return a content-type header" do
+ headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :continue (101) does not return a content-type header" do
+ headers = HeadController.action(:continue).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :processing (102) does not return a content-type header" do
+ headers = HeadController.action(:processing).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :no_content (204) does not return a content-type header" do
+ headers = HeadController.action(:no_content).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :reset_content (205) does not return a content-type header" do
+ headers = HeadController.action(:reset_content).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
+
+ test "head :not_modified (304) does not return a content-type header" do
+ headers = HeadController.action(:not_modified).call(Rack::MockRequest.env_for("/")).second
+ assert_nil headers['Content-Type']
+ assert_nil headers['Content-Length']
+ end
end
class BareControllerTest < ActionController::TestCase
diff --git a/actionpack/test/controller/new_base/content_type_test.rb b/actionpack/test/controller/new_base/content_type_test.rb
index 4b70031c90..9b57641e75 100644
--- a/actionpack/test/controller/new_base/content_type_test.rb
+++ b/actionpack/test/controller/new_base/content_type_test.rb
@@ -43,7 +43,7 @@ module ContentType
test "default response is HTML and UTF8" do
with_routing do |set|
set.draw do
- match ':controller', :action => 'index'
+ get ':controller', :action => 'index'
end
get "/content_type/base"
diff --git a/actionpack/test/controller/new_base/render_layout_test.rb b/actionpack/test/controller/new_base/render_layout_test.rb
index d3dcb5cad6..4ac40ca405 100644
--- a/actionpack/test/controller/new_base/render_layout_test.rb
+++ b/actionpack/test/controller/new_base/render_layout_test.rb
@@ -71,7 +71,8 @@ module ControllerLayouts
self.view_paths = [ActionView::FixtureResolver.new(
"layouts/application.html.erb" => "<html><%= yield %></html>",
"controller_layouts/mismatch_format/index.xml.builder" => "xml.instruct!",
- "controller_layouts/mismatch_format/implicit.builder" => "xml.instruct!"
+ "controller_layouts/mismatch_format/implicit.builder" => "xml.instruct!",
+ "controller_layouts/mismatch_format/explicit.js.erb" => "alert('foo');"
)]
def explicit
@@ -81,7 +82,7 @@ module ControllerLayouts
class MismatchFormatTest < Rack::TestCase
testing ControllerLayouts::MismatchFormatController
-
+
XML_INSTRUCT = %Q(<?xml version="1.0" encoding="UTF-8"?>\n)
test "if XML is selected, an HTML template is not also selected" do
@@ -94,10 +95,33 @@ module ControllerLayouts
assert_response XML_INSTRUCT
end
- test "if an HTML template is explicitly provides for a JS template, an error is raised" do
- assert_raises ActionView::MissingTemplate do
- get :explicit, {}, "action_dispatch.show_exceptions" => false
- end
+ test "a layout for JS is ignored even if explicitly provided for HTML" do
+ get :explicit, { :format => "js" }
+ assert_response "alert('foo');"
+ end
+ end
+
+ class FalseLayoutMethodController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "controller_layouts/false_layout_method/index.js.erb" => "alert('foo');"
+ )]
+
+ layout :which_layout?
+
+ def which_layout?
+ false
+ end
+
+ def index
+ end
+ end
+
+ class FalseLayoutMethodTest < Rack::TestCase
+ testing ControllerLayouts::FalseLayoutMethodController
+
+ test "access false layout returned by a method/proc" do
+ get :index, :format => "js"
+ assert_response "alert('foo');"
end
end
end
diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb
index 1a17e24914..bfca8c5c24 100644
--- a/actionpack/test/controller/new_base/render_streaming_test.rb
+++ b/actionpack/test/controller/new_base/render_streaming_test.rb
@@ -73,19 +73,19 @@ module RenderStreaming
test "rendering with layout exception" do
get "/render_streaming/basic/layout_exception"
- assert_body "d\r\n<body class=\"\r\n4e\r\n\"><script type=\"text/javascript\">window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n"
+ assert_body "d\r\n<body class=\"\r\n37\r\n\"><script>window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n"
assert_streaming!
end
test "rendering with template exception" do
get "/render_streaming/basic/template_exception"
- assert_body "4e\r\n\"><script type=\"text/javascript\">window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n"
+ assert_body "37\r\n\"><script>window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n"
assert_streaming!
end
test "rendering with template exception logs the exception" do
io = StringIO.new
- _old, ActionController::Base.logger = ActionController::Base.logger, Logger.new(io)
+ _old, ActionController::Base.logger = ActionController::Base.logger, ActiveSupport::Logger.new(io)
begin
get "/render_streaming/basic/template_exception"
@@ -111,4 +111,4 @@ module RenderStreaming
assert_equal cache, headers["Cache-Control"]
end
end
-end if defined?(Fiber)
+end
diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb
index ba804421da..00c7df2af8 100644
--- a/actionpack/test/controller/new_base/render_template_test.rb
+++ b/actionpack/test/controller/new_base/render_template_test.rb
@@ -59,6 +59,12 @@ module RenderTemplate
def with_error
render :template => "test/with_error"
end
+
+ private
+
+ def show_detailed_exceptions?
+ request.local?
+ end
end
class TestWithoutLayout < Rack::TestCase
@@ -158,7 +164,7 @@ module RenderTemplate
test "rendering with implicit layout" do
with_routing do |set|
- set.draw { match ':controller', :action => :index }
+ set.draw { get ':controller', :action => :index }
get "/render_template/with_layout"
diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb
index 60468bf5c7..cc7f12ac6d 100644
--- a/actionpack/test/controller/new_base/render_test.rb
+++ b/actionpack/test/controller/new_base/render_test.rb
@@ -57,7 +57,7 @@ module Render
test "render with blank" do
with_routing do |set|
set.draw do
- match ":controller", :action => 'index'
+ get ":controller", :action => 'index'
end
get "/render/blank_render"
@@ -70,7 +70,7 @@ module Render
test "rendering more than once raises an exception" do
with_routing do |set|
set.draw do
- match ":controller", :action => 'index'
+ get ":controller", :action => 'index'
end
assert_raises(AbstractController::DoubleRenderError) do
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index 06d500cca7..f8d02e8b6c 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -65,9 +65,9 @@ module RenderText
class RenderTextTest < Rack::TestCase
describe "Rendering text using render :text"
- test "rendering text from a action with default options renders the text with the layout" do
+ test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { match ':controller', :action => 'index' }
+ set.draw { get ':controller', :action => 'index' }
get "/render_text/simple"
assert_body "hello david"
@@ -75,9 +75,9 @@ module RenderText
end
end
- test "rendering text from a action with default options renders the text without the layout" do
+ test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { match ':controller', :action => 'index' }
+ set.draw { get ':controller', :action => 'index' }
get "/render_text/with_layout"
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 7bef1e8d5d..fa1608b9df 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -147,6 +147,7 @@ class ParamsWrapperTest < ActionController::TestCase
end
def test_derived_wrapped_keys_from_matching_model
+ User.expects(:respond_to?).with(:accessible_attributes).returns(false)
User.expects(:respond_to?).with(:attribute_names).returns(true)
User.expects(:attribute_names).twice.returns(["username"])
@@ -159,6 +160,7 @@ class ParamsWrapperTest < ActionController::TestCase
def test_derived_wrapped_keys_from_specified_model
with_default_wrapper_options do
+ Person.expects(:respond_to?).with(:accessible_attributes).returns(false)
Person.expects(:respond_to?).with(:attribute_names).returns(true)
Person.expects(:attribute_names).twice.returns(["username"])
@@ -169,8 +171,46 @@ class ParamsWrapperTest < ActionController::TestCase
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
end
end
+
+ def test_accessible_wrapped_keys_from_matching_model
+ User.expects(:respond_to?).with(:accessible_attributes).returns(true)
+ User.expects(:accessible_attributes).with(:default).twice.returns(["username"])
+
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_accessible_wrapped_keys_from_specified_model
+ with_default_wrapper_options do
+ Person.expects(:respond_to?).with(:accessible_attributes).returns(true)
+ Person.expects(:accessible_attributes).with(:default).twice.returns(["username"])
+
+ UsersController.wrap_parameters Person
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
+
+ def test_accessible_wrapped_keys_with_role_from_specified_model
+ with_default_wrapper_options do
+ Person.expects(:respond_to?).with(:accessible_attributes).returns(true)
+ Person.expects(:accessible_attributes).with(:admin).twice.returns(["username"])
+
+ UsersController.wrap_parameters Person, :as => :admin
+
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
+ assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
+ end
+ end
def test_not_wrapping_abstract_model
+ User.expects(:respond_to?).with(:accessible_attributes).returns(false)
User.expects(:respond_to?).with(:attribute_names).returns(true)
User.expects(:attribute_names).returns([])
@@ -285,3 +325,38 @@ class AnonymousControllerParamsWrapperTest < ActionController::TestCase
end
end
end
+
+class IrregularInflectionParamsWrapperTest < ActionController::TestCase
+ include ParamsWrapperTestHelp
+
+ class ParamswrappernewsItem
+ def self.attribute_names
+ ['test_attr']
+ end
+ end
+
+ class ParamswrappernewsController < ActionController::Base
+ class << self
+ attr_accessor :last_parameters
+ end
+
+ def parse
+ self.class.last_parameters = request.params.except(:controller, :action)
+ head :ok
+ end
+ end
+
+ tests ParamswrappernewsController
+
+ def test_uses_model_attribute_names_with_irregular_inflection
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular 'paramswrappernews_item', 'paramswrappernews'
+ end
+
+ with_default_wrapper_options do
+ @request.env['CONTENT_TYPE'] = 'application/json'
+ post :parse, { 'username' => 'sikachu', 'test_attr' => 'test_value' }
+ assert_parameters({ 'username' => 'sikachu', 'test_attr' => 'test_value', 'paramswrappernews_item' => { 'test_attr' => 'test_value' }})
+ end
+ end
+end
diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb
index f3e5ff8a47..eb38b81e85 100644
--- a/actionpack/test/controller/record_identifier_test.rb
+++ b/actionpack/test/controller/record_identifier_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'controller/fake_models'
-class RecordIdentifierTest < Test::Unit::TestCase
+class RecordIdentifierTest < ActiveSupport::TestCase
include ActionController::RecordIdentifier
def setup
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 79041055bd..4331333b98 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -70,6 +70,10 @@ class RedirectController < ActionController::Base
redirect_to "x-test+scheme.complex:redirect"
end
+ def redirect_to_url_with_network_path_reference
+ redirect_to "//www.rubyonrails.org/"
+ end
+
def redirect_to_back
redirect_to :back
end
@@ -99,9 +103,15 @@ class RedirectController < ActionController::Base
redirect_to proc { {:action => "hello_world"} }
end
- def rescue_errors(e) raise e end
+ def redirect_with_header_break
+ redirect_to "/lol\r\nwat"
+ end
- def rescue_action(e) raise end
+ def redirect_with_null_bytes
+ redirect_to "\000/lol\r\nwat"
+ end
+
+ def rescue_errors(e) raise e end
protected
def dashbord_url(id, message)
@@ -118,6 +128,18 @@ class RedirectTest < ActionController::TestCase
assert_equal "http://test.host/redirect/hello_world", redirect_to_url
end
+ def test_redirect_with_header_break
+ get :redirect_with_header_break
+ assert_response :redirect
+ assert_equal "http://test.host/lolwat", redirect_to_url
+ end
+
+ def test_redirect_with_null_bytes
+ get :redirect_with_null_bytes
+ assert_response :redirect
+ assert_equal "http://test.host/lolwat", redirect_to_url
+ end
+
def test_redirect_with_no_status
get :simple_redirect
assert_response 302
@@ -216,6 +238,12 @@ class RedirectTest < ActionController::TestCase
assert_equal "x-test+scheme.complex:redirect", redirect_to_url
end
+ def test_redirect_to_url_with_network_path_reference
+ get :redirect_to_url_with_network_path_reference
+ assert_response :redirect
+ assert_equal "//www.rubyonrails.org/", redirect_to_url
+ end
+
def test_redirect_to_back
@request.env["HTTP_REFERER"] = "http://www.example.com/coming/from"
get :redirect_to_back
@@ -234,7 +262,7 @@ class RedirectTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :workshops
- match ':controller/:action'
+ get ':controller/:action'
end
get :redirect_to_existing_record
@@ -268,7 +296,7 @@ class RedirectTest < ActionController::TestCase
def test_redirect_to_with_block_and_accepted_options
with_routing do |set|
set.draw do
- match ':controller/:action'
+ get ':controller/:action'
end
get :redirect_to_with_block_and_options
diff --git a/actionpack/test/controller/render_json_test.rb b/actionpack/test/controller/render_json_test.rb
index fc604a2db3..7c0a6bd67e 100644
--- a/actionpack/test/controller/render_json_test.rb
+++ b/actionpack/test/controller/render_json_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'controller/fake_models'
+require 'active_support/logger'
require 'pathname'
class RenderJsonTest < ActionController::TestCase
@@ -69,7 +70,7 @@ class RenderJsonTest < ActionController::TestCase
# 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)
+ @controller.logger = ActiveSupport::Logger.new(nil)
@request.host = "www.nextangle.com"
end
@@ -101,7 +102,7 @@ class RenderJsonTest < ActionController::TestCase
def test_render_json_with_callback
get :render_json_hello_world_with_callback
assert_equal 'alert({"hello":"world"})', @response.body
- assert_equal 'application/json', @response.content_type
+ assert_equal 'text/javascript', @response.content_type
end
def test_render_json_with_custom_content_type
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index aea603b014..3d58c02338 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -9,7 +9,7 @@ module Fun
end
def nested_partial_with_form_builder
- render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}, Proc.new {})
+ render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {})
end
end
end
@@ -25,6 +25,8 @@ end
class TestController < ActionController::Base
protect_from_forgery
+ before_filter :set_variable_for_layout
+
class LabellingFormBuilder < ActionView::Helpers::FormBuilder
end
@@ -41,7 +43,7 @@ class TestController < ActionController::Base
end
def hello_world_file
- render :file => File.expand_path("../../fixtures/hello.html", __FILE__)
+ render :file => File.expand_path("../../fixtures/hello", __FILE__), :formats => [:html]
end
def conditional_hello
@@ -50,12 +52,28 @@ class TestController < ActionController::Base
end
end
+ def conditional_hello_with_record
+ record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123")
+
+ if stale?(record)
+ render :action => 'hello_world'
+ end
+ end
+
def conditional_hello_with_public_header
if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true)
render :action => 'hello_world'
end
end
+ def conditional_hello_with_public_header_with_record
+ record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123")
+
+ if stale?(record, :public => true)
+ render :action => 'hello_world'
+ end
+ end
+
def conditional_hello_with_public_header_and_expires_at
expires_in 1.minute
if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true)
@@ -73,13 +91,23 @@ class TestController < ActionController::Base
render :action => 'hello_world'
end
+ def conditional_hello_with_expires_in_with_must_revalidate
+ expires_in 1.minute, :must_revalidate => true
+ render :action => 'hello_world'
+ end
+
+ def conditional_hello_with_expires_in_with_public_and_must_revalidate
+ expires_in 1.minute, :public => true, :must_revalidate => true
+ render :action => 'hello_world'
+ end
+
def conditional_hello_with_expires_in_with_public_with_more_keys
- expires_in 1.minute, :public => true, 'max-stale' => 5.hours
+ expires_in 1.minute, :public => true, 's-maxage' => 5.hours
render :action => 'hello_world'
end
def conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
- expires_in 1.minute, :public => true, :private => nil, 'max-stale' => 5.hours
+ expires_in 1.minute, :public => true, :private => nil, 's-maxage' => 5.hours
render :action => 'hello_world'
end
@@ -348,17 +376,14 @@ class TestController < ActionController::Base
end
def layout_test_with_different_layout
- @variable_for_layout = nil
render :action => "hello_world", :layout => "standard"
end
def layout_test_with_different_layout_and_string_action
- @variable_for_layout = nil
render "hello_world", :layout => "standard"
end
def layout_test_with_different_layout_and_symbol_action
- @variable_for_layout = nil
render :hello_world, :layout => "standard"
end
@@ -367,7 +392,6 @@ class TestController < ActionController::Base
end
def layout_overriding_layout
- @variable_for_layout = nil
render :action => "hello_world", :layout => "standard"
end
@@ -481,6 +505,14 @@ class TestController < ActionController::Base
render :text => "hello world!"
end
+ def head_created
+ head :created
+ end
+
+ def head_created_with_application_json_content_type
+ head :created, :content_type => "application/json"
+ end
+
def head_with_location_header
head :location => "/foo"
end
@@ -529,12 +561,33 @@ class TestController < ActionController::Base
render :partial => 'partial'
end
+ def partial_html_erb
+ render :partial => 'partial_html_erb'
+ 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") }
render :template => "test/hello_world"
end
+ def render_to_string_with_template_and_html_partial
+ @text = render_to_string :template => "test/with_partial", :formats => [:text]
+ @html = render_to_string :template => "test/with_partial", :formats => [:html]
+ render :template => "test/with_html_partial"
+ end
+
+ def render_to_string_and_render_with_different_formats
+ @html = render_to_string :template => "test/with_partial", :formats => [:html]
+ render :template => "test/with_partial", :formats => [:text]
+ end
+
+ def render_template_within_a_template_with_other_format
+ render :template => "test/with_xml_template",
+ :formats => [:html],
+ :layout => "with_html_partial"
+ end
+
def partial_with_counter
render :partial => "counter", :locals => { :counter_counter => 5 }
end
@@ -544,11 +597,11 @@ class TestController < ActionController::Base
end
def partial_with_form_builder
- render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}, Proc.new {})
+ render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {})
end
def partial_with_form_builder_subclass
- render :partial => LabellingFormBuilder.new(:post, nil, view_context, {}, Proc.new {})
+ render :partial => LabellingFormBuilder.new(:post, nil, view_context, {})
end
def partial_collection
@@ -635,10 +688,6 @@ class TestController < ActionController::Base
render :action => "calling_partial_with_layout", :layout => "layouts/partial_with_layout"
end
- def rescue_action(e)
- raise
- end
-
before_filter :only => :render_with_filters do
request.format = :xml
end
@@ -650,8 +699,11 @@ class TestController < ActionController::Base
private
+ def set_variable_for_layout
+ @variable_for_layout = nil
+ end
+
def determine_layout
- @variable_for_layout ||= nil
case action_name
when "hello_world", "layout_test", "rendering_without_layout",
"rendering_nothing_on_layout", "render_text_hello_world",
@@ -679,7 +731,8 @@ class RenderTest < ActionController::TestCase
# 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)
+ @controller.logger = ActiveSupport::Logger.new(nil)
+ ActionView::Base.logger = ActiveSupport::Logger.new(nil)
@request.host = "www.nextangle.com"
end
@@ -793,9 +846,7 @@ class RenderTest < ActionController::TestCase
end
def test_render_file
- assert_deprecated do
- get :hello_world_file
- end
+ get :hello_world_file
assert_equal "Hello world!", @response.body
end
@@ -876,12 +927,12 @@ class RenderTest < ActionController::TestCase
# :ported:
def test_attempt_to_access_object_method
- assert_raise(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
+ assert_raise(AbstractController::ActionNotFound, "No action responded to [clone]") { get :clone }
end
# :ported:
def test_private_methods
- assert_raise(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout }
+ assert_raise(AbstractController::ActionNotFound, "No action responded to [determine_layout]") { get :determine_layout }
end
# :ported:
@@ -892,7 +943,7 @@ class RenderTest < ActionController::TestCase
def test_access_to_logger_in_view
get :accessing_logger_in_template
- assert_equal "Logger", @response.body
+ assert_equal "ActiveSupport::Logger", @response.body
end
# :ported:
@@ -979,6 +1030,7 @@ class RenderTest < ActionController::TestCase
def test_accessing_local_assigns_in_inline_template
get :accessing_local_assigns_in_inline_template, :local_name => "Local David"
assert_equal "Goodbye, Local David", @response.body
+ assert_equal "text/html", @response.content_type
end
def test_should_implicitly_render_html_template_from_xhr_request
@@ -1081,15 +1133,15 @@ class RenderTest < ActionController::TestCase
# :ported:
def test_double_render
- assert_raise(ActionController::DoubleRenderError) { get :double_render }
+ assert_raise(AbstractController::DoubleRenderError) { get :double_render }
end
def test_double_redirect
- assert_raise(ActionController::DoubleRenderError) { get :double_redirect }
+ assert_raise(AbstractController::DoubleRenderError) { get :double_redirect }
end
def test_render_and_redirect
- assert_raise(ActionController::DoubleRenderError) { get :render_and_redirect }
+ assert_raise(AbstractController::DoubleRenderError) { get :render_and_redirect }
end
# specify the one exception to double render rule - render_to_string followed by render
@@ -1133,6 +1185,19 @@ class RenderTest < ActionController::TestCase
assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body
end
+ def test_head_created
+ post :head_created
+ assert_blank @response.body
+ assert_response :created
+ end
+
+ def test_head_created_with_application_json_content_type
+ post :head_created_with_application_json_content_type
+ assert_blank @response.body
+ assert_equal "application/json", @response.content_type
+ assert_response :created
+ end
+
def test_head_with_location_header
get :head_with_location_header
assert_blank @response.body
@@ -1144,7 +1209,7 @@ class RenderTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :customers
- match ':controller/:action'
+ get ':controller/:action'
end
get :head_with_location_object
@@ -1224,22 +1289,57 @@ class RenderTest < ActionController::TestCase
def test_partial_only
get :partial_only
assert_equal "only partial", @response.body
+ assert_equal "text/html", @response.content_type
end
def test_should_render_html_formatted_partial
get :partial
- assert_equal 'partial html', @response.body
+ assert_equal "partial html", @response.body
+ assert_equal "text/html", @response.content_type
+ end
+
+ def test_render_html_formatted_partial_even_with_other_mime_time_in_accept
+ @request.accept = "text/javascript, text/html"
+
+ get :partial_html_erb
+
+ assert_equal "partial.html.erb", @response.body.strip
+ assert_equal "text/html", @response.content_type
end
def test_should_render_html_partial_with_formats
get :partial_formats_html
- assert_equal 'partial html', @response.body
+ assert_equal "partial html", @response.body
+ assert_equal "text/html", @response.content_type
end
def test_render_to_string_partial
get :render_to_string_with_partial
assert_equal "only partial", assigns(:partial_only)
assert_equal "Hello: david", assigns(:partial_with_locals)
+ assert_equal "text/html", @response.content_type
+ end
+
+ def test_render_to_string_with_template_and_html_partial
+ get :render_to_string_with_template_and_html_partial
+ assert_equal "**only partial**\n", assigns(:text)
+ assert_equal "<strong>only partial</strong>\n", assigns(:html)
+ assert_equal "<strong>only html partial</strong>\n", @response.body
+ assert_equal "text/html", @response.content_type
+ end
+
+ def test_render_to_string_and_render_with_different_formats
+ get :render_to_string_and_render_with_different_formats
+ assert_equal "<strong>only partial</strong>\n", assigns(:html)
+ assert_equal "**only partial**\n", @response.body
+ assert_equal "text/plain", @response.content_type
+ end
+
+ def test_render_template_within_a_template_with_other_format
+ get :render_template_within_a_template_with_other_format
+ expected = "only html partial<p>This is grand!</p>"
+ assert_equal expected, @response.body.strip
+ assert_equal "text/html", @response.content_type
end
def test_partial_with_counter
@@ -1387,20 +1487,37 @@ class ExpiresInRenderTest < ActionController::TestCase
assert_equal "max-age=60, public", @response.headers["Cache-Control"]
end
+ def test_expires_in_header_with_must_revalidate
+ get :conditional_hello_with_expires_in_with_must_revalidate
+ assert_equal "max-age=60, private, must-revalidate", @response.headers["Cache-Control"]
+ end
+
+ def test_expires_in_header_with_public_and_must_revalidate
+ get :conditional_hello_with_expires_in_with_public_and_must_revalidate
+ assert_equal "max-age=60, public, must-revalidate", @response.headers["Cache-Control"]
+ end
+
def test_expires_in_header_with_additional_headers
get :conditional_hello_with_expires_in_with_public_with_more_keys
- assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
+ assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"]
end
def test_expires_in_old_syntax
get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
- assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"]
+ assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"]
end
def test_expires_now
get :conditional_hello_with_expires_now
assert_equal "no-cache", @response.headers["Cache-Control"]
end
+
+ def test_date_header_when_expires_in
+ time = Time.mktime(2011,10,30)
+ Time.stubs(:now).returns(time)
+ get :conditional_hello_with_expires_in
+ assert_equal Time.now.httpdate, @response.headers["Date"]
+ end
end
class LastModifiedRenderTest < ActionController::TestCase
@@ -1440,6 +1557,36 @@ class LastModifiedRenderTest < ActionController::TestCase
assert_equal @last_modified, @response.headers['Last-Modified']
end
+
+ def test_responds_with_last_modified_with_record
+ get :conditional_hello_with_record
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_not_modified_with_record
+ @request.if_modified_since = @last_modified
+ get :conditional_hello_with_record
+ assert_equal 304, @response.status.to_i
+ assert_blank @response.body
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+ def test_request_not_modified_but_etag_differs_with_record
+ @request.if_modified_since = @last_modified
+ @request.if_none_match = "234"
+ get :conditional_hello_with_record
+ assert_response :success
+ end
+
+ def test_request_modified_with_record
+ @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
+ get :conditional_hello_with_record
+ assert_equal 200, @response.status.to_i
+ assert_present @response.body
+ assert_equal @last_modified, @response.headers['Last-Modified']
+ end
+
+
def test_request_with_bang_gets_last_modified
get :conditional_hello_with_bangs
assert_equal @last_modified, @response.headers['Last-Modified']
diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb
index ec4dc848ff..4f280c4bec 100644
--- a/actionpack/test/controller/render_xml_test.rb
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -48,7 +48,7 @@ class RenderXmlTest < ActionController::TestCase
# 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)
+ @controller.logger = ActiveSupport::Logger.new(nil)
@request.host = "www.nextangle.com"
end
@@ -72,7 +72,7 @@ class RenderXmlTest < ActionController::TestCase
with_routing do |set|
set.draw do
resources :customers
- match ':controller/:action'
+ get ':controller/:action'
end
get :render_with_object_location
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index fd5a41a0bb..066cd523be 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -1,6 +1,5 @@
require 'abstract_unit'
require 'digest/sha1'
-require 'active_support/core_ext/string/strip'
require "active_support/log_subscriber/test_helper"
# common controller actions
@@ -37,6 +36,22 @@ module RequestForgeryProtectionActions
render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>"
end
+ def form_for_remote
+ render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>"
+ end
+
+ def form_for_remote_with_token
+ render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => true ) {} %>"
+ end
+
+ def form_for_with_token
+ render :inline => "<%= form_for(:some_resource, :authenticity_token => true ) {} %>"
+ end
+
+ def form_for_remote_with_external_token
+ render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>"
+ end
+
def rescue_action(e) raise e end
end
@@ -46,7 +61,7 @@ class RequestForgeryProtectionController < ActionController::Base
protect_from_forgery :only => %w(index meta)
end
-class RequestForgeryProtectionControllerUsingOldBehaviour < ActionController::Base
+class RequestForgeryProtectionControllerUsingException < ActionController::Base
include RequestForgeryProtectionActions
protect_from_forgery :only => %w(index meta)
@@ -74,9 +89,7 @@ class CustomAuthenticityParamController < RequestForgeryProtectionController
end
end
-
# common test methods
-
module RequestForgeryProtectionTests
def setup
@token = "cf50faa3fe97702ca1ae"
@@ -103,6 +116,60 @@ module RequestForgeryProtectionTests
assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
end
+ def test_should_render_form_without_token_tag_if_remote
+ assert_not_blocked do
+ get :form_for_remote
+ end
+ assert_no_match(/authenticity_token/, response.body)
+ end
+
+ def test_should_render_form_with_token_tag_if_remote_and_embedding_token_is_on
+ original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
+ begin
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
+ assert_not_blocked do
+ get :form_for_remote
+ end
+ assert_match(/authenticity_token/, response.body)
+ ensure
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
+ end
+ end
+
+ def test_should_render_form_with_token_tag_if_remote_and_external_authenticity_token_requested_and_embedding_is_on
+ original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms
+ begin
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true
+ assert_not_blocked do
+ get :form_for_remote_with_external_token
+ end
+ assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
+ ensure
+ ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original
+ end
+ end
+
+ def test_should_render_form_with_token_tag_if_remote_and_external_authenticity_token_requested
+ assert_not_blocked do
+ get :form_for_remote_with_external_token
+ end
+ assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token'
+ end
+
+ def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested
+ assert_not_blocked do
+ get :form_for_remote_with_token
+ end
+ assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
+ end
+
+ def test_should_render_form_with_token_tag_with_authenticity_token_requested
+ assert_not_blocked do
+ get :form_for_with_token
+ end
+ assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token
+ end
+
def test_should_allow_get
assert_not_blocked { get :index }
end
@@ -119,6 +186,10 @@ module RequestForgeryProtectionTests
assert_blocked { post :index, :format=>'xml' }
end
+ def test_should_not_allow_patch_without_token
+ assert_blocked { patch :index }
+ end
+
def test_should_not_allow_put_without_token
assert_blocked { put :index }
end
@@ -135,6 +206,10 @@ module RequestForgeryProtectionTests
assert_not_blocked { post :index, :custom_authenticity_token => @token }
end
+ def test_should_allow_patch_with_token
+ assert_not_blocked { patch :index, :custom_authenticity_token => @token }
+ end
+
def test_should_allow_put_with_token
assert_not_blocked { put :index, :custom_authenticity_token => @token }
end
@@ -153,6 +228,11 @@ module RequestForgeryProtectionTests
assert_not_blocked { delete :index }
end
+ def test_should_allow_patch_with_token_in_header
+ @request.env['HTTP_X_CSRF_TOKEN'] = @token
+ assert_not_blocked { patch :index }
+ end
+
def test_should_allow_put_with_token_in_header
@request.env['HTTP_X_CSRF_TOKEN'] = @token
assert_not_blocked { put :index }
@@ -207,7 +287,7 @@ class RequestForgeryProtectionControllerTest < ActionController::TestCase
end
end
-class RequestForgeryProtectionControllerUsingOldBehaviourTest < ActionController::TestCase
+class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase
include RequestForgeryProtectionTests
def assert_blocked
assert_raises(ActionController::InvalidAuthenticityToken) do
@@ -237,7 +317,7 @@ class FreeCookieControllerTest < ActionController::TestCase
end
def test_should_allow_all_methods_without_token
- [:post, :put, :delete].each do |method|
+ [:post, :patch, :put, :delete].each do |method|
assert_nothing_raised { send(method, :index)}
end
end
@@ -248,10 +328,6 @@ class FreeCookieControllerTest < ActionController::TestCase
end
end
-
-
-
-
class CustomAuthenticityParamControllerTest < ActionController::TestCase
def setup
ActionController::Base.request_forgery_protection_token = :custom_token_name
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index c445285538..48e2d6491e 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -60,11 +60,6 @@ class RescueController < ActionController::Base
render :text => exception.message
end
- # This is a Dispatcher exception and should be in ApplicationController.
- rescue_from ActionController::RoutingError do
- render :text => 'no way'
- end
-
rescue_from ActionView::TemplateError do
render :text => 'action_view templater error'
end
@@ -137,11 +132,11 @@ class RescueController < ActionController::Base
end
def io_error_in_view
- raise ActionView::TemplateError.new(nil, {}, IOError.new('this is io error'))
+ raise ActionView::TemplateError.new(nil, IOError.new('this is io error'))
end
def zero_division_error_in_view
- raise ActionView::TemplateError.new(nil, {}, ZeroDivisionError.new('this is zero division error'))
+ raise ActionView::TemplateError.new(nil, ZeroDivisionError.new('this is zero division error'))
end
protected
@@ -338,29 +333,14 @@ class RescueTest < ActionDispatch::IntegrationTest
end
end
- test 'rescue routing exceptions' do
- raiser = proc { |env| raise ActionController::RoutingError, "Did not handle the request" }
- @app = ActionDispatch::Rescue.new(raiser) do
- rescue_from ActionController::RoutingError, lambda { |env| [200, {"Content-Type" => "text/html"}, ["Gotcha!"]] }
- end
-
- get '/b00m'
- assert_equal "Gotcha!", response.body
- end
-
- test 'unrescued exception' do
- raiser = proc { |env| raise ActionController::RoutingError, "Did not handle the request" }
- @app = ActionDispatch::Rescue.new(raiser)
- assert_raise(ActionController::RoutingError) { get '/b00m' }
- end
-
private
+
def with_test_routing
with_routing do |set|
set.draw do
- match 'foo', :to => ::RescueTest::TestController.action(:foo)
- match 'invalid', :to => ::RescueTest::TestController.action(:invalid)
- match 'b00m', :to => ::RescueTest::TestController.action(:b00m)
+ get 'foo', :to => ::RescueTest::TestController.action(:foo)
+ get 'invalid', :to => ::RescueTest::TestController.action(:invalid)
+ get 'b00m', :to => ::RescueTest::TestController.action(:b00m)
end
yield
end
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 6b8a8f6161..9fc875014c 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -6,7 +6,6 @@ require 'active_support/core_ext/object/inclusion'
class ResourcesController < ActionController::Base
def index() render :nothing => true end
alias_method :show, :index
- def rescue_action(e) raise e end
end
class ThreadsController < ResourcesController; end
@@ -159,7 +158,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_collection_actions
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -168,6 +167,7 @@ class ResourcesTest < ActionController::TestCase
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
@@ -186,7 +186,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_collection_actions_and_name_prefix
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -196,6 +196,7 @@ class ResourcesTest < ActionController::TestCase
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
end
@@ -242,7 +243,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_collection_action_and_name_prefix_and_formatted
- actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
+ actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch }
with_routing do |set|
set.draw do
@@ -252,6 +253,7 @@ class ResourcesTest < ActionController::TestCase
put :b, :on => :collection
post :c, :on => :collection
delete :d, :on => :collection
+ patch :e, :on => :collection
end
end
end
@@ -271,7 +273,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_member_action
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method } do
mark_options = {:action => 'mark', :id => '1'}
mark_path = "/messages/1/mark"
@@ -295,7 +297,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_member_when_override_paths_for_default_restful_actions_with
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do
mark_options = {:action => 'mark', :id => '1', :controller => "messages"}
mark_path = "/messages/1/mark"
@@ -312,7 +314,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_with_two_member_actions_with_same_method
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resources :messages do
@@ -565,7 +567,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_singleton_resource_with_member_action
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resource :account do
@@ -587,7 +589,7 @@ class ResourcesTest < ActionController::TestCase
end
def test_singleton_resource_with_two_member_actions_with_same_method
- [:put, :post].each do |method|
+ [:patch, :put, :post].each do |method|
with_routing do |set|
set.draw do
resource :account do
@@ -652,13 +654,17 @@ class ResourcesTest < ActionController::TestCase
end
end
- def test_should_not_allow_delete_or_put_on_collection_path
+ def test_should_not_allow_delete_or_patch_or_put_on_collection_path
controller_name = :messages
with_restful_routing controller_name do
options = { :controller => controller_name.to_s }
collection_path = "/#{controller_name}"
assert_raise(ActionController::RoutingError) do
+ assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :patch)
+ end
+
+ assert_raise(ActionController::RoutingError) do
assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put)
end
@@ -674,7 +680,7 @@ class ResourcesTest < ActionController::TestCase
scope '/threads/:thread_id' do
resources :messages, :as => 'thread_messages' do
get :search, :on => :collection
- match :preview, :on => :new
+ get :preview, :on => :new
end
end
end
@@ -692,7 +698,7 @@ class ResourcesTest < ActionController::TestCase
scope '/admin' do
resource :account, :as => :admin_account do
get :login, :on => :member
- match :preview, :on => :new
+ get :preview, :on => :new
end
end
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 5bf68decca..cd91064ab8 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -6,25 +6,18 @@ require 'active_support/core_ext/object/with_options'
class MilestonesController < ActionController::Base
def index() head :ok end
alias_method :show, :index
- def rescue_action(e) raise e end
end
ROUTING = ActionDispatch::Routing
-module RoutingTestHelpers
- def url_for(set, options, recall = nil)
- set.send(:url_for, options.merge(:only_path => true, :_path_segments => recall))
- end
-end
-
# See RFC 3986, section 3.3 for allowed path characters.
-class UriReservedCharactersRoutingTest < Test::Unit::TestCase
+class UriReservedCharactersRoutingTest < ActiveSupport::TestCase
include RoutingTestHelpers
def setup
@set = ActionDispatch::Routing::RouteSet.new
@set.draw do
- match ':controller/:action/:variable/*additional'
+ get ':controller/:action/:variable/*additional'
end
safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ])
@@ -66,11 +59,11 @@ end
class MockController
def self.build(helpers)
Class.new do
- def url_for(options)
+ def url_options
+ options = super
options[:protocol] ||= "http"
options[:host] ||= "test.host"
-
- super(options)
+ options
end
include helpers
@@ -78,17 +71,197 @@ class MockController
end
end
-class LegacyRouteSetTests < Test::Unit::TestCase
+class LegacyRouteSetTests < ActiveSupport::TestCase
include RoutingTestHelpers
+ include ActionDispatch::RoutingVerbs
attr_reader :rs
+ alias :routes :rs
def setup
- @rs = ::ActionDispatch::Routing::RouteSet.new
+ @rs = ::ActionDispatch::Routing::RouteSet.new
+ @response = nil
end
- def teardown
- @rs.clear!
+ def test_symbols_with_dashes
+ rs.draw do
+ get '/:artist/:song-omg', :to => lambda { |env|
+ resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = JSON.load get(URI('http://example.org/journey/faithfully-omg'))
+ assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash)
+ end
+
+ def test_id_with_dash
+ rs.draw do
+ get '/journey/:id', :to => lambda { |env|
+ resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = JSON.load get(URI('http://example.org/journey/faithfully-omg'))
+ assert_equal({"id"=>"faithfully-omg"}, hash)
+ end
+
+ def test_dash_with_custom_regexp
+ rs.draw do
+ get '/:artist/:song-omg', :constraints => { :song => /\d+/ }, :to => lambda { |env|
+ resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = JSON.load get(URI('http://example.org/journey/123-omg'))
+ assert_equal({"artist"=>"journey", "song"=>"123"}, hash)
+ assert_equal 'Not Found', get(URI('http://example.org/journey/faithfully-omg'))
+ end
+
+ def test_pre_dash
+ rs.draw do
+ get '/:artist/omg-:song', :to => lambda { |env|
+ resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = JSON.load get(URI('http://example.org/journey/omg-faithfully'))
+ assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash)
+ end
+
+ def test_pre_dash_with_custom_regexp
+ rs.draw do
+ get '/:artist/omg-:song', :constraints => { :song => /\d+/ }, :to => lambda { |env|
+ resp = JSON.dump env[ActionDispatch::Routing::RouteSet::PARAMETERS_KEY]
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = JSON.load get(URI('http://example.org/journey/omg-123'))
+ assert_equal({"artist"=>"journey", "song"=>"123"}, hash)
+ assert_equal 'Not Found', get(URI('http://example.org/journey/omg-faithfully'))
+ end
+
+ def test_star_paths_are_greedy
+ rs.draw do
+ get "/*path", :to => lambda { |env|
+ x = env["action_dispatch.request.path_parameters"][:path]
+ [200, {}, [x]]
+ }, :format => false
+ end
+
+ u = URI('http://example.org/foo/bar.html')
+ assert_equal u.path.sub(/^\//, ''), get(u)
+ end
+
+ def test_star_paths_are_greedy_but_not_too_much
+ rs.draw do
+ get "/*path", :to => lambda { |env|
+ x = JSON.dump env["action_dispatch.request.path_parameters"]
+ [200, {}, [x]]
+ }
+ end
+
+ expected = { "path" => "foo/bar", "format" => "html" }
+ u = URI('http://example.org/foo/bar.html')
+ assert_equal expected, JSON.parse(get(u))
+ end
+
+ def test_optional_star_paths_are_greedy
+ rs.draw do
+ get "/(*filters)", :to => lambda { |env|
+ x = env["action_dispatch.request.path_parameters"][:filters]
+ [200, {}, [x]]
+ }, :format => false
+ end
+
+ u = URI('http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794')
+ assert_equal u.path.sub(/^\//, ''), get(u)
+ end
+
+ def test_optional_star_paths_are_greedy_but_not_too_much
+ rs.draw do
+ get "/(*filters)", :to => lambda { |env|
+ x = JSON.dump env["action_dispatch.request.path_parameters"]
+ [200, {}, [x]]
+ }
+ end
+
+ expected = { "filters" => "ne_27.065938,-80.6092/sw_25.489856,-82",
+ "format" => "542794" }
+ u = URI('http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794')
+ assert_equal expected, JSON.parse(get(u))
+ end
+
+ def test_regexp_precidence
+ @rs.draw do
+ get '/whois/:domain', :constraints => {
+ :domain => /\w+\.[\w\.]+/ },
+ :to => lambda { |env| [200, {}, %w{regexp}] }
+
+ get '/whois/:id', :to => lambda { |env| [200, {}, %w{id}] }
+ end
+
+ assert_equal 'regexp', get(URI('http://example.org/whois/example.org'))
+ assert_equal 'id', get(URI('http://example.org/whois/123'))
+ end
+
+ def test_class_and_lambda_constraints
+ subdomain = Class.new {
+ def matches? request
+ request.subdomain.present? and request.subdomain != 'clients'
+ end
+ }
+
+ @rs.draw do
+ get '/', :constraints => subdomain.new,
+ :to => lambda { |env| [200, {}, %w{default}] }
+ get '/', :constraints => { :subdomain => 'clients' },
+ :to => lambda { |env| [200, {}, %w{clients}] }
+ end
+
+ assert_equal 'default', get(URI('http://www.example.org/'))
+ assert_equal 'clients', get(URI('http://clients.example.org/'))
+ end
+
+ def test_lambda_constraints
+ @rs.draw do
+ get '/', :constraints => lambda { |req|
+ req.subdomain.present? and req.subdomain != "clients" },
+ :to => lambda { |env| [200, {}, %w{default}] }
+
+ get '/', :constraints => lambda { |req|
+ req.subdomain.present? && req.subdomain == "clients" },
+ :to => lambda { |env| [200, {}, %w{clients}] }
+ end
+
+ assert_equal 'default', get(URI('http://www.example.org/'))
+ assert_equal 'clients', get(URI('http://clients.example.org/'))
+ end
+
+ def test_empty_string_match
+ rs.draw do
+ get '/:username', :constraints => { :username => /[^\/]+/ },
+ :to => lambda { |e| [200, {}, ['foo']] }
+ end
+ assert_equal 'Not Found', get(URI('http://example.org/'))
+ assert_equal 'foo', get(URI('http://example.org/hello'))
+ end
+
+ def test_non_greedy_glob_regexp
+ params = nil
+ rs.draw do
+ get '/posts/:id(/*filters)', :constraints => { :filters => /.+?/ },
+ :to => lambda { |e|
+ params = e["action_dispatch.request.path_parameters"]
+ [200, {}, ['foo']]
+ }
+ end
+ assert_equal 'foo', get(URI('http://example.org/posts/1/foo.js'))
+ assert_equal({:id=>"1", :filters=>"foo", :format=>"js"}, params)
end
def test_draw_with_block_arity_one_raises
@@ -98,7 +271,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
end
def test_default_setup
- @rs.draw { match '/:controller(/:action(/:id))' }
+ @rs.draw { get '/:controller(/:action(/:id))' }
assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list"))
assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10"))
@@ -116,51 +289,21 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_ignores_leading_slash
@rs.clear!
- @rs.draw { match '/:controller(/:action(/:id))'}
+ @rs.draw { get '/:controller(/:action(/:id))'}
test_default_setup
end
- def test_time_recognition
- # We create many routes to make situation more realistic
- @rs = ::ActionDispatch::Routing::RouteSet.new
- @rs.draw {
- root :to => "search#new", :as => "frontpage"
- resources :videos do
- resources :comments
- resource :file, :controller => 'video_file'
- resource :share, :controller => 'video_shares'
- resource :abuse, :controller => 'video_abuses'
- end
- resources :abuses, :controller => 'video_abuses'
- resources :video_uploads
- resources :video_visits
-
- resources :users do
- resource :settings
- resources :videos
- end
- resources :channels do
- resources :videos, :controller => 'channel_videos'
- end
- resource :session
- resource :lost_password
- match 'search' => 'search#index', :as => 'search'
- resources :pages
- match ':controller/:action/:id'
- }
- end
-
def test_route_with_colon_first
rs.draw do
- match '/:controller/:action/:id', :action => 'index', :id => nil
- match ':url', :controller => 'tiny_url', :action => 'translate'
+ get '/:controller/:action/:id', :action => 'index', :id => nil
+ get ':url', :controller => 'tiny_url', :action => 'translate'
end
end
def test_route_with_regexp_for_controller
rs.draw do
- match ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/
- match '/:controller(/:action(/:id))'
+ get ':controller/:admintoken(/:action(/:id))', :controller => /admin\/.+/
+ get '/:controller(/:action(/:id))'
end
assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"},
@@ -174,7 +317,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_route_with_regexp_and_captures_for_controller
rs.draw do
- match '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/
+ get '/:controller(/:action(/:id))', :controller => /admin\/(accounts|users)/
end
assert_equal({:controller => "admin/accounts", :action => "index"}, rs.recognize_path("/admin/accounts"))
assert_equal({:controller => "admin/users", :action => "index"}, rs.recognize_path("/admin/users"))
@@ -183,7 +326,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_route_with_regexp_and_dot
rs.draw do
- match ':controller/:action/:file',
+ get ':controller/:action/:file',
:controller => /admin|user/,
:action => /upload|download/,
:defaults => {:file => nil},
@@ -213,7 +356,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_named_route_with_option
rs.draw do
- match 'page/:title' => 'content#show_page', :as => 'page'
+ get 'page/:title' => 'content#show_page', :as => 'page'
end
assert_equal("http://test.host/page/new%20stuff",
@@ -222,7 +365,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_named_route_with_default
rs.draw do
- match 'page/:title' => 'content#show_page', :title => 'AboutPage', :as => 'page'
+ get 'page/:title' => 'content#show_page', :title => 'AboutPage', :as => 'page'
end
assert_equal("http://test.host/page/AboutRails",
@@ -232,7 +375,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_named_route_with_path_prefix
rs.draw do
scope "my" do
- match 'page' => 'content#show_page', :as => 'page'
+ get 'page' => 'content#show_page', :as => 'page'
end
end
@@ -243,7 +386,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_named_route_with_blank_path_prefix
rs.draw do
scope "" do
- match 'page' => 'content#show_page', :as => 'page'
+ get 'page' => 'content#show_page', :as => 'page'
end
end
@@ -253,7 +396,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_named_route_with_nested_controller
rs.draw do
- match 'admin/user' => 'admin/user#index', :as => "users"
+ get 'admin/user' => 'admin/user#index', :as => "users"
end
assert_equal("http://test.host/admin/user",
@@ -262,7 +405,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_optimised_named_route_with_host
rs.draw do
- match 'page' => 'content#show_page', :as => 'pages', :host => 'foo.com'
+ get 'page' => 'content#show_page', :as => 'pages', :host => 'foo.com'
end
routes = setup_for_named_route
routes.expects(:url_for).with({
@@ -281,7 +424,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_named_route_without_hash
rs.draw do
- match ':controller/:action/:id', :as => 'normal'
+ get ':controller/:action/:id', :as => 'normal'
end
end
@@ -294,11 +437,20 @@ class LegacyRouteSetTests < Test::Unit::TestCase
assert_equal("/", routes.send(:root_path))
end
+ def test_named_route_root_without_hash
+ rs.draw do
+ root "hello#index"
+ end
+ routes = setup_for_named_route
+ assert_equal("http://test.host/", routes.send(:root_url))
+ assert_equal("/", routes.send(:root_path))
+ end
+
def test_named_route_with_regexps
rs.draw do
- match 'page/:year/:month/:day/:title' => 'page#show', :as => 'article',
+ get 'page/:year/:month/:day/:title' => 'page#show', :as => 'article',
:year => /\d+/, :month => /\d+/, :day => /\d+/
- match ':controller/:action/:id'
+ get ':controller/:action/:id'
end
routes = setup_for_named_route
@@ -308,7 +460,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
end
def test_changing_controller
- @rs.draw { match ':controller/:action/:id' }
+ @rs.draw { get ':controller/:action/:id' }
assert_equal '/admin/stuff/show/10',
url_for(rs, {:controller => 'stuff', :action => 'show', :id => 10},
@@ -317,8 +469,8 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_paths_escaped
rs.draw do
- match 'file/*path' => 'content#show_file', :as => 'path'
- match ':controller/:action/:id'
+ get 'file/*path' => 'content#show_file', :as => 'path'
+ get ':controller/:action/:id'
end
# No + to space in URI escaping, only for query params.
@@ -334,7 +486,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_paths_slashes_unescaped_with_ordered_parameters
rs.draw do
- match '/file/*path' => 'content#index', :as => 'path'
+ get '/file/*path' => 'content#index', :as => 'path'
end
# No / to %2F in URI, only for query params.
@@ -343,14 +495,14 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_non_controllers_cannot_be_matched
rs.draw do
- match ':controller/:action/:id'
+ get ':controller/:action/:id'
end
assert_raise(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") }
end
def test_should_list_options_diff_when_routing_constraints_dont_match
rs.draw do
- match 'post/:id' => 'post#show', :constraints => { :id => /\d+/ }, :as => 'post'
+ get 'post/:id' => 'post#show', :constraints => { :id => /\d+/ }, :as => 'post'
end
assert_raise(ActionController::RoutingError) do
url_for(rs, { :controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post" })
@@ -359,7 +511,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_dynamic_path_allowed
rs.draw do
- match '*path' => 'content#show_file'
+ get '*path' => 'content#show_file'
end
assert_equal '/pages/boo',
@@ -368,7 +520,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_dynamic_recall_paths_allowed
rs.draw do
- match '*path' => 'content#show_file'
+ get '*path' => 'content#show_file'
end
assert_equal '/pages/boo',
@@ -377,8 +529,8 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_backwards
rs.draw do
- match 'page/:id(/:action)' => 'pages#show'
- match ':controller(/:action(/:id))'
+ get 'page/:id(/:action)' => 'pages#show'
+ get ':controller(/:action(/:id))'
end
assert_equal '/page/20', url_for(rs, { :id => 20 }, { :controller => 'pages', :action => 'show' })
@@ -388,8 +540,8 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_route_with_fixnum_default
rs.draw do
- match 'page(/:id)' => 'content#show_page', :id => 1
- match ':controller/:action/:id'
+ get 'page(/:id)' => 'content#show_page', :id => 1
+ get ':controller/:action/:id'
end
assert_equal '/page', url_for(rs, { :controller => 'content', :action => 'show_page' })
@@ -405,15 +557,15 @@ class LegacyRouteSetTests < Test::Unit::TestCase
# For newer revision
def test_route_with_text_default
rs.draw do
- match 'page/:id' => 'content#show_page', :id => 1
- match ':controller/:action/:id'
+ get 'page/:id' => 'content#show_page', :id => 1
+ get ':controller/:action/:id'
end
assert_equal '/page/foo', url_for(rs, { :controller => 'content', :action => 'show_page', :id => 'foo' })
assert_equal({ :controller => "content", :action => 'show_page', :id => 'foo' }, rs.recognize_path("/page/foo"))
token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in Russian
- token.force_encoding(Encoding::BINARY) if token.respond_to?(:force_encoding)
+ token.force_encoding(Encoding::BINARY)
escaped_token = CGI::escape(token)
assert_equal '/page/' + escaped_token, url_for(rs, { :controller => 'content', :action => 'show_page', :id => token })
@@ -421,13 +573,13 @@ class LegacyRouteSetTests < Test::Unit::TestCase
end
def test_action_expiry
- @rs.draw { match ':controller(/:action(/:id))' }
+ @rs.draw { get ':controller(/:action(/:id))' }
assert_equal '/content', url_for(rs, { :controller => 'content' }, { :controller => 'content', :action => 'show' })
end
def test_requirement_should_prevent_optional_id
rs.draw do
- match 'post/:id' => 'post#show', :constraints => {:id => /\d+/}, :as => 'post'
+ get 'post/:id' => 'post#show', :constraints => {:id => /\d+/}, :as => 'post'
end
assert_equal '/post/10', url_for(rs, { :controller => 'post', :action => 'show', :id => 10 })
@@ -439,11 +591,11 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_both_requirement_and_optional
rs.draw do
- match('test(/:year)' => 'post#show', :as => 'blog',
+ get('test(/:year)' => 'post#show', :as => 'blog',
:defaults => { :year => nil },
:constraints => { :year => /\d{4}/ }
)
- match ':controller/:action/:id'
+ get ':controller/:action/:id'
end
assert_equal '/test', url_for(rs, { :controller => 'post', :action => 'show' })
@@ -454,8 +606,8 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_set_to_nil_forgets
rs.draw do
- match 'pages(/:year(/:month(/:day)))' => 'content#list_pages', :month => nil, :day => nil
- match ':controller/:action/:id'
+ get 'pages(/:year(/:month(/:day)))' => 'content#list_pages', :month => nil, :day => nil
+ get ':controller/:action/:id'
end
assert_equal '/pages/2005',
@@ -497,8 +649,8 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_named_route_method
rs.draw do
- match 'categories' => 'content#categories', :as => 'categories'
- match ':controller(/:action(/:id))'
+ get 'categories' => 'content#categories', :as => 'categories'
+ get ':controller(/:action(/:id))'
end
assert_equal '/categories', url_for(rs, { :controller => 'content', :action => 'categories' })
@@ -512,9 +664,9 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_nil_defaults
rs.draw do
- match 'journal' => 'content#list_journal',
+ get 'journal' => 'content#list_journal',
:date => nil, :user_id => nil
- match ':controller/:action/:id'
+ get ':controller/:action/:id'
end
assert_equal '/journal', url_for(rs, {
@@ -530,11 +682,12 @@ class LegacyRouteSetTests < Test::Unit::TestCase
match '/match' => 'books#get', :via => :get
match '/match' => 'books#post', :via => :post
match '/match' => 'books#put', :via => :put
+ match '/match' => 'books#patch', :via => :patch
match '/match' => 'books#delete', :via => :delete
end
end
- %w(GET POST PUT DELETE).each do |request_method|
+ %w(GET PATCH POST PUT DELETE).each do |request_method|
define_method("test_request_method_recognized_with_#{request_method}") do
setup_request_method_routes_for(request_method)
params = rs.recognize_path("/match", :method => request_method)
@@ -545,7 +698,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_recognize_array_of_methods
rs.draw do
match '/match' => 'books#get_or_post', :via => [:get, :post]
- match '/match' => 'books#not_get_or_post'
+ put '/match' => 'books#not_get_or_post'
end
params = rs.recognize_path("/match", :method => :post)
@@ -557,10 +710,10 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_subpath_recognized
rs.draw do
- match '/books/:id/edit' => 'subpath_books#edit'
- match '/items/:id/:action' => 'subpath_books'
- match '/posts/new/:action' => 'subpath_books'
- match '/posts/:id' => 'subpath_books#show'
+ get '/books/:id/edit' => 'subpath_books#edit'
+ get '/items/:id/:action' => 'subpath_books'
+ get '/posts/new/:action' => 'subpath_books'
+ get '/posts/:id' => 'subpath_books#show'
end
hash = rs.recognize_path "/books/17/edit"
@@ -582,9 +735,9 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_subpath_generated
rs.draw do
- match '/books/:id/edit' => 'subpath_books#edit'
- match '/items/:id/:action' => 'subpath_books'
- match '/posts/new/:action' => 'subpath_books'
+ get '/books/:id/edit' => 'subpath_books#edit'
+ get '/items/:id/:action' => 'subpath_books'
+ get '/posts/new/:action' => 'subpath_books'
end
assert_equal "/books/7/edit", url_for(rs, { :controller => "subpath_books", :id => 7, :action => "edit" })
@@ -594,7 +747,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_failed_constraints_raises_exception_with_violated_constraints
rs.draw do
- match 'foos/:id' => 'foos#show', :as => 'foo_with_requirement', :constraints => { :id => /\d+/ }
+ get 'foos/:id' => 'foos#show', :as => 'foo_with_requirement', :constraints => { :id => /\d+/ }
end
assert_raise(ActionController::RoutingError) do
@@ -605,11 +758,11 @@ class LegacyRouteSetTests < Test::Unit::TestCase
def test_routes_changed_correctly_after_clear
rs = ::ActionDispatch::Routing::RouteSet.new
rs.draw do
- match 'ca' => 'ca#aa'
- match 'cb' => 'cb#ab'
- match 'cc' => 'cc#ac'
- match ':controller/:action/:id'
- match ':controller/:action/:id.:format'
+ get 'ca' => 'ca#aa'
+ get 'cb' => 'cb#ab'
+ get 'cc' => 'cc#ac'
+ get ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
end
hash = rs.recognize_path "/cc"
@@ -618,10 +771,10 @@ class LegacyRouteSetTests < Test::Unit::TestCase
assert_equal %w(cc ac), [hash[:controller], hash[:action]]
rs.draw do
- match 'cb' => 'cb#ab'
- match 'cc' => 'cc#ac'
- match ':controller/:action/:id'
- match ':controller/:action/:id.:format'
+ get 'cb' => 'cb#ab'
+ get 'cc' => 'cc#ac'
+ get ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
end
hash = rs.recognize_path "/cc"
@@ -646,29 +799,29 @@ class RouteSetTest < ActiveSupport::TestCase
@default_route_set ||= begin
set = ROUTING::RouteSet.new
set.draw do
- match '/:controller(/:action(/:id))'
+ get '/:controller(/:action(/:id))'
end
set
end
end
def test_generate_extras
- set.draw { match ':controller/(:action(/:id))' }
+ set.draw { get ':controller/(:action(/:id))' }
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
assert_equal %w(that this), extras.map { |e| e.to_s }.sort
end
def test_extra_keys
- set.draw { match ':controller/:action/:id' }
+ set.draw { get ':controller/:action/:id' }
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal %w(that this), extras.map { |e| e.to_s }.sort
end
def test_generate_extras_not_first
set.draw do
- match ':controller/:action/:id.:format'
- match ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
end
path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal "/foo/bar/15", path
@@ -677,8 +830,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_not_first
set.draw do
- match ':controller/:action/:id.:format'
- match ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
end
assert_equal "/foo/bar/15?this=hello",
url_for(set, { :controller => "foo", :action => "bar", :id => 15, :this => "hello" })
@@ -686,8 +839,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_extra_keys_not_first
set.draw do
- match ':controller/:action/:id.:format'
- match ':controller/:action/:id'
+ get ':controller/:action/:id.:format'
+ get ':controller/:action/:id'
end
extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
assert_equal %w(that this), extras.map { |e| e.to_s }.sort
@@ -696,7 +849,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_draw
assert_equal 0, set.routes.size
set.draw do
- match '/hello/world' => 'a#b'
+ get '/hello/world' => 'a#b'
end
assert_equal 1, set.routes.size
end
@@ -704,7 +857,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_draw_symbol_controller_name
assert_equal 0, set.routes.size
set.draw do
- match '/users/index' => 'users#index'
+ get '/users/index' => 'users#index'
end
set.recognize_path('/users/index', :method => :get)
assert_equal 1, set.routes.size
@@ -713,48 +866,31 @@ class RouteSetTest < ActiveSupport::TestCase
def test_named_draw
assert_equal 0, set.routes.size
set.draw do
- match '/hello/world' => 'a#b', :as => 'hello'
+ get '/hello/world' => 'a#b', :as => 'hello'
end
assert_equal 1, set.routes.size
assert_equal set.routes.first, set.named_routes[:hello]
end
- def test_later_named_routes_take_precedence
+ def test_earlier_named_routes_take_precedence
set.draw do
- match '/hello/world' => 'a#b', :as => 'hello'
- match '/hello' => 'a#b', :as => 'hello'
+ get '/hello/world' => 'a#b', :as => 'hello'
+ get '/hello' => 'a#b', :as => 'hello'
end
- assert_equal set.routes.last, set.named_routes[:hello]
+ assert_equal set.routes.first, set.named_routes[:hello]
end
def setup_named_route_test
set.draw do
- match '/people(/:id)' => 'people#show', :as => 'show'
- match '/people' => 'people#index', :as => 'index'
- match '/people/go/:foo/:bar/joe(/:id)' => 'people#multi', :as => 'multi'
- match '/admin/users' => 'admin/users#index', :as => "users"
+ get '/people(/:id)' => 'people#show', :as => 'show'
+ get '/people' => 'people#index', :as => 'index'
+ get '/people/go/:foo/:bar/joe(/:id)' => 'people#multi', :as => 'multi'
+ get '/admin/users' => 'admin/users#index', :as => "users"
end
MockController.build(set.url_helpers).new
end
- def test_named_route_hash_access_method
- controller = setup_named_route_test
-
- assert_equal(
- { :controller => 'people', :action => 'show', :id => 5, :use_route => "show", :only_path => false },
- controller.send(:hash_for_show_url, :id => 5))
-
- assert_equal(
- { :controller => 'people', :action => 'index', :use_route => "index", :only_path => false },
- controller.send(:hash_for_index_url))
-
- assert_equal(
- { :controller => 'people', :action => 'show', :id => 5, :use_route => "show", :only_path => true },
- controller.send(:hash_for_show_path, :id => 5)
- )
- end
-
def test_named_route_url_method
controller = setup_named_route_test
@@ -766,7 +902,6 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal "http://test.host/admin/users", controller.send(:users_url)
assert_equal '/admin/users', controller.send(:users_path)
- assert_equal '/admin/users', url_for(set, controller.send(:hash_for_users_url), { :controller => 'users', :action => 'index' })
end
def test_named_route_url_method_with_anchor
@@ -832,7 +967,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_draw_default_route
set.draw do
- match '/:controller/:action/:id'
+ get '/:controller/:action/:id'
end
assert_equal 1, set.routes.size
@@ -846,8 +981,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_with_parameter_shell
set.draw do
- match 'page/:id' => 'pages#show', :id => /\d+/
- match '/:controller(/:action(/:id))'
+ get 'page/:id' => 'pages#show', :id => /\d+/
+ get '/:controller(/:action(/:id))'
end
assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages'))
@@ -861,7 +996,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_constraints_on_request_object_with_anchors_are_valid
assert_nothing_raised do
set.draw do
- match 'page/:id' => 'pages#show', :constraints => { :host => /^foo$/ }
+ get 'page/:id' => 'pages#show', :constraints => { :host => /^foo$/ }
end
end
end
@@ -869,27 +1004,27 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_constraints_with_anchor_chars_are_invalid
assert_raise ArgumentError do
set.draw do
- match 'page/:id' => 'pages#show', :id => /^\d+/
+ get 'page/:id' => 'pages#show', :id => /^\d+/
end
end
assert_raise ArgumentError do
set.draw do
- match 'page/:id' => 'pages#show', :id => /\A\d+/
+ get 'page/:id' => 'pages#show', :id => /\A\d+/
end
end
assert_raise ArgumentError do
set.draw do
- match 'page/:id' => 'pages#show', :id => /\d+$/
+ get 'page/:id' => 'pages#show', :id => /\d+$/
end
end
assert_raise ArgumentError do
set.draw do
- match 'page/:id' => 'pages#show', :id => /\d+\Z/
+ get 'page/:id' => 'pages#show', :id => /\d+\Z/
end
end
assert_raise ArgumentError do
set.draw do
- match 'page/:id' => 'pages#show', :id => /\d+\z/
+ get 'page/:id' => 'pages#show', :id => /\d+\z/
end
end
end
@@ -904,7 +1039,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_recognize_with_encoded_id_and_regex
set.draw do
- match 'page/:id' => 'pages#show', :id => /[a-zA-Z0-9\+]+/
+ get 'page/:id' => 'pages#show', :id => /[a-zA-Z0-9\+]+/
end
assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
@@ -917,6 +1052,7 @@ class RouteSetTest < ActiveSupport::TestCase
post "/people" => "people#create"
get "/people/:id" => "people#show", :as => "person"
put "/people/:id" => "people#update"
+ patch "/people/:id" => "people#update"
delete "/people/:id" => "people#destroy"
end
@@ -929,6 +1065,9 @@ class RouteSetTest < ActiveSupport::TestCase
params = set.recognize_path("/people/5", :method => :put)
assert_equal("update", params[:action])
+ params = set.recognize_path("/people/5", :method => :patch)
+ assert_equal("update", params[:action])
+
assert_raise(ActionController::UnknownHttpMethod) {
set.recognize_path("/people", :method => :bacon)
}
@@ -941,6 +1080,10 @@ class RouteSetTest < ActiveSupport::TestCase
assert_equal("update", params[:action])
assert_equal("5", params[:id])
+ params = set.recognize_path("/people/5", :method => :patch)
+ assert_equal("update", params[:action])
+ assert_equal("5", params[:id])
+
params = set.recognize_path("/people/5", :method => :delete)
assert_equal("destroy", params[:action])
assert_equal("5", params[:id])
@@ -967,7 +1110,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_typo_recognition
set.draw do
- match 'articles/:year/:month/:day/:title' => 'articles#permalink',
+ get 'articles/:year/:month/:day/:title' => 'articles#permalink',
:year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/
end
@@ -982,7 +1125,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_routing_traversal_does_not_load_extra_classes
assert !Object.const_defined?("Profiler__"), "Profiler should not be loaded"
set.draw do
- match '/profile' => 'profile#index'
+ get '/profile' => 'profile#index'
end
set.recognize_path("/profile") rescue nil
@@ -994,6 +1137,7 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
get "people/:id" => "people#show", :as => "person"
put "people/:id" => "people#update"
+ patch "people/:id" => "people#update"
get "people/:id(.:format)" => "people#show"
end
@@ -1004,6 +1148,9 @@ class RouteSetTest < ActiveSupport::TestCase
params = set.recognize_path("/people/5", :method => :put)
assert_equal("update", params[:action])
+ params = set.recognize_path("/people/5", :method => :patch)
+ assert_equal("update", params[:action])
+
params = set.recognize_path("/people/5.png", :method => :get)
assert_equal("show", params[:action])
assert_equal("5", params[:id])
@@ -1012,8 +1159,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_default_action
set.draw do
- match "/people", :controller => "people", :action => "index"
- match "/people/list", :controller => "people", :action => "list"
+ get "/people", :controller => "people", :action => "index"
+ get "/people/list", :controller => "people", :action => "list"
end
url = url_for(set, { :controller => "people", :action => "list" })
@@ -1032,7 +1179,7 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
namespace 'api' do
- match 'inventory' => 'products#inventory'
+ get 'inventory' => 'products#inventory'
end
end
@@ -1057,7 +1204,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_namespace_with_path_prefix
set.draw do
scope :module => "api", :path => "prefix" do
- match 'inventory' => 'products#inventory'
+ get 'inventory' => 'products#inventory'
end
end
@@ -1069,7 +1216,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_namespace_with_blank_path_prefix
set.draw do
scope :module => "api", :path => "" do
- match 'inventory' => 'products#inventory'
+ get 'inventory' => 'products#inventory'
end
end
@@ -1079,7 +1226,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_generate_changes_controller_module
- set.draw { match ':controller/:action/:id' }
+ set.draw { get ':controller/:action/:id' }
current = { :controller => "bling/bloop", :action => "bap", :id => 9 }
assert_equal "/foo/bar/baz/7",
@@ -1088,7 +1235,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_id_is_sticky_when_it_ought_to_be
set.draw do
- match ':controller/:id/:action'
+ get ':controller/:id/:action'
end
url = url_for(set, { :action => "destroy" }, { :controller => "people", :action => "show", :id => "7" })
@@ -1097,8 +1244,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_use_static_path_when_possible
set.draw do
- match 'about' => "welcome#about"
- match ':controller/:action/:id'
+ get 'about' => "welcome#about"
+ get ':controller/:action/:id'
end
url = url_for(set, { :controller => "welcome", :action => "about" },
@@ -1108,7 +1255,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_generate
- set.draw { match ':controller/:action/:id' }
+ set.draw { get ':controller/:action/:id' }
args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" }
assert_equal "/foo/bar/7?x=y", url_for(set, args)
@@ -1119,7 +1266,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_path_prefix
set.draw do
scope "my" do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
end
@@ -1130,7 +1277,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_blank_path_prefix
set.draw do
scope "" do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
end
@@ -1140,9 +1287,9 @@ class RouteSetTest < ActiveSupport::TestCase
def test_named_routes_are_never_relative_to_modules
set.draw do
- match "/connection/manage(/:action)" => 'connection/manage#index'
- match "/connection/connection" => "connection/connection#index"
- match '/connection' => 'connection#index', :as => 'family_connection'
+ get "/connection/manage(/:action)" => 'connection/manage#index'
+ get "/connection/connection" => "connection/connection#index"
+ get '/connection' => 'connection#index', :as => 'family_connection'
end
url = url_for(set, { :controller => "connection" }, { :controller => 'connection/manage' })
@@ -1154,7 +1301,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_action_left_off_when_id_is_recalled
set.draw do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
assert_equal '/books', url_for(set,
{:controller => 'books', :action => 'index'},
@@ -1164,8 +1311,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_query_params_will_be_shown_when_recalled
set.draw do
- match 'show_weblog/:parameter' => 'weblog#show'
- match ':controller(/:action(/:id))'
+ get 'show_weblog/:parameter' => 'weblog#show'
+ get ':controller(/:action(/:id))'
end
assert_equal '/weblog/edit?parameter=1', url_for(set,
{:action => 'edit', :parameter => 1},
@@ -1175,7 +1322,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_format_is_not_inherit
set.draw do
- match '/posts(.:format)' => 'posts#index'
+ get '/posts(.:format)' => 'posts#index'
end
assert_equal '/posts', url_for(set,
@@ -1190,7 +1337,7 @@ class RouteSetTest < ActiveSupport::TestCase
end
def test_expiry_determination_should_consider_values_with_to_param
- set.draw { match 'projects/:project_id/:controller/:action' }
+ set.draw { get 'projects/:project_id/:controller/:action' }
assert_equal '/projects/1/weblog/show', url_for(set,
{ :action => 'show', :project_id => 1 },
{ :controller => 'weblog', :action => 'show', :project_id => '1' })
@@ -1200,7 +1347,7 @@ class RouteSetTest < ActiveSupport::TestCase
set.draw do
resources :projects do
member do
- match 'milestones' => 'milestones#index', :as => 'milestones'
+ get 'milestones' => 'milestones#index', :as => 'milestones'
end
end
end
@@ -1233,7 +1380,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_constraints_with_unsupported_regexp_options_must_error
assert_raise ArgumentError do
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => { :name => /(david|jamis)/m }
end
end
@@ -1242,13 +1389,13 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_constraints_with_supported_options_must_not_error
assert_nothing_raised do
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => { :name => /(david|jamis)/i }
end
end
assert_nothing_raised do
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => { :name => / # Desperately overcommented regexp
( #Either
david #The Creator
@@ -1259,9 +1406,22 @@ class RouteSetTest < ActiveSupport::TestCase
end
end
+ def test_route_with_subdomain_and_constraints_must_receive_params
+ name_param = nil
+ set.draw do
+ get 'page/:name' => 'pages#show', :constraints => lambda {|request|
+ name_param = request.params[:name]
+ return true
+ }
+ end
+ assert_equal({:controller => 'pages', :action => 'show', :name => 'mypage'},
+ set.recognize_path('http://subdomain.example.org/page/mypage'))
+ assert_equal(name_param, 'mypage')
+ end
+
def test_route_requirement_recognize_with_ignore_case
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => {:name => /(david|jamis)/i}
end
assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis'))
@@ -1273,7 +1433,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_requirement_generate_with_ignore_case
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => {:name => /(david|jamis)/i}
end
@@ -1288,7 +1448,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_requirement_recognize_with_extended_syntax
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => {:name => / # Desperately overcommented regexp
( #Either
david #The Creator
@@ -1308,7 +1468,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_route_requirement_with_xi_modifiers
set.draw do
- match 'page/:name' => 'pages#show',
+ get 'page/:name' => 'pages#show',
:constraints => {:name => / # Desperately overcommented regexp
( #Either
david #The Creator
@@ -1326,8 +1486,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_routes_with_symbols
set.draw do
- match 'unnamed', :controller => :pages, :action => :show, :name => :as_symbol
- match 'named' , :controller => :pages, :action => :show, :name => :as_symbol, :as => :named
+ get 'unnamed', :controller => :pages, :action => :show, :name => :as_symbol
+ get 'named' , :controller => :pages, :action => :show, :name => :as_symbol, :as => :named
end
assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/unnamed'))
assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/named'))
@@ -1335,8 +1495,8 @@ class RouteSetTest < ActiveSupport::TestCase
def test_regexp_chunk_should_add_question_mark_for_optionals
set.draw do
- match '/' => 'foo#index'
- match '/hello' => 'bar#index'
+ get '/' => 'foo#index'
+ get '/hello' => 'bar#index'
end
assert_equal '/', url_for(set, { :controller => 'foo' })
@@ -1348,7 +1508,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_assign_route_options_with_anchor_chars
set.draw do
- match '/cars/:action/:person/:car/', :controller => 'cars'
+ get '/cars/:action/:person/:car/', :controller => 'cars'
end
assert_equal '/cars/buy/1/2', url_for(set, { :controller => 'cars', :action => 'buy', :person => '1', :car => '2' })
@@ -1358,7 +1518,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_segmentation_of_dot_path
set.draw do
- match '/books/:action.rss', :controller => 'books'
+ get '/books/:action.rss', :controller => 'books'
end
assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list' })
@@ -1368,7 +1528,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_segmentation_of_dynamic_dot_path
set.draw do
- match '/books(/:action(.:format))', :controller => 'books'
+ get '/books(/:action(.:format))', :controller => 'books'
end
assert_equal '/books/list.rss', url_for(set, { :controller => 'books', :action => 'list', :format => 'rss' })
@@ -1384,7 +1544,7 @@ class RouteSetTest < ActiveSupport::TestCase
def test_slashes_are_implied
@set = nil
- set.draw { match("/:controller(/:action(/:id))") }
+ set.draw { get("/:controller(/:action(/:id))") }
assert_equal '/content', url_for(set, { :controller => 'content', :action => 'index' })
assert_equal '/content/list', url_for(set, { :controller => 'content', :action => 'list' })
@@ -1469,13 +1629,13 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_default_params
set.draw do
- match 'dummy/page/:page' => 'dummy#show'
- match 'dummy/dots/page.:page' => 'dummy#dots'
- match 'ibocorp(/:page)' => 'ibocorp#show',
+ get 'dummy/page/:page' => 'dummy#show'
+ get 'dummy/dots/page.:page' => 'dummy#dots'
+ get 'ibocorp(/:page)' => 'ibocorp#show',
:constraints => { :page => /\d+/ },
:defaults => { :page => 1 }
- match ':controller/:action/:id'
+ get ':controller/:action/:id'
end
assert_equal '/ibocorp', url_for(set, { :controller => 'ibocorp', :action => "show", :page => 1 })
@@ -1483,17 +1643,17 @@ class RouteSetTest < ActiveSupport::TestCase
def test_generate_with_optional_params_recalls_last_request
set.draw do
- match "blog/", :controller => "blog", :action => "index"
+ get "blog/", :controller => "blog", :action => "index"
- match "blog(/:year(/:month(/:day)))",
+ get "blog(/:year(/:month(/:day)))",
:controller => "blog",
:action => "show_date",
:constraints => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/ },
:day => nil, :month => nil
- match "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/
- match "blog/:controller/:action(/:id)"
- match "*anything", :controller => "blog", :action => "unknown_request"
+ get "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/
+ get "blog/:controller/:action(/:id)"
+ get "*anything", :controller => "blog", :action => "unknown_request"
end
assert_equal({:controller => "blog", :action => "index"}, set.recognize_path("/blog"))
@@ -1541,7 +1701,7 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
root :to => 'users#index'
end
- match '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
+ get '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
:constraints => {
:year => /(19|20)\d\d/,
:month => /[01]?\d/,
@@ -1550,37 +1710,37 @@ class RackMountIntegrationTests < ActiveSupport::TestCase
:day => nil,
:month => nil
- match 'archive/:year', :controller => 'archive', :action => 'index',
+ get 'archive/:year', :controller => 'archive', :action => 'index',
:defaults => { :year => nil },
:constraints => { :year => /\d{4}/ },
:as => "blog"
resources :people
- match 'legacy/people' => "people#index", :legacy => "true"
+ get 'legacy/people' => "people#index", :legacy => "true"
- match 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
- match 'id_default(/:id)' => "foo#id_default", :id => 1
+ get 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
+ get 'id_default(/:id)' => "foo#id_default", :id => 1
match 'get_or_post' => "foo#get_or_post", :via => [:get, :post]
- match 'optional/:optional' => "posts#index"
- match 'projects/:project_id' => "project#index", :as => "project"
- match 'clients' => "projects#index"
+ get 'optional/:optional' => "posts#index"
+ get 'projects/:project_id' => "project#index", :as => "project"
+ get 'clients' => "projects#index"
- match 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
- match 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
+ get 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
+ get 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
:postalcode => /# Postcode format
\d{5} #Prefix
(-\d{4})? #Suffix
/x
}, :as => "geocode"
- match 'news(.:format)' => "news#index"
+ get 'news(.:format)' => "news#index"
- match 'comment/:id(/:action)' => "comments#show"
- match 'ws/:controller(/:action(/:id))', :ws => true
- match 'account(/:action)' => "account#subscription"
- match 'pages/:page_id/:controller(/:action(/:id))'
- match ':controller/ping', :action => 'ping'
- match ':controller(/:action(/:id))(.:format)'
+ get 'comment/:id(/:action)' => "comments#show"
+ get 'ws/:controller(/:action(/:id))', :ws => true
+ get 'account(/:action)' => "account#subscription"
+ get 'pages/:page_id/:controller(/:action(/:id))'
+ get ':controller/ping', :action => 'ping'
+ match ':controller(/:action(/:id))(.:format)', :via => :all
root :to => "news#index"
}
diff --git a/actionpack/test/controller/runner_test.rb b/actionpack/test/controller/runner_test.rb
index 24c220dcd5..3e9383abb2 100644
--- a/actionpack/test/controller/runner_test.rb
+++ b/actionpack/test/controller/runner_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
require 'action_dispatch/testing/integration'
module ActionDispatch
- class RunnerTest < Test::Unit::TestCase
+ class RunnerTest < ActiveSupport::TestCase
class MyRunner
include Integration::Runner
diff --git a/actionpack/test/controller/selector_test.rb b/actionpack/test/controller/selector_test.rb
index 8ce9e43402..5e302da212 100644
--- a/actionpack/test/controller/selector_test.rb
+++ b/actionpack/test/controller/selector_test.rb
@@ -7,7 +7,7 @@ require 'abstract_unit'
require 'controller/fake_controllers'
require 'action_controller/vendor/html-scanner'
-class SelectorTest < Test::Unit::TestCase
+class SelectorTest < ActiveSupport::TestCase
#
# Basic selector: element, id, class, attributes.
#
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 8f885ff28e..6fc3556e31 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -23,10 +23,6 @@ class SendFileController < ActionController::Base
def data
send_data(file_data, options)
end
-
- def multibyte_text_data
- send_data("Кирилица\n祝您好運.", options)
- end
end
class SendFileTest < ActionController::TestCase
@@ -61,7 +57,7 @@ class SendFileTest < ActionController::TestCase
require 'stringio'
output = StringIO.new
output.binmode
- output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding)
+ output.string.force_encoding(file_data.encoding)
assert_nothing_raised { response.body_parts.each { |part| output << part.to_s } }
assert_equal file_data, output.string
end
@@ -158,6 +154,17 @@ class SendFileTest < ActionController::TestCase
end
end
+ def test_send_file_with_default_content_disposition_header
+ process('data')
+ assert_equal 'attachment', @controller.headers['Content-Disposition']
+ end
+
+ def test_send_file_without_content_disposition_header
+ @controller.options = {:disposition => nil}
+ process('data')
+ assert_nil @controller.headers['Content-Disposition']
+ end
+
%w(file data).each do |method|
define_method "test_send_#{method}_status" do
@controller.options = { :stream => false, :status => 500 }
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
new file mode 100644
index 0000000000..13ab19ed8f
--- /dev/null
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -0,0 +1,71 @@
+require 'abstract_unit'
+
+module ShowExceptions
+ class ShowExceptionsController < ActionController::Base
+ use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")
+ use ActionDispatch::DebugExceptions
+
+ before_filter :only => :another_boom do
+ request.env["action_dispatch.show_detailed_exceptions"] = true
+ end
+
+ def boom
+ raise 'boom!'
+ end
+
+ def another_boom
+ raise 'boom!'
+ end
+
+ def show_detailed_exceptions?
+ request.local?
+ end
+ end
+
+ class ShowExceptionsTest < ActionDispatch::IntegrationTest
+ test 'show error page from a remote ip' do
+ @app = ShowExceptionsController.action(:boom)
+ self.remote_addr = '208.77.188.166'
+ get '/'
+ assert_equal "500 error fixture\n", body
+ end
+
+ test 'show diagnostics from a local ip if show_detailed_exceptions? is set to request.local?' do
+ @app = ShowExceptionsController.action(:boom)
+ ['127.0.0.1', '127.0.0.127', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
+ self.remote_addr = ip_address
+ get '/'
+ assert_match(/boom/, body)
+ end
+ end
+
+ test 'show diagnostics from a remote ip when env is already set' do
+ @app = ShowExceptionsController.action(:another_boom)
+ self.remote_addr = '208.77.188.166'
+ get '/'
+ assert_match(/boom/, body)
+ end
+ end
+
+ class ShowExceptionsOverridenController < ShowExceptionsController
+ private
+
+ def show_detailed_exceptions?
+ params['detailed'] == '1'
+ end
+ end
+
+ class ShowExceptionsOverridenTest < ActionDispatch::IntegrationTest
+ test 'show error page' do
+ @app = ShowExceptionsOverridenController.action(:boom)
+ get '/', {'detailed' => '0'}
+ assert_equal "500 error fixture\n", body
+ end
+
+ test 'show diagnostics message' do
+ @app = ShowExceptionsOverridenController.action(:boom)
+ get '/', {'detailed' => '1'}
+ assert_match(/boom/, body)
+ end
+ end
+end
diff --git a/actionpack/test/controller/sweeper_test.rb b/actionpack/test/controller/sweeper_test.rb
new file mode 100644
index 0000000000..0561efc62f
--- /dev/null
+++ b/actionpack/test/controller/sweeper_test.rb
@@ -0,0 +1,16 @@
+require 'abstract_unit'
+
+
+class SweeperTest < ActionController::TestCase
+
+ class ::AppSweeper < ActionController::Caching::Sweeper; end
+
+ def test_sweeper_should_not_ignore_unknown_method_calls
+ sweeper = ActionController::Caching::Sweeper.send(:new)
+ assert_raise NameError do
+ sweeper.instance_eval do
+ some_method_that_doesnt_exist
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_case_test.rb
index b64e275363..0d6d303b51 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_case_test.rb
@@ -1,8 +1,7 @@
require 'abstract_unit'
require 'controller/fake_controllers'
-require 'active_support/ordered_hash'
-class TestTest < ActionController::TestCase
+class TestCaseTest < ActionController::TestCase
class TestController < ActionController::Base
def no_op
render :text => 'dummy'
@@ -46,6 +45,10 @@ class TestTest < ActionController::TestCase
render :text => request.fullpath
end
+ def test_format
+ render :text => request.format
+ end
+
def test_query_string
render :text => request.query_string
end
@@ -120,13 +123,11 @@ XML
def test_assigns
@foo = "foo"
+ @foo_hash = {:foo => :bar}
render :nothing => true
end
private
- def rescue_action(e)
- raise e
- end
def generate_url(opts)
url_for(opts.merge(:action => "test_uri"))
@@ -141,7 +142,7 @@ XML
@request.env['PATH_INFO'] = nil
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
end
end
@@ -158,32 +159,56 @@ XML
end
def test_raw_post_handling
- params = ActiveSupport::OrderedHash[:page, {:name => 'page name'}, 'some key', 123]
+ params = Hash[:page, {:name => 'page name'}, 'some key', 123]
post :render_raw_post, params.dup
assert_equal params.to_query, @response.body
end
def test_body_stream
- params = ActiveSupport::OrderedHash[:page, { :name => 'page name' }, 'some key', 123]
+ params = Hash[:page, { :name => 'page name' }, 'some key', 123]
post :render_body, params.dup
assert_equal params.to_query, @response.body
end
+ def test_document_body_and_params_with_post
+ post :test_params, :id => 1
+ assert_equal("{\"id\"=>\"1\", \"controller\"=>\"test_case_test/test\", \"action\"=>\"test_params\"}", @response.body)
+ end
+
+ def test_document_body_with_post
+ post :render_body, "document body"
+ assert_equal "document body", @response.body
+ end
+
+ def test_document_body_with_put
+ put :render_body, "document body"
+ assert_equal "document body", @response.body
+ end
+
+ def test_head
+ head :test_params
+ assert_equal 200, @response.status
+ end
+
+ def test_head_params_as_sting
+ assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 }
+ end
+
def test_process_without_flash
process :set_flash
assert_equal '><', flash['test']
end
def test_process_with_flash
- process :set_flash, nil, nil, { "test" => "value" }
+ process :set_flash, "GET", nil, nil, { "test" => "value" }
assert_equal '>value<', flash['test']
end
def test_process_with_flash_now
- process :set_flash_now, nil, nil, { "test_now" => "value_now" }
+ process :set_flash_now, "GET", nil, nil, { "test_now" => "value_now" }
assert_equal '>value_now<', flash['test_now']
end
@@ -196,13 +221,33 @@ XML
end
def test_process_with_session_arg
- process :no_op, nil, { 'string' => 'value1', :symbol => 'value2' }
+ process :no_op, "GET", nil, { 'string' => 'value1', :symbol => 'value2' }
assert_equal 'value1', session['string']
assert_equal 'value1', session[:string]
assert_equal 'value2', session['symbol']
assert_equal 'value2', session[:symbol]
end
+ def test_process_merges_session_arg
+ session[:foo] = 'bar'
+ get :no_op, nil, { :bar => 'baz' }
+ assert_equal 'bar', session[:foo]
+ assert_equal 'baz', session[:bar]
+ end
+
+ def test_merged_session_arg_is_retained_across_requests
+ get :no_op, nil, { :foo => 'bar' }
+ assert_equal 'bar', session[:foo]
+ get :no_op
+ assert_equal 'bar', session[:foo]
+ end
+
+ def test_process_overwrites_existing_session_arg
+ session[:foo] = 'bar'
+ get :no_op, nil, { :foo => 'baz' }
+ assert_equal 'baz', session[:foo]
+ end
+
def test_session_is_cleared_from_controller_after_reset_session
process :set_session
process :reset_the_session
@@ -223,22 +268,29 @@ XML
def test_process_with_request_uri_with_no_params
process :test_uri
- assert_equal "/test_test/test/test_uri", @response.body
+ assert_equal "/test_case_test/test/test_uri", @response.body
end
def test_process_with_request_uri_with_params
- process :test_uri, :id => 7
- assert_equal "/test_test/test/test_uri/7", @response.body
+ process :test_uri, "GET", :id => 7
+ assert_equal "/test_case_test/test/test_uri/7", @response.body
+ end
+
+ def test_process_with_old_api
+ assert_deprecated do
+ process :test_uri, :id => 7
+ assert_equal "/test_case_test/test/test_uri/7", @response.body
+ end
end
def test_process_with_request_uri_with_params_with_explicit_uri
@request.env['PATH_INFO'] = "/explicit/uri"
- process :test_uri, :id => 7
+ process :test_uri, "GET", :id => 7
assert_equal "/explicit/uri", @response.body
end
def test_process_with_query_string
- process :test_query_string, :q => 'test'
+ process :test_query_string, "GET", :q => 'test'
assert_equal "q=test", @response.body
end
@@ -250,9 +302,9 @@ XML
end
def test_multiple_calls
- process :test_only_one_param, :left => true
+ process :test_only_one_param, "GET", :left => true
assert_equal "OK", @response.body
- process :test_only_one_param, :right => true
+ process :test_only_one_param, "GET", :right => true
assert_equal "OK", @response.body
end
@@ -265,6 +317,10 @@ XML
assert_equal "foo", assigns("foo")
assert_equal "foo", assigns[:foo]
assert_equal "foo", assigns["foo"]
+
+ # but the assigned variable should not have its own keys stringified
+ expected_hash = { :foo => :bar }
+ assert_equal expected_hash, assigns(:foo_hash)
end
def test_view_assigns
@@ -492,7 +548,7 @@ XML
with_routing do |set|
set.draw do
namespace :admin do
- match 'user' => 'user#index'
+ get 'user' => 'user#index'
end
end
@@ -502,7 +558,7 @@ XML
def test_assert_routing_with_glob
with_routing do |set|
- set.draw { match('*path' => "pages#show") }
+ set.draw { get('*path' => "pages#show") }
assert_routing('/company/about', { :controller => 'pages', :action => 'show', :path => 'company/about' })
end
end
@@ -511,7 +567,7 @@ XML
get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'}
parsed_params = eval(@response.body)
assert_equal(
- {'controller' => 'test_test/test', 'action' => 'test_params',
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
parsed_params
)
@@ -521,20 +577,40 @@ XML
get :test_params, :page => {:name => "Page name", :month => 4, :year => 2004, :day => 6}
parsed_params = eval(@response.body)
assert_equal(
- {'controller' => 'test_test/test', 'action' => 'test_params',
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
'page' => {'name' => "Page name", 'month' => '4', 'year' => '2004', 'day' => '6'}},
parsed_params
)
end
+ def test_params_passing_with_fixnums_when_not_html_request
+ get :test_params, :format => 'json', :count => 999
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'format' => 'json', 'count' => 999 },
+ parsed_params
+ )
+ end
+
+ def test_params_passing_path_parameter_is_string_when_not_html_request
+ get :test_params, :format => 'json', :id => 1
+ parsed_params = eval(@response.body)
+ assert_equal(
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'format' => 'json', 'id' => '1' },
+ parsed_params
+ )
+ end
+
def test_params_passing_with_frozen_values
assert_nothing_raised do
- get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze
+ get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze, :deepfreeze => { :frozen => 'icy'.freeze }.freeze
end
parsed_params = eval(@response.body)
assert_equal(
- {'controller' => 'test_test/test', 'action' => 'test_params',
- 'frozen' => 'icy', 'frozens' => ['icy']},
+ {'controller' => 'test_case_test/test', 'action' => 'test_params',
+ 'frozen' => 'icy', 'frozens' => ['icy'], 'deepfreeze' => { 'frozen' => 'icy' }},
parsed_params
)
end
@@ -553,8 +629,8 @@ XML
def test_array_path_parameter_handled_properly
with_routing do |set|
set.draw do
- match 'file/*path', :to => 'test_test/test#test_params'
- match ':controller/:action'
+ get 'file/*path', :to => 'test_case_test/test#test_params'
+ get ':controller/:action'
end
get :test_params, :path => ['hello', 'world']
@@ -635,6 +711,20 @@ XML
assert_equal "http://", @response.body
end
+ def test_request_format
+ get :test_format, :format => 'html'
+ assert_equal 'text/html', @response.body
+
+ get :test_format, :format => 'json'
+ assert_equal 'application/json', @response.body
+
+ get :test_format, :format => 'xml'
+ assert_equal 'application/xml', @response.body
+
+ get :test_format
+ assert_equal 'text/html', @response.body
+ end
+
def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set
cookies['foo'] = 'bar'
get :no_op
@@ -665,20 +755,15 @@ XML
FILES_DIR = File.dirname(__FILE__) + '/../fixtures/multipart'
- if RUBY_VERSION < '1.9'
- READ_BINARY = 'rb'
- READ_PLAIN = 'r'
- else
- READ_BINARY = 'rb:binary'
- READ_PLAIN = 'r:binary'
- end
+ READ_BINARY = 'rb:binary'
+ READ_PLAIN = 'r:binary'
def test_test_uploaded_file
filename = 'mona_lisa.jpg'
path = "#{FILES_DIR}/#{filename}"
content_type = 'image/png'
expected = File.read(path)
- expected.force_encoding(Encoding::BINARY) if expected.respond_to?(:force_encoding)
+ expected.force_encoding(Encoding::BINARY)
file = Rack::Test::UploadedFile.new(path, content_type)
assert_equal filename, file.original_filename
@@ -693,7 +778,7 @@ XML
end
def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case
- TestTest.stubs(:fixture_path).returns(FILES_DIR)
+ TestCaseTest.stubs(:fixture_path).returns(FILES_DIR)
uploaded_file = fixture_file_upload('/mona_lisa.jpg', 'image/png')
assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read
@@ -801,3 +886,24 @@ class NamedRoutesControllerTest < ActionController::TestCase
end
end
end
+
+class AnonymousControllerTest < ActionController::TestCase
+ def setup
+ @controller = Class.new(ActionController::Base) do
+ def index
+ render :text => params[:controller]
+ end
+ end.new
+
+ @routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
+ r.draw do
+ get ':controller(/:action(/:id))'
+ end
+ end
+ end
+
+ def test_controller_name
+ get :index
+ assert_equal 'anonymous', @response.body
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/url_for_integration_test.rb b/actionpack/test/controller/url_for_integration_test.rb
index 7b734ff0fb..6c2311e7a5 100644
--- a/actionpack/test/controller/url_for_integration_test.rb
+++ b/actionpack/test/controller/url_for_integration_test.rb
@@ -3,12 +3,6 @@ require 'abstract_unit'
require 'controller/fake_controllers'
require 'active_support/core_ext/object/with_options'
-module RoutingTestHelpers
- def url_for(set, options, recall = nil)
- set.send(:url_for, options.merge(:only_path => true, :_path_segments => recall))
- end
-end
-
module ActionPack
class URLForIntegrationTest < ActiveSupport::TestCase
include RoutingTestHelpers
@@ -24,7 +18,7 @@ module ActionPack
root :to => 'users#index'
end
- match '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
+ get '/blog(/:year(/:month(/:day)))' => 'posts#show_date',
:constraints => {
:year => /(19|20)\d\d/,
:month => /[01]?\d/,
@@ -33,7 +27,7 @@ module ActionPack
:day => nil,
:month => nil
- match 'archive/:year', :controller => 'archive', :action => 'index',
+ get 'archive/:year', :controller => 'archive', :action => 'index',
:defaults => { :year => nil },
:constraints => { :year => /\d{4}/ },
:as => "blog"
@@ -41,29 +35,29 @@ module ActionPack
resources :people
#match 'legacy/people' => "people#index", :legacy => "true"
- match 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
- match 'id_default(/:id)' => "foo#id_default", :id => 1
+ get 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol
+ get 'id_default(/:id)' => "foo#id_default", :id => 1
match 'get_or_post' => "foo#get_or_post", :via => [:get, :post]
- match 'optional/:optional' => "posts#index"
- match 'projects/:project_id' => "project#index", :as => "project"
- match 'clients' => "projects#index"
+ get 'optional/:optional' => "posts#index"
+ get 'projects/:project_id' => "project#index", :as => "project"
+ get 'clients' => "projects#index"
- match 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
- match 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
+ get 'ignorecase/geocode/:postalcode' => 'geocode#show', :postalcode => /hx\d\d-\d[a-z]{2}/i
+ get 'extended/geocode/:postalcode' => 'geocode#show',:constraints => {
:postalcode => /# Postcode format
\d{5} #Prefix
(-\d{4})? #Suffix
/x
}, :as => "geocode"
- match 'news(.:format)' => "news#index"
+ get 'news(.:format)' => "news#index"
- match 'comment/:id(/:action)' => "comments#show"
- match 'ws/:controller(/:action(/:id))', :ws => true
- match 'account(/:action)' => "account#subscription"
- match 'pages/:page_id/:controller(/:action(/:id))'
- match ':controller/ping', :action => 'ping'
- match ':controller(/:action(/:id))(.:format)'
+ get 'comment/:id(/:action)' => "comments#show"
+ get 'ws/:controller(/:action(/:id))', :ws => true
+ get 'account(/:action)' => "account#subscription"
+ get 'pages/:page_id/:controller(/:action(/:id))'
+ get ':controller/ping', :action => 'ping'
+ get ':controller(/:action(/:id))(.:format)'
root :to => "news#index"
}
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 11ced2df2a..b2cb5f80d5 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -5,7 +5,7 @@ module AbstractController
class UrlForTests < ActionController::TestCase
class W
- include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { match ':controller(/:action(/:id(.:format)))' } }.url_helpers
+ include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { get ':controller(/:action(/:id(.:format)))' } }.url_helpers
end
def teardown
@@ -16,6 +16,14 @@ module AbstractController
W.default_url_options[:host] = 'www.basecamphq.com'
end
+ def add_port!
+ W.default_url_options[:port] = 3000
+ end
+
+ def add_numeric_host!
+ W.default_url_options[:host] = '127.0.0.1'
+ end
+
def test_exception_is_thrown_without_host
assert_raise ArgumentError do
W.new.url_for :controller => 'c', :action => 'a', :id => 'i'
@@ -67,6 +75,14 @@ module AbstractController
)
end
+ def test_subdomain_may_be_object
+ model = mock(:to_param => 'api')
+ add_host!
+ assert_equal('http://api.basecamphq.com/c/a/i',
+ W.new.url_for(:subdomain => model, :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
def test_subdomain_may_be_removed
add_host!
assert_equal('http://basecamphq.com/c/a/i',
@@ -81,6 +97,13 @@ module AbstractController
)
end
+ def test_subdomain_may_be_accepted_with_numeric_host
+ add_numeric_host!
+ assert_equal('http://127.0.0.1/c/a/i',
+ W.new.url_for(:subdomain => 'api', :controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
def test_domain_may_be_changed
add_host!
assert_equal('http://www.37signals.com/c/a/i',
@@ -102,6 +125,14 @@ module AbstractController
)
end
+ def test_default_port
+ add_host!
+ add_port!
+ assert_equal('http://www.basecamphq.com:3000/c/a/i',
+ W.new.url_for(:controller => 'c', :action => 'a', :id => 'i')
+ )
+ end
+
def test_protocol
add_host!
assert_equal('https://www.basecamphq.com/c/a/i',
@@ -179,8 +210,8 @@ module AbstractController
def test_named_routes
with_routing do |set|
set.draw do
- match 'this/is/verbose', :to => 'home#index', :as => :no_args
- match 'home/sweet/home/:user', :to => 'home#index', :as => :home
+ get 'this/is/verbose', :to => 'home#index', :as => :no_args
+ get 'home/sweet/home/:user', :to => 'home#index', :as => :home
end
# We need to create a new class in order to install the new named route.
@@ -200,7 +231,7 @@ module AbstractController
def test_relative_url_root_is_respected_for_named_routes
with_routing do |set|
set.draw do
- match '/home/sweet/home/:user', :to => 'home#index', :as => :home
+ get '/home/sweet/home/:user', :to => 'home#index', :as => :home
end
kls = Class.new { include set.url_helpers }
@@ -214,8 +245,8 @@ module AbstractController
def test_only_path
with_routing do |set|
set.draw do
- match 'home/sweet/home/:user', :to => 'home#index', :as => :home
- match ':controller/:action/:id'
+ get 'home/sweet/home/:user', :to => 'home#index', :as => :home
+ get ':controller/:action/:id'
end
# We need to create a new class in order to install the new named route.
@@ -282,8 +313,8 @@ module AbstractController
def test_named_routes_with_nil_keys
with_routing do |set|
set.draw do
- match 'posts.:format', :to => 'posts#index', :as => :posts
- match '/', :to => 'posts#index', :as => :main
+ get 'posts.:format', :to => 'posts#index', :as => :posts
+ get '/', :to => 'posts#index', :as => :main
end
# We need to create a new class in order to install the new named route.
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index f88903b10e..cc3706aeee 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -21,7 +21,7 @@ class UrlRewriterTests < ActionController::TestCase
@rewriter = Rewriter.new(@request) #.new(@request, @params)
@routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
r.draw do
- match ':controller(/:action(/:id))'
+ get ':controller(/:action(/:id))'
end
end
end
diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb
index f5ac886c20..40f6dc6f0f 100644
--- a/actionpack/test/controller/view_paths_test.rb
+++ b/actionpack/test/controller/view_paths_test.rb
@@ -3,7 +3,6 @@ require 'abstract_unit'
class ViewLoadPathsTest < ActionController::TestCase
class TestController < ActionController::Base
def self.controller_path() "test" end
- def rescue_action(e) raise end
before_filter :add_view_path, :only => :hello_world_at_request_time
@@ -16,22 +15,17 @@ class ViewLoadPathsTest < ActionController::TestCase
end
end
- class Test::SubController < ActionController::Base
- layout 'test/sub'
- def hello_world; render(:template => 'test/hello_world'); end
+ module Test
+ class SubController < ActionController::Base
+ layout 'test/sub'
+ def hello_world; render(:template => 'test/hello_world'); end
+ end
end
def setup
- # TestController.view_paths = nil
-
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
-
@controller = TestController.new
- # Following is needed in order to setup @controller.template object properly
- @controller.send :assign_shortcuts, @request, @response
- @controller.send :initialize_template_class, @response
-
@paths = TestController.view_paths
end
diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb
index ae8588cbb0..c0b9833603 100644
--- a/actionpack/test/controller/webservice_test.rb
+++ b/actionpack/test/controller/webservice_test.rb
@@ -18,8 +18,6 @@ class WebServiceTest < ActionDispatch::IntegrationTest
s << "#{k}#{value}"
end
end
-
- def rescue_action(e) raise end
end
def setup
@@ -256,7 +254,7 @@ class WebServiceTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- match '/', :to => 'web_service_test/test#assign_parameters'
+ match '/', :to => 'web_service_test/test#assign_parameters', :via => :all
end
yield
end
diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb
index eed2eca2ab..f767b07e75 100644
--- a/actionpack/test/dispatch/callbacks_test.rb
+++ b/actionpack/test/dispatch/callbacks_test.rb
@@ -27,6 +27,12 @@ class DispatcherTest < ActiveSupport::TestCase
dispatch
assert_equal 4, Foo.a
assert_equal 4, Foo.b
+
+ dispatch do |env|
+ raise "error"
+ end rescue nil
+ assert_equal 6, Foo.a
+ assert_equal 6, Foo.b
end
def test_to_prepare_and_cleanup_delegation
@@ -44,8 +50,9 @@ class DispatcherTest < ActiveSupport::TestCase
private
def dispatch(&block)
- @dispatcher ||= ActionDispatch::Callbacks.new(block || DummyApp.new)
- @dispatcher.call({'rack.input' => StringIO.new('')})
+ ActionDispatch::Callbacks.new(block || DummyApp.new).call(
+ {'rack.input' => StringIO.new('')}
+ )
end
end
diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb
index 49da448001..2467654a70 100644
--- a/actionpack/test/dispatch/cookies_test.rb
+++ b/actionpack/test/dispatch/cookies_test.rb
@@ -38,6 +38,8 @@ class CookiesTest < ActionController::TestCase
head :ok
end
+ alias delete_cookie logout
+
def delete_cookie_with_path
cookies.delete("user_name", :path => '/beaten')
head :ok
@@ -179,6 +181,18 @@ class CookiesTest < ActionController::TestCase
assert_equal({"user_name" => "david"}, @response.cookies)
end
+ def test_setting_the_same_value_to_cookie
+ request.cookies[:user_name] = 'david'
+ get :authenticate
+ assert response.cookies.empty?
+ end
+
+ def test_setting_the_same_value_to_permanent_cookie
+ request.cookies[:user_name] = 'Jamie'
+ get :set_permanent_cookie
+ assert response.cookies, 'user_name' => 'Jamie'
+ end
+
def test_setting_with_escapable_characters
get :set_with_with_escapable_characters
assert_cookie_header "that+%26+guy=foo+%26+bar+%3D%3E+baz; path=/"
@@ -210,8 +224,8 @@ class CookiesTest < ActionController::TestCase
assert_equal({"user_name" => "david"}, @response.cookies)
end
- def test_setting_cookie_with_secure_in_development
- Rails.env.stubs(:development?).returns(true)
+ def test_setting_cookie_with_secure_when_always_write_cookie_is_true
+ ActionDispatch::Cookies::CookieJar.any_instance.stubs(:always_write_cookie).returns(true)
get :authenticate_with_secure
assert_cookie_header "user_name=david; path=/; secure"
assert_equal({"user_name" => "david"}, @response.cookies)
@@ -235,16 +249,37 @@ class CookiesTest < ActionController::TestCase
end
def test_expiring_cookie
+ request.cookies[:user_name] = 'Joe'
get :logout
assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
assert_equal({"user_name" => nil}, @response.cookies)
end
def test_delete_cookie_with_path
+ request.cookies[:user_name] = 'Joe'
get :delete_cookie_with_path
assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
+ def test_delete_unexisting_cookie
+ request.cookies.clear
+ get :delete_cookie
+ assert @response.cookies.empty?
+ end
+
+ def test_deleted_cookie_predicate
+ cookies[:user_name] = 'Joe'
+ cookies.delete("user_name")
+ assert cookies.deleted?("user_name")
+ assert_equal false, cookies.deleted?("another")
+ end
+
+ def test_deleted_cookie_predicate_with_mismatching_options
+ cookies[:user_name] = 'Joe'
+ cookies.delete("user_name", :path => "/path")
+ assert_equal false, cookies.deleted?("user_name", :path => "/different")
+ end
+
def test_cookies_persist_throughout_request
response = get :authenticate
assert response.headers["Set-Cookie"] =~ /user_name=david/
@@ -273,6 +308,7 @@ class CookiesTest < ActionController::TestCase
end
def test_delete_and_set_cookie
+ request.cookies[:user_name] = 'Joe'
get :delete_and_set_cookie
assert_cookie_header "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT"
assert_equal({"user_name" => "david"}, @response.cookies)
@@ -376,6 +412,7 @@ class CookiesTest < ActionController::TestCase
end
def test_deleting_cookie_with_all_domain_option
+ request.cookies[:user_name] = 'Joe'
get :delete_cookie_with_domain
assert_response :success
assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
@@ -402,6 +439,7 @@ class CookiesTest < ActionController::TestCase
end
def test_deleting_cookie_with_all_domain_option_and_tld_length
+ request.cookies[:user_name] = 'Joe'
get :delete_cookie_with_domain_and_tld
assert_response :success
assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
@@ -430,6 +468,7 @@ class CookiesTest < ActionController::TestCase
def test_deletings_cookie_with_several_preset_domains_using_one_of_these_domains
@request.host = "example2.com"
+ request.cookies[:user_name] = 'Joe'
get :delete_cookie_with_domains
assert_response :success
assert_cookie_header "user_name=; domain=example2.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
@@ -437,19 +476,19 @@ class CookiesTest < ActionController::TestCase
def test_deletings_cookie_with_several_preset_domains_using_other_domain
@request.host = "other-domain.com"
+ request.cookies[:user_name] = 'Joe'
get :delete_cookie_with_domains
assert_response :success
assert_cookie_header "user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"
end
-
def test_cookies_hash_is_indifferent_access
- get :symbol_key
- assert_equal "david", cookies[:user_name]
- assert_equal "david", cookies['user_name']
- get :string_key
- assert_equal "dhh", cookies[:user_name]
- assert_equal "dhh", cookies['user_name']
+ get :symbol_key
+ assert_equal "david", cookies[:user_name]
+ assert_equal "david", cookies['user_name']
+ get :string_key
+ assert_equal "dhh", cookies[:user_name]
+ assert_equal "dhh", cookies['user_name']
end
@@ -565,99 +604,3 @@ 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/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
new file mode 100644
index 0000000000..11c292d61a
--- /dev/null
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -0,0 +1,157 @@
+require 'abstract_unit'
+
+class DebugExceptionsTest < ActionDispatch::IntegrationTest
+
+ class Boomer
+ attr_accessor :closed
+
+ def initialize(detailed = false)
+ @detailed = detailed
+ @closed = false
+ end
+
+ def each
+ end
+
+ def close
+ @closed = true
+ end
+
+ def call(env)
+ env['action_dispatch.show_detailed_exceptions'] = @detailed
+ req = ActionDispatch::Request.new(env)
+ case req.path
+ when "/pass"
+ [404, { "X-Cascade" => "pass" }, self]
+ when "/not_found"
+ raise AbstractController::ActionNotFound
+ when "/runtime_error"
+ raise RuntimeError
+ when "/method_not_allowed"
+ raise ActionController::MethodNotAllowed
+ when "/not_implemented"
+ raise ActionController::NotImplemented
+ when "/unprocessable_entity"
+ raise ActionController::InvalidAuthenticityToken
+ when "/not_found_original_exception"
+ raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
+ else
+ raise "puke!"
+ end
+ end
+ end
+
+ ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false))
+ DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true))
+
+ test 'skip diagnosis if not showing detailed exceptions' do
+ @app = ProductionApp
+ assert_raise RuntimeError do
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ end
+ end
+
+ test 'skip diagnosis if not showing exceptions' do
+ @app = DevelopmentApp
+ assert_raise RuntimeError do
+ get "/", {}, {'action_dispatch.show_exceptions' => false}
+ end
+ end
+
+ test 'raise an exception on cascade pass' do
+ @app = ProductionApp
+ assert_raise ActionController::RoutingError do
+ get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ end
+ end
+
+ test 'closes the response body on cascade pass' do
+ boomer = Boomer.new(false)
+ @app = ActionDispatch::DebugExceptions.new(boomer)
+ assert_raise ActionController::RoutingError do
+ get "/pass", {}, {'action_dispatch.show_exceptions' => true}
+ end
+ assert boomer.closed, "Expected to close the response body"
+ end
+
+ test "rescue with diagnostics message" do
+ @app = DevelopmentApp
+
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 500
+ assert_match(/puke/, body)
+
+ get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_match(/#{AbstractController::ActionNotFound.name}/, body)
+
+ get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 405
+ assert_match(/ActionController::MethodNotAllowed/, body)
+ end
+
+ test "does not show filtered parameters" do
+ @app = DevelopmentApp
+
+ get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.parameter_filter' => [:foo]}
+ assert_response 500
+ assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
+ end
+
+ test "show registered original exception for wrapped exceptions" do
+ @app = DevelopmentApp
+
+ get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 404
+ assert_match(/AbstractController::ActionNotFound/, body)
+ end
+
+ test "show the controller name in the diagnostics template when controller name is present" do
+ @app = DevelopmentApp
+ get("/runtime_error", {}, {
+ 'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.request.parameters' => {
+ 'action' => 'show',
+ 'id' => 'unknown',
+ 'controller' => 'featured_tile'
+ }
+ })
+ assert_response 500
+ assert_match(/RuntimeError\n in FeaturedTileController/, body)
+ end
+
+ test "sets the HTTP charset parameter" do
+ @app = DevelopmentApp
+
+ get "/", {}, {'action_dispatch.show_exceptions' => true}
+ assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
+ end
+
+ test 'uses logger from env' do
+ @app = DevelopmentApp
+ output = StringIO.new
+ get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => Logger.new(output)}
+ assert_match(/puke/, output.rewind && output.read)
+ end
+
+ test 'uses backtrace cleaner from env' do
+ @app = DevelopmentApp
+ cleaner = stub(:clean => ['passed backtrace cleaner'])
+ get "/", {}, {'action_dispatch.show_exceptions' => true, 'action_dispatch.backtrace_cleaner' => cleaner}
+ assert_match(/passed backtrace cleaner/, body)
+ end
+
+ test 'logs exception backtrace when all lines silenced' do
+ output = StringIO.new
+ backtrace_cleaner = ActiveSupport::BacktraceCleaner.new
+ backtrace_cleaner.add_silencer { true }
+
+ env = {'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.logger' => Logger.new(output),
+ 'action_dispatch.backtrace_cleaner' => backtrace_cleaner}
+
+ get "/", {}, env
+ assert_operator((output.rewind && output.read).lines.count, :>, 10)
+ end
+end
diff --git a/actionpack/test/dispatch/header_test.rb b/actionpack/test/dispatch/header_test.rb
index ec6ba494dc..bc7cad8db5 100644
--- a/actionpack/test/dispatch/header_test.rb
+++ b/actionpack/test/dispatch/header_test.rb
@@ -13,4 +13,9 @@ class HeaderTest < ActiveSupport::TestCase
assert_equal "text/plain", @headers["CONTENT_TYPE"]
assert_equal "text/plain", @headers["HTTP_CONTENT_TYPE"]
end
+
+ test "fetch" do
+ assert_equal "text/plain", @headers.fetch("content-type", nil)
+ assert_equal "not found", @headers.fetch('not-found', 'not found')
+ end
end
diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index d3465589c1..bd078d2b21 100644
--- a/actionpack/test/dispatch/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -4,11 +4,12 @@ module ActionDispatch
module Routing
class MapperTest < ActiveSupport::TestCase
class FakeSet
- attr_reader :routes
+ attr_reader :routes, :draw_paths
alias :set :routes
def initialize
@routes = []
+ @draw_paths = []
end
def resources_path_names
@@ -37,7 +38,7 @@ module ActionDispatch
end
def test_mapping_requirements
- options = { :controller => 'foo', :action => 'bar' }
+ options = { :controller => 'foo', :action => 'bar', :via => :get }
m = Mapper::Mapping.new FakeSet.new, {}, '/store/:name(*rest)', options
_, _, requirements, _ = m.to_route
assert_equal(/.+?/, requirements[:rest])
@@ -46,7 +47,7 @@ module ActionDispatch
def test_map_slash
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/', :to => 'posts#index', :as => :main
+ mapper.get '/', :to => 'posts#index', :as => :main
assert_equal '/', fakeset.conditions.first[:path_info]
end
@@ -55,14 +56,14 @@ module ActionDispatch
mapper = Mapper.new fakeset
# FIXME: is this a desired behavior?
- mapper.match '/one/two/', :to => 'posts#index', :as => :main
+ mapper.get '/one/two/', :to => 'posts#index', :as => :main
assert_equal '/one/two(.:format)', fakeset.conditions.first[:path_info]
end
def test_map_wildcard
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/*path', :to => 'pages#show'
+ mapper.get '/*path', :to => 'pages#show'
assert_equal '/*path(.:format)', fakeset.conditions.first[:path_info]
assert_equal(/.+?/, fakeset.requirements.first[:path])
end
@@ -70,7 +71,7 @@ module ActionDispatch
def test_map_wildcard_with_other_element
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/*path/foo/:bar', :to => 'pages#show'
+ mapper.get '/*path/foo/:bar', :to => 'pages#show'
assert_equal '/*path/foo/:bar(.:format)', fakeset.conditions.first[:path_info]
assert_nil fakeset.requirements.first[:path]
end
@@ -78,7 +79,7 @@ module ActionDispatch
def test_map_wildcard_with_multiple_wildcard
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/*foo/*bar', :to => 'pages#show'
+ mapper.get '/*foo/*bar', :to => 'pages#show'
assert_equal '/*foo/*bar(.:format)', fakeset.conditions.first[:path_info]
assert_nil fakeset.requirements.first[:foo]
assert_equal(/.+?/, fakeset.requirements.first[:bar])
@@ -87,7 +88,7 @@ module ActionDispatch
def test_map_wildcard_with_format_false
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/*path', :to => 'pages#show', :format => false
+ mapper.get '/*path', :to => 'pages#show', :format => false
assert_equal '/*path', fakeset.conditions.first[:path_info]
assert_nil fakeset.requirements.first[:path]
end
@@ -95,7 +96,7 @@ module ActionDispatch
def test_map_wildcard_with_format_true
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/*path', :to => 'pages#show', :format => true
+ mapper.get '/*path', :to => 'pages#show', :format => true
assert_equal '/*path.:format', fakeset.conditions.first[:path_info]
end
end
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 831f3db3e2..948a690979 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -45,7 +45,7 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BazMiddleware, @stack.last.klass
assert_equal([true, {:foo => "bar"}], @stack.last.args)
end
-
+
test "use should push middleware class with block arguments onto the stack" do
proc = Proc.new {}
assert_difference "@stack.size" do
@@ -54,7 +54,7 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BlockMiddleware, @stack.last.klass
assert_equal proc, @stack.last.block
end
-
+
test "insert inserts middleware at the integer index" do
@stack.insert(1, BazMiddleware)
assert_equal BazMiddleware, @stack[1].klass
@@ -81,6 +81,17 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BazMiddleware, @stack[0].klass
end
+ test "swaps one middleware out for same middleware class" do
+ assert_equal FooMiddleware, @stack[0].klass
+ @stack.swap(FooMiddleware, FooMiddleware, Proc.new { |env| [500, {}, ['error!']] })
+ assert_equal FooMiddleware, @stack[0].klass
+ end
+
+ test "unshift adds a new middleware at the beginning of the stack" do
+ @stack.unshift :"MiddlewareStackTest::BazMiddleware"
+ assert_equal BazMiddleware, @stack.first.klass
+ end
+
test "raise an error on invalid index" do
assert_raise RuntimeError do
@stack.insert("HiyaMiddleware", BazMiddleware)
diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb
index 08fe2127b9..e3f9faaa64 100644
--- a/actionpack/test/dispatch/mime_type_test.rb
+++ b/actionpack/test/dispatch/mime_type_test.rb
@@ -69,6 +69,18 @@ class MimeTypeTest < ActiveSupport::TestCase
assert_equal expect, Mime::Type.parse(accept)
end
+ test "parse single media range with q" do
+ accept = "text/html;q=0.9"
+ expect = [Mime::HTML]
+ assert_equal expect, Mime::Type.parse(accept)
+ end
+
+ test "parse arbitarry media type parameters" do
+ accept = 'multipart/form-data; boundary="simple boundary"'
+ expect = [Mime::MULTIPART_FORM]
+ assert_equal expect, Mime::Type.parse(accept)
+ end
+
# Accept header send with user HTTP_USER_AGENT: Sunrise/0.42j (Windows XP)
test "parse broken acceptlines" do
accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5"
@@ -95,6 +107,37 @@ class MimeTypeTest < ActiveSupport::TestCase
end
end
+ test "custom type with type aliases" do
+ begin
+ Mime::Type.register "text/foobar", :foobar, ["text/foo", "text/bar"]
+ %w[text/foobar text/foo text/bar].each do |type|
+ assert_equal Mime::FOOBAR, type
+ end
+ ensure
+ Mime::Type.unregister(:FOOBAR)
+ end
+ end
+
+ test "custom type with extension aliases" do
+ begin
+ Mime::Type.register "text/foobar", :foobar, [], [:foo, "bar"]
+ %w[foobar foo bar].each do |extension|
+ assert_equal Mime::FOOBAR, Mime::EXTENSION_LOOKUP[extension]
+ end
+ ensure
+ Mime::Type.unregister(:FOOBAR)
+ end
+ end
+
+ test "register alias" do
+ begin
+ Mime::Type.register_alias "application/xhtml+xml", :foobar
+ assert_equal Mime::HTML, Mime::EXTENSION_LOOKUP['foobar']
+ ensure
+ Mime::Type.unregister(:FOOBAR)
+ end
+ end
+
test "type should be equal to symbol" do
assert_equal Mime::HTML, 'application/xhtml+xml'
assert_equal Mime::HTML, :html
diff --git a/actionpack/test/dispatch/mount_test.rb b/actionpack/test/dispatch/mount_test.rb
index 1a032539b9..536e35ab2e 100644
--- a/actionpack/test/dispatch/mount_test.rb
+++ b/actionpack/test/dispatch/mount_test.rb
@@ -37,6 +37,11 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
assert_equal "/sprockets -- /omg", response.body
end
+ def test_mounting_works_with_nested_script_name
+ get "/foo/sprockets/omg", {}, 'SCRIPT_NAME' => '/foo', 'PATH_INFO' => '/sprockets/omg'
+ assert_equal "/foo/sprockets -- /omg", response.body
+ end
+
def test_mounting_works_with_scope
get "/its_a/sprocket/omg"
assert_equal "/its_a/sprocket -- /omg", response.body
@@ -51,4 +56,4 @@ class TestRoutingMount < ActionDispatch::IntegrationTest
get "/fakeengine"
assert_equal "OK", response.body
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb
index 93eccaecbd..ab2f7612ce 100644
--- a/actionpack/test/dispatch/prefix_generation_test.rb
+++ b/actionpack/test/dispatch/prefix_generation_test.rb
@@ -25,11 +25,12 @@ module TestGenerationPrefix
@routes ||= begin
routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
- match "/posts/:id", :to => "inside_engine_generating#show", :as => :post
- match "/posts", :to => "inside_engine_generating#index", :as => :posts
- match "/url_to_application", :to => "inside_engine_generating#url_to_application"
- match "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine"
- match "/conflicting_url", :to => "inside_engine_generating#conflicting"
+ get "/posts/:id", :to => "inside_engine_generating#show", :as => :post
+ get "/posts", :to => "inside_engine_generating#index", :as => :posts
+ get "/url_to_application", :to => "inside_engine_generating#url_to_application"
+ get "/polymorphic_path_for_engine", :to => "inside_engine_generating#polymorphic_path_for_engine"
+ get "/conflicting_url", :to => "inside_engine_generating#conflicting"
+ get "/foo", :to => "never#invoked", :as => :named_helper_that_should_be_invoked_only_in_respond_to_test
end
routes
@@ -50,12 +51,12 @@ module TestGenerationPrefix
scope "/:omg", :omg => "awesome" do
mount BlogEngine => "/blog", :as => "blog_engine"
end
- match "/posts/:id", :to => "outside_engine_generating#post", :as => :post
- match "/generate", :to => "outside_engine_generating#index"
- match "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app"
- match "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine"
- match "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for"
- match "/conflicting_url", :to => "outside_engine_generating#conflicting"
+ get "/posts/:id", :to => "outside_engine_generating#post", :as => :post
+ get "/generate", :to => "outside_engine_generating#index"
+ get "/polymorphic_path_for_app", :to => "outside_engine_generating#polymorphic_path_for_app"
+ get "/polymorphic_path_for_engine", :to => "outside_engine_generating#polymorphic_path_for_engine"
+ get "/polymorphic_with_url_for", :to => "outside_engine_generating#polymorphic_with_url_for"
+ get "/conflicting_url", :to => "outside_engine_generating#conflicting"
root :to => "outside_engine_generating#index"
end
@@ -152,6 +153,8 @@ module TestGenerationPrefix
RailsApplication.routes.default_url_options = {}
end
+ include BlogEngine.routes.mounted_helpers
+
# Inside Engine
test "[ENGINE] generating engine's url use SCRIPT_NAME from request" do
get "/pure-awesomeness/blog/posts/1"
@@ -219,6 +222,10 @@ module TestGenerationPrefix
end
# Inside any Object
+ test "[OBJECT] proxy route should override respond_to?() as expected" do
+ assert_respond_to blog_engine, :named_helper_that_should_be_invoked_only_in_respond_to_test_path
+ end
+
test "[OBJECT] generating engine's route includes prefix" do
assert_equal "/awesome/blog/posts/1", engine_object.post_path(:id => 1)
end
@@ -275,7 +282,7 @@ module TestGenerationPrefix
@routes ||= begin
routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
- match "/posts/:id", :to => "posts#show", :as => :post
+ get "/posts/:id", :to => "posts#show", :as => :post
end
routes
diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb
index eaabc1feb3..ce9ccfcee8 100644
--- a/actionpack/test/dispatch/reloader_test.rb
+++ b/actionpack/test/dispatch/reloader_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class ReloaderTest < Test::Unit::TestCase
+class ReloaderTest < ActiveSupport::TestCase
Reloader = ActionDispatch::Reloader
def test_prepare_callbacks
@@ -43,6 +43,29 @@ class ReloaderTest < Test::Unit::TestCase
assert_respond_to body, :close
end
+ def test_returned_body_object_always_responds_to_close_even_if_called_twice
+ body = call_and_return_body
+ assert_respond_to body, :close
+ body.close
+
+ body = call_and_return_body
+ assert_respond_to body, :close
+ body.close
+ end
+
+ def test_condition_specifies_when_to_reload
+ i, j = 0, 0, 0, 0
+ Reloader.to_prepare { |*args| i += 1 }
+ Reloader.to_cleanup { |*args| j += 1 }
+ app = Reloader.new(lambda { |env| [200, {}, []] }, lambda { i < 3 })
+ 5.times do
+ resp = app.call({})
+ resp[2].close
+ end
+ assert_equal 3, i
+ assert_equal 3, j
+ end
+
def test_returned_body_object_behaves_like_underlying_object
body = call_and_return_body do
b = MyBody.new
@@ -116,6 +139,15 @@ class ReloaderTest < Test::Unit::TestCase
assert cleaned
end
+ def test_prepend_prepare_callback
+ i = 10
+ Reloader.to_prepare { i += 1 }
+ Reloader.to_prepare(:prepend => true) { i = 0 }
+
+ Reloader.prepare!
+ assert_equal 1, i
+ end
+
def test_cleanup_callbacks_are_called_on_exceptions
cleaned = false
Reloader.to_cleanup { cleaned = true }
@@ -132,7 +164,8 @@ class ReloaderTest < Test::Unit::TestCase
private
def call_and_return_body(&block)
- @reloader ||= Reloader.new(block || proc {[200, {}, 'response']})
+ @response ||= 'response'
+ @reloader ||= Reloader.new(block || proc {[200, {}, @response]})
@reloader.call({'rack.input' => StringIO.new('')})[2]
end
end
diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb
index d854d55173..302bff0696 100644
--- a/actionpack/test/dispatch/request/json_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb
@@ -32,13 +32,21 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
test "logs error if parsing unsuccessful" do
with_test_routing do
+ output = StringIO.new
+ json = "[\"person]\": {\"name\": \"David\"}}"
+ post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output)}
+ assert_response :error
+ output.rewind && err = output.read
+ assert err =~ /Error occurred while parsing request parameters/
+ end
+ end
+
+ test "occurring a parse error if parsing unsuccessful" do
+ with_test_routing do
begin
- $stderr = StringIO.new
+ $stderr = StringIO.new # suppress the log
json = "[\"person]\": {\"name\": \"David\"}}"
- post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true}
- assert_response :error
- $stderr.rewind && err = $stderr.read
- assert err =~ /Error occurred while parsing request parameters/
+ assert_raise(MultiJson::DecodeError) { post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => false} }
ensure
$stderr = STDERR
end
@@ -57,7 +65,7 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match ':action', :to => ::JsonParamsParsingTest::TestController
+ post ':action', :to => ::JsonParamsParsingTest::TestController
end
yield
end
@@ -110,7 +118,7 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing(controller)
with_routing do |set|
set.draw do
- match ':action', :to => controller
+ post ':action', :to => controller
end
yield
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 560ea00923..63c5ea26a6 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -89,7 +89,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
# Rack doesn't handle multipart/mixed for us.
files = params['files']
- files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding)
+ files.force_encoding('ASCII-8BIT')
assert_equal 19756, files.size
end
@@ -144,7 +144,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match ':action', :to => 'multipart_params_parsing_test/test'
+ post ':action', :to => 'multipart_params_parsing_test/test'
end
yield
end
diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb
index f6a1475d04..d14f188e30 100644
--- a/actionpack/test/dispatch/request/query_string_parsing_test.rb
+++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb
@@ -109,7 +109,7 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest
def assert_parses(expected, actual)
with_routing do |set|
set.draw do
- match ':action', :to => ::QueryStringParsingTest::TestController
+ get ':action', :to => ::QueryStringParsingTest::TestController
end
get "/parse", actual
diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb
new file mode 100644
index 0000000000..4d24456ba6
--- /dev/null
+++ b/actionpack/test/dispatch/request/session_test.rb
@@ -0,0 +1,48 @@
+require 'abstract_unit'
+require 'action_dispatch/middleware/session/abstract_store'
+
+module ActionDispatch
+ class Request
+ class SessionTest < ActiveSupport::TestCase
+ def test_create_adds_itself_to_env
+ env = {}
+ s = Session.create(store, env, {})
+ assert_equal s, env[Rack::Session::Abstract::ENV_SESSION_KEY]
+ end
+
+ def test_to_hash
+ env = {}
+ s = Session.create(store, env, {})
+ s['foo'] = 'bar'
+ assert_equal 'bar', s['foo']
+ assert_equal({'foo' => 'bar'}, s.to_hash)
+ end
+
+ def test_create_merges_old
+ env = {}
+ s = Session.create(store, env, {})
+ s['foo'] = 'bar'
+
+ s1 = Session.create(store, env, {})
+ refute_equal s, s1
+ assert_equal 'bar', s1['foo']
+ end
+
+ def test_find
+ env = {}
+ assert_nil Session.find(env)
+
+ s = Session.create(store, env, {})
+ assert_equal s, Session.find(env)
+ end
+
+ private
+ def store
+ Class.new {
+ def load_session(env); [1, {}]; end
+ def session_exists?(env); true; end
+ }.new
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 04a0fb6f34..568e220b15 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -130,7 +130,7 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match ':action', :to => ::UrlEncodedParamsParsingTest::TestController
+ post ':action', :to => ::UrlEncodedParamsParsingTest::TestController
end
yield
end
@@ -146,8 +146,6 @@ class UrlEncodedParamsParsingTest < ActionDispatch::IntegrationTest
end
def assert_utf8(object)
- return unless "ruby".encoding_aware?
-
correct_encoding = Encoding.default_internal
unless object.is_a?(Hash)
diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
index 38453dfe48..84823e2896 100644
--- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb
@@ -41,7 +41,7 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest
test "parses single file" do
with_test_routing do
- xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></person>"
+ xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{::Base64.encode64('ABC')}</avatar></person>"
post "/parse", xml, default_headers
assert_response :ok
@@ -54,13 +54,21 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest
test "logs error if parsing unsuccessful" do
with_test_routing do
+ output = StringIO.new
+ xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{::Base64.encode64('ABC')}</avatar></pineapple>"
+ post "/parse", xml, default_headers.merge('action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output))
+ assert_response :error
+ output.rewind && err = output.read
+ assert err =~ /Error occurred while parsing request parameters/
+ end
+ end
+
+ test "occurring a parse error if parsing unsuccessful" do
+ with_test_routing do
begin
- $stderr = StringIO.new
- xml = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar></pineapple>"
- post "/parse", xml, default_headers.merge('action_dispatch.show_exceptions' => true)
- assert_response :error
- $stderr.rewind && err = $stderr.read
- assert err =~ /Error occurred while parsing request parameters/
+ $stderr = StringIO.new # suppress the log
+ xml = "<person><name>David</name></pineapple>"
+ assert_raise(REXML::ParseException) { post "/parse", xml, default_headers.merge('action_dispatch.show_exceptions' => false) }
ensure
$stderr = STDERR
end
@@ -72,8 +80,8 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest
<person>
<name>David</name>
<avatars>
- <avatar type='file' name='me.jpg' content_type='image/jpg'>#{ActiveSupport::Base64.encode64('ABC')}</avatar>
- <avatar type='file' name='you.gif' content_type='image/gif'>#{ActiveSupport::Base64.encode64('DEF')}</avatar>
+ <avatar type='file' name='me.jpg' content_type='image/jpg'>#{::Base64.encode64('ABC')}</avatar>
+ <avatar type='file' name='you.gif' content_type='image/gif'>#{::Base64.encode64('DEF')}</avatar>
</avatars>
</person>
end_body
@@ -98,7 +106,7 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match ':action', :to => ::XmlParamsParsingTest::TestController
+ post ':action', :to => ::XmlParamsParsingTest::TestController
end
yield
end
@@ -147,7 +155,7 @@ class RootLessXmlParamsParsingTest < ActionDispatch::IntegrationTest
def with_test_routing
with_routing do |set|
set.draw do
- match ':action', :to => ::RootLessXmlParamsParsingTest::TestController
+ post ':action', :to => ::RootLessXmlParamsParsingTest::TestController
end
yield
end
diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb
new file mode 100644
index 0000000000..a8050b4fab
--- /dev/null
+++ b/actionpack/test/dispatch/request_id_test.rb
@@ -0,0 +1,65 @@
+require 'abstract_unit'
+
+class RequestIdTest < ActiveSupport::TestCase
+ test "passing on the request id from the outside" do
+ assert_equal "external-uu-rid", stub_request('HTTP_X_REQUEST_ID' => 'external-uu-rid').uuid
+ end
+
+ test "ensure that only alphanumeric uurids are accepted" do
+ assert_equal "X-Hacked-HeaderStuff", stub_request('HTTP_X_REQUEST_ID' => '; X-Hacked-Header: Stuff').uuid
+ end
+
+ test "ensure that 255 char limit on the request id is being enforced" do
+ assert_equal "X" * 255, stub_request('HTTP_X_REQUEST_ID' => 'X' * 500).uuid
+ end
+
+ test "generating a request id when none is supplied" do
+ assert_match(/\w+-\w+-\w+-\w+-\w+/, stub_request.uuid)
+ end
+
+ private
+
+ def stub_request(env = {})
+ ActionDispatch::RequestId.new(lambda { |environment| [ 200, environment, [] ] }).call(env)
+ ActionDispatch::Request.new(env)
+ end
+end
+
+class RequestIdResponseTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def index
+ head :ok
+ end
+ end
+
+ test "request id is passed all the way to the response" do
+ with_test_route_set do
+ get '/'
+ assert_match(/\w+/, @response.headers["X-Request-Id"])
+ end
+ end
+
+ test "request id given on request is passed all the way to the response" do
+ with_test_route_set do
+ get '/', {}, 'HTTP_X_REQUEST_ID' => 'X' * 500
+ assert_equal "X" * 255, @response.headers["X-Request-Id"]
+ end
+ end
+
+
+ private
+
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ get '/', :to => ::RequestIdResponseTest::TestController.action(:index)
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use ActionDispatch::RequestId
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index a611252b31..94d0e09842 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -35,37 +35,40 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '1.2.3.4', request.remote_ip
request = stub_request 'REMOTE_ADDR' => '1.2.3.4',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
- assert_equal '1.2.3.4', request.remote_ip
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'REMOTE_ADDR' => '127.0.0.1',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
assert_equal '3.4.5.6', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,3.4.5.6'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,unknown'
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.16.0.1,3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => '127.0.0.1,3.4.5.6'
- assert_equal '3.4.5.6', request.remote_ip
+ assert_equal nil, request.remote_ip
request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1'
- assert_equal 'unknown', request.remote_ip
+ assert_equal nil, request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 172.31.4.4'
assert_equal '3.4.5.6', request.remote_ip
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address'
+ assert_equal nil, request.remote_ip
+
request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1',
'HTTP_CLIENT_IP' => '2.2.2.2'
e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) {
@@ -85,34 +88,141 @@ class RequestTest < ActiveSupport::TestCase
:ip_spoofing_check => false
assert_equal '2.2.2.2', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 8.8.8.8'
assert_equal '9.9.9.9', request.remote_ip
end
- test "remote ip with user specified trusted proxies" do
- @trusted_proxies = /^67\.205\.106\.73$/i
+ test "remote ip v6" do
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
- request = stub_request 'REMOTE_ADDR' => '67.205.106.73',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '::1',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1, ::1, fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329',
+ 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) {
+ request.remote_ip
+ }
+ assert_match(/IP spoofing attack/, e.message)
+ assert_match(/HTTP_X_FORWARDED_FOR="fe80:0000:0000:0000:0202:b3ff:fe1e:8329"/, e.message)
+ assert_match(/HTTP_CLIENT_IP="2001:0db8:85a3:0000:0000:8a2e:0370:7334"/, e.message)
+
+ # Turn IP Spoofing detection off.
+ # This is useful for sites that are aimed at non-IP clients. The typical
+ # example is WAP. Since the cellular network is not IP based, it's a
+ # leap of faith to assume that their proxies are ever going to set the
+ # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly.
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329',
+ 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ :ip_spoofing_check => false
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
+ end
+
+ test "remote ip when the remote ip middleware returns nil" do
+ request = stub_request 'REMOTE_ADDR' => '127.0.0.1'
+ assert_equal '127.0.0.1', request.remote_ip
+ end
+
+ test "remote ip with user specified trusted proxies String" do
+ @trusted_proxies = "67.205.106.73"
+
+ request = stub_request 'REMOTE_ADDR' => '3.4.5.6',
+ 'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
assert_equal '3.4.5.6', request.remote_ip
request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ 'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
+ assert_equal '172.16.0.1', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.73,3.4.5.6',
+ 'HTTP_X_FORWARDED_FOR' => '67.205.106.73'
assert_equal '3.4.5.6', request.remote_ip
- request = stub_request 'REMOTE_ADDR' => '67.205.106.73,172.16.0.1',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 67.205.106.73'
assert_equal '3.4.5.6', request.remote_ip
+ end
- request = stub_request 'REMOTE_ADDR' => '67.205.106.74,172.16.0.1',
- 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
- assert_equal '67.205.106.74', request.remote_ip
+ test "remote ip v6 with user specified trusted proxies String" do
+ @trusted_proxies = 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
- request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73'
- assert_equal 'unknown', request.remote_ip
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip
- request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73'
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal nil, request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal nil, request.remote_ip
+ end
+
+ test "remote ip with user specified trusted proxies Regexp" do
+ @trusted_proxies = /^67\.205\.106\.73$/i
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.73',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73, 10.0.0.1, 9.9.9.9, 3.4.5.6'
+ assert_equal nil, request.remote_ip
+ end
+
+ test "remote ip v6 with user specified trusted proxies Regexp" do
+ @trusted_proxies = /^fe80:0000:0000:0000:0202:b3ff:fe1e:8329$/i
+
+ request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334',
+ 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329'
+ assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334'
+ assert_equal nil, request.remote_ip
end
test "domains" do
@@ -309,14 +419,14 @@ class RequestTest < ActiveSupport::TestCase
end
test "String request methods" do
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method.to_s.upcase, request.method
end
end
test "Symbol forms of request methods via method_symbol" do
- [:get, :post, :put, :delete].each do |method|
+ [:get, :post, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase
assert_equal method, request.method_symbol
end
@@ -330,7 +440,7 @@ class RequestTest < ActiveSupport::TestCase
end
test "allow method hacking on post" do
- %w(GET OPTIONS PUT POST DELETE).each do |method|
+ %w(GET OPTIONS PATCH PUT POST DELETE).each do |method|
request = stub_request "REQUEST_METHOD" => method.to_s.upcase
assert_equal(method == "HEAD" ? "GET" : method, request.method)
end
@@ -344,7 +454,7 @@ class RequestTest < ActiveSupport::TestCase
end
test "restrict method hacking" do
- [:get, :put, :delete].each do |method|
+ [:get, :patch, :put, :delete].each do |method|
request = stub_request 'REQUEST_METHOD' => method.to_s.upcase,
'action_dispatch.request.request_parameters' => { :_method => 'put' }
assert_equal method.to_s.upcase, request.method
@@ -359,6 +469,13 @@ class RequestTest < ActiveSupport::TestCase
assert request.head?
end
+ test "post masquerading as patch" do
+ request = stub_request 'REQUEST_METHOD' => 'PATCH', "rack.methodoverride.original_method" => "POST"
+ assert_equal "POST", request.method
+ assert_equal "PATCH", request.request_method
+ assert request.patch?
+ end
+
test "post masquerading as put" do
request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST"
assert_equal "POST", request.method
@@ -613,6 +730,30 @@ class RequestTest < ActiveSupport::TestCase
assert_equal "/authenticate?secret", path
end
+ test "original_fullpath returns ORIGINAL_FULLPATH" do
+ request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar")
+
+ path = request.original_fullpath
+ assert_equal "/foo?bar", path
+ end
+
+ test "original_url returns url built using ORIGINAL_FULLPATH" do
+ request = stub_request('ORIGINAL_FULLPATH' => "/foo?bar",
+ 'HTTP_HOST' => "example.org",
+ 'rack.url_scheme' => "http")
+
+ url = request.original_url
+ assert_equal "http://example.org/foo?bar", url
+ end
+
+ test "original_fullpath returns fullpath if ORIGINAL_FULLPATH is not present" do
+ request = stub_request('PATH_INFO' => "/foo",
+ 'QUERY_STRING' => "bar")
+
+ path = request.original_fullpath
+ assert_equal "/foo?bar", path
+ end
+
protected
def stub_request(env = {})
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 5abbaf74fe..82d1200f8e 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -5,6 +5,12 @@ class ResponseTest < ActiveSupport::TestCase
@response = ActionDispatch::Response.new
end
+ def test_response_body_encoding
+ body = ["hello".encode('utf-8')]
+ response = ActionDispatch::Response.new 200, {}, body
+ assert_equal Encoding::UTF_8, response.body.encoding
+ end
+
test "simple output" do
@response.body = "Hello, World!"
@@ -130,6 +136,17 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal('application/xml; charset=utf-16', resp.headers['Content-Type'])
end
+
+ test "read content type without charset" do
+ original = ActionDispatch::Response.default_charset
+ begin
+ ActionDispatch::Response.default_charset = 'utf-16'
+ resp = ActionDispatch::Response.new(200, { "Content-Type" => "text/xml" })
+ assert_equal('utf-16', resp.charset)
+ ensure
+ ActionDispatch::Response.default_charset = original
+ end
+ end
end
class ResponseIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionpack/test/dispatch/routing_assertions_test.rb b/actionpack/test/dispatch/routing_assertions_test.rb
index 9f95d82129..517354ae58 100644
--- a/actionpack/test/dispatch/routing_assertions_test.rb
+++ b/actionpack/test/dispatch/routing_assertions_test.rb
@@ -3,6 +3,7 @@ require 'controller/fake_controllers'
class SecureArticlesController < ArticlesController; end
class BlockArticlesController < ArticlesController; end
+class QueryArticlesController < ArticlesController; end
class RoutingAssertionsTest < ActionController::TestCase
@@ -18,6 +19,10 @@ class RoutingAssertionsTest < ActionController::TestCase
scope 'block', :constraints => lambda { |r| r.ssl? } do
resources :articles, :controller => 'block_articles'
end
+
+ scope 'query', :constraints => lambda { |r| r.params[:use_query] == 'true' } do
+ resources :articles, :controller => 'query_articles'
+ end
end
end
@@ -42,7 +47,7 @@ class RoutingAssertionsTest < ActionController::TestCase
def test_assert_recognizes_with_extras
assert_recognizes({ :controller => 'articles', :action => 'index', :page => '1' }, '/articles', { :page => '1' })
end
-
+
def test_assert_recognizes_with_method
assert_recognizes({ :controller => 'articles', :action => 'create' }, { :path => '/articles', :method => :post })
assert_recognizes({ :controller => 'articles', :action => 'update', :id => '1' }, { :path => '/articles/1', :method => :put })
@@ -52,7 +57,7 @@ class RoutingAssertionsTest < ActionController::TestCase
assert_raise(ActionController::RoutingError) do
assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'http://test.host/secure/articles')
end
- assert_recognizes({ :controller => 'secure_articles', :action => 'index' }, 'https://test.host/secure/articles')
+ assert_recognizes({ :controller => 'secure_articles', :action => 'index', :protocol => 'https://' }, 'https://test.host/secure/articles')
end
def test_assert_recognizes_with_block_constraint
@@ -62,6 +67,13 @@ class RoutingAssertionsTest < ActionController::TestCase
assert_recognizes({ :controller => 'block_articles', :action => 'index' }, 'https://test.host/block/articles')
end
+ def test_assert_recognizes_with_query_constraint
+ assert_raise(ActionController::RoutingError) do
+ assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'false' }, '/query/articles', { :use_query => 'false' })
+ end
+ assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'true' }, '/query/articles', { :use_query => 'true' })
+ end
+
def test_assert_routing
assert_routing('/articles', :controller => 'articles', :action => 'index')
end
@@ -78,7 +90,7 @@ class RoutingAssertionsTest < ActionController::TestCase
assert_raise(ActionController::RoutingError) do
assert_routing('http://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index' })
end
- assert_routing('https://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index' })
+ assert_routing('https://test.host/secure/articles', { :controller => 'secure_articles', :action => 'index', :protocol => 'https://' })
end
def test_assert_routing_with_block_constraint
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index cf22731823..1a8f40037f 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1,3 +1,4 @@
+# encoding: UTF-8
require 'erb'
require 'abstract_unit'
require 'controller/fake_controllers'
@@ -57,45 +58,46 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "remove", :action => :destroy, :as => :remove
end
- match 'account/logout' => redirect("/logout"), :as => :logout_redirect
- match 'account/login', :to => redirect("/login")
- match 'secure', :to => redirect("/secure/login")
+ get 'account/logout' => redirect("/logout"), :as => :logout_redirect
+ get 'account/login', :to => redirect("/login")
+ get 'secure', :to => redirect("/secure/login")
- match 'mobile', :to => redirect(:subdomain => 'mobile')
- match 'documentation', :to => redirect(:domain => 'example-documentation.com', :path => '')
- match 'new_documentation', :to => redirect(:path => '/documentation/new')
- match 'super_new_documentation', :to => redirect(:host => 'super-docs.com')
+ get 'mobile', :to => redirect(:subdomain => 'mobile')
+ get 'documentation', :to => redirect(:domain => 'example-documentation.com', :path => '')
+ get 'new_documentation', :to => redirect(:path => '/documentation/new')
+ get 'super_new_documentation', :to => redirect(:host => 'super-docs.com')
- match 'stores/:name', :to => redirect(:subdomain => 'stores', :path => '/%{name}')
- match 'stores/:name(*rest)', :to => redirect(:subdomain => 'stores', :path => '/%{name}%{rest}')
+ get 'stores/:name', :to => redirect(:subdomain => 'stores', :path => '/%{name}')
+ get 'stores/:name(*rest)', :to => redirect(:subdomain => 'stores', :path => '/%{name}%{rest}')
- match 'youtube_favorites/:youtube_id/:name', :to => redirect(YoutubeFavoritesRedirector)
+ get 'youtube_favorites/:youtube_id/:name', :to => redirect(YoutubeFavoritesRedirector)
constraints(lambda { |req| true }) do
- match 'account/overview'
+ get 'account/overview'
end
- match '/account/nested/overview'
- match 'sign_in' => "sessions#new"
+ get '/account/nested/overview'
+ get 'sign_in' => "sessions#new"
- match 'account/modulo/:name', :to => redirect("/%{name}s")
- match 'account/proc/:name', :to => redirect {|params| "/#{params[:name].pluralize}" }
- match 'account/proc_req' => redirect {|params, req| "/#{req.method}" }
+ get 'account/modulo/:name', :to => redirect("/%{name}s")
+ get 'account/proc/:name', :to => redirect {|params, req| "/#{params[:name].pluralize}" }
+ get 'account/proc_req' => redirect {|params, req| "/#{req.method}" }
- match 'account/google' => redirect('http://www.google.com/', :status => 302)
+ get 'account/google' => redirect('http://www.google.com/', :status => 302)
match 'openid/login', :via => [:get, :post], :to => "openid#login"
controller(:global) do
get 'global/hide_notice'
- match 'global/export', :to => :export, :as => :export_request
- match '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ }
- match 'global/:action'
+ get 'global/export', :to => :export, :as => :export_request
+ get '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ }
+ get 'global/:action'
end
- match "/local/:action", :controller => "local"
+ get "/local/:action", :controller => "local"
- match "/projects/status(.:format)"
+ get "/projects/status(.:format)"
+ get "/404", :to => lambda { |env| [404, {"Content-Type" => "text/plain"}, ["NOT FOUND"]] }
constraints(:ip => /192\.168\.1\.\d\d\d/) do
get 'admin' => "queenbee#index"
@@ -169,6 +171,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
post :preview, :on => :collection
end
end
+
+ post 'new', :action => 'new', :on => :collection, :as => :new
end
resources :replies do
@@ -280,25 +284,25 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
+ get 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
- match 'people/:id/update', :to => 'people#update', :as => :update_person
- match '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person
+ get 'people/:id/update', :to => 'people#update', :as => :update_person
+ get '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person
# misc
- match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
+ get 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article
# default params
- match 'inline_pages/(:id)', :to => 'pages#show', :id => 'home'
- match 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' }
+ get 'inline_pages/(:id)', :to => 'pages#show', :id => 'home'
+ get 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' }
defaults :id => 'home' do
- match 'scoped_pages/(:id)', :to => 'pages#show'
+ get 'scoped_pages/(:id)', :to => 'pages#show'
end
namespace :account do
- match 'shorthand'
- match 'description', :to => :description, :as => "description"
- match ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback
+ get 'shorthand'
+ get 'description', :to => :description, :as => "description"
+ get ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback
resource :subscription, :credit, :credit_card
root :to => "account#index"
@@ -321,7 +325,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
controller :articles do
scope '/articles', :as => 'article' do
scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do
- match '/:id', :to => :with_id, :as => ""
+ get '/:id', :to => :with_id, :as => ""
end
end
end
@@ -330,7 +334,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :rooms
end
- match '/info' => 'projects#info', :as => 'info'
+ get '/info' => 'projects#info', :as => 'info'
namespace :admin do
scope '(:locale)', :locale => /en|pl/ do
@@ -364,7 +368,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
scope :path => 'api' do
resource :me
- match '/' => 'mes#index'
+ get '/' => 'mes#index'
end
get "(/:username)/followers" => "followers#index"
@@ -377,7 +381,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- match "whatever/:controller(/:action(/:id))", :id => /\d+/
+ get "whatever/:controller(/:action(/:id))", :id => /\d+/
resource :profile do
get :settings
@@ -410,7 +414,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
namespace :private do
root :to => redirect('/private/index')
- match "index", :to => 'private#index'
+ get "index", :to => 'private#index'
end
scope :only => [:index, :show] do
@@ -478,6 +482,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get :preview, :on => :member
end
+ resources :profiles, :param => :username do
+ get :details, :on => :member
+ resources :messages
+ end
+
scope :as => "routes" do
get "/c/:id", :as => :collision, :to => "collision#show"
get "/collision", :to => "collision#show"
@@ -487,7 +496,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get "/forced_collision", :as => :forced_collision, :to => "forced_collision#show"
end
- match '/purchases/:token/:filename',
+ get '/purchases/:token/:filename',
:to => 'purchases#fetch',
:token => /[[:alnum:]]{10}/,
:filename => /(.+)/,
@@ -498,18 +507,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
scope '/countries/:country', :constraints => lambda { |params, req| params[:country].in?(["all", "France"]) } do
- match '/', :to => 'countries#index'
- match '/cities', :to => 'countries#cities'
+ get '/', :to => 'countries#index'
+ get '/cities', :to => 'countries#cities'
end
- match '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' }
+ get '/countries/:country/(*other)', :to => redirect{ |params, req| params[:other] ? "/countries/all/#{params[:other]}" : '/countries/all' }
- match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
+ get '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
scope '/italians' do
- match '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor
- match '/sculptors', :to => 'italians#sculptors'
- match '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/}
+ get '/writers', :to => 'italians#writers', :constraints => ::TestRoutingMapper::IpRestrictor
+ get '/sculptors', :to => 'italians#sculptors'
+ get '/painters/:painter', :to => 'italians#painters', :constraints => {:painter => /michelangelo/}
end
end
end
@@ -528,6 +537,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
"GET"
end
+ def ip
+ "127.0.0.1"
+ end
+
def x_header
@env["HTTP_X_HEADER"] || ""
end
@@ -578,52 +591,42 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
include Routes.url_helpers
def test_logout
- with_test_routes do
- delete '/logout'
- assert_equal 'sessions#destroy', @response.body
+ delete '/logout'
+ assert_equal 'sessions#destroy', @response.body
- assert_equal '/logout', logout_path
- assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true)
- end
+ assert_equal '/logout', logout_path
+ assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true)
end
def test_login
- with_test_routes do
- get '/login'
- assert_equal 'sessions#new', @response.body
- assert_equal '/login', login_path
+ get '/login'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/login', login_path
- post '/login'
- assert_equal 'sessions#create', @response.body
+ post '/login'
+ assert_equal 'sessions#create', @response.body
- assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true)
- assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true)
+ assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true)
+ assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true)
- assert_equal 'http://rubyonrails.org/login', Routes.url_for(:controller => 'sessions', :action => 'create')
- assert_equal 'http://rubyonrails.org/login', Routes.url_helpers.login_url
- end
+ assert_equal 'http://rubyonrails.org/login', Routes.url_for(:controller => 'sessions', :action => 'create')
+ assert_equal 'http://rubyonrails.org/login', Routes.url_helpers.login_url
end
def test_login_redirect
- with_test_routes do
- get '/account/login'
- verify_redirect 'http://www.example.com/login'
- end
+ get '/account/login'
+ verify_redirect 'http://www.example.com/login'
end
def test_logout_redirect_without_to
- with_test_routes do
- assert_equal '/account/logout', logout_redirect_path
- get '/account/logout'
- verify_redirect 'http://www.example.com/logout'
- end
+ assert_equal '/account/logout', logout_redirect_path
+ get '/account/logout'
+ verify_redirect 'http://www.example.com/logout'
end
def test_namespace_redirect
- with_test_routes do
- get '/private'
- verify_redirect 'http://www.example.com/private/index'
- end
+ get '/private'
+ verify_redirect 'http://www.example.com/private/index'
end
def test_namespace_with_controller_segment
@@ -631,7 +634,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
self.class.stub_controllers do |routes|
routes.draw do
namespace :admin do
- match '/:controller(/:action(/:id(.:format)))'
+ get '/:controller(/:action(/:id(.:format)))'
end
end
end
@@ -639,217 +642,179 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_session_singleton_resource
- with_test_routes do
- get '/session'
- assert_equal 'sessions#create', @response.body
- assert_equal '/session', session_path
+ get '/session'
+ assert_equal 'sessions#create', @response.body
+ assert_equal '/session', session_path
- post '/session'
- assert_equal 'sessions#create', @response.body
+ post '/session'
+ assert_equal 'sessions#create', @response.body
- put '/session'
- assert_equal 'sessions#update', @response.body
+ put '/session'
+ assert_equal 'sessions#update', @response.body
- delete '/session'
- assert_equal 'sessions#destroy', @response.body
+ delete '/session'
+ assert_equal 'sessions#destroy', @response.body
- get '/session/new'
- assert_equal 'sessions#new', @response.body
- assert_equal '/session/new', new_session_path
+ get '/session/new'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/session/new', new_session_path
- get '/session/edit'
- assert_equal 'sessions#edit', @response.body
- assert_equal '/session/edit', edit_session_path
+ get '/session/edit'
+ assert_equal 'sessions#edit', @response.body
+ assert_equal '/session/edit', edit_session_path
- post '/session/reset'
- assert_equal 'sessions#reset', @response.body
- assert_equal '/session/reset', reset_session_path
- end
+ post '/session/reset'
+ assert_equal 'sessions#reset', @response.body
+ assert_equal '/session/reset', reset_session_path
end
def test_session_info_nested_singleton_resource
- with_test_routes do
- get '/session/info'
- assert_equal 'infos#show', @response.body
- assert_equal '/session/info', session_info_path
- end
+ get '/session/info'
+ assert_equal 'infos#show', @response.body
+ assert_equal '/session/info', session_info_path
end
def test_member_on_resource
- with_test_routes do
- get '/session/crush'
- assert_equal 'sessions#crush', @response.body
- assert_equal '/session/crush', crush_session_path
- end
+ get '/session/crush'
+ assert_equal 'sessions#crush', @response.body
+ assert_equal '/session/crush', crush_session_path
end
def test_redirect_modulo
- with_test_routes do
- get '/account/modulo/name'
- verify_redirect 'http://www.example.com/names'
- end
+ get '/account/modulo/name'
+ verify_redirect 'http://www.example.com/names'
end
def test_redirect_proc
- with_test_routes do
- get '/account/proc/person'
- verify_redirect 'http://www.example.com/people'
- end
+ get '/account/proc/person'
+ verify_redirect 'http://www.example.com/people'
end
def test_redirect_proc_with_request
- with_test_routes do
- get '/account/proc_req'
- verify_redirect 'http://www.example.com/GET'
- end
+ get '/account/proc_req'
+ verify_redirect 'http://www.example.com/GET'
end
def test_redirect_hash_with_subdomain
- with_test_routes do
- get '/mobile'
- verify_redirect 'http://mobile.example.com/mobile'
- end
+ get '/mobile'
+ verify_redirect 'http://mobile.example.com/mobile'
end
def test_redirect_hash_with_domain_and_path
- with_test_routes do
- get '/documentation'
- verify_redirect 'http://www.example-documentation.com'
- end
+ get '/documentation'
+ verify_redirect 'http://www.example-documentation.com'
end
def test_redirect_hash_with_path
- with_test_routes do
- get '/new_documentation'
- verify_redirect 'http://www.example.com/documentation/new'
- end
+ get '/new_documentation'
+ verify_redirect 'http://www.example.com/documentation/new'
end
def test_redirect_hash_with_host
- with_test_routes do
- get '/super_new_documentation?section=top'
- verify_redirect 'http://super-docs.com/super_new_documentation?section=top'
- end
+ get '/super_new_documentation?section=top'
+ verify_redirect 'http://super-docs.com/super_new_documentation?section=top'
end
def test_redirect_hash_path_substitution
- with_test_routes do
- get '/stores/iernest'
- verify_redirect 'http://stores.example.com/iernest'
- end
+ get '/stores/iernest'
+ verify_redirect 'http://stores.example.com/iernest'
end
def test_redirect_hash_path_substitution_with_catch_all
- with_test_routes do
- get '/stores/iernest/products'
- verify_redirect 'http://stores.example.com/iernest/products'
- end
+ get '/stores/iernest/products'
+ verify_redirect 'http://stores.example.com/iernest/products'
end
def test_redirect_class
- with_test_routes do
- get '/youtube_favorites/oHg5SJYRHA0/rick-rolld'
- verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0'
- end
+ get '/youtube_favorites/oHg5SJYRHA0/rick-rolld'
+ verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0'
end
def test_openid
- with_test_routes do
- get '/openid/login'
- assert_equal 'openid#login', @response.body
+ get '/openid/login'
+ assert_equal 'openid#login', @response.body
- post '/openid/login'
- assert_equal 'openid#login', @response.body
- end
+ post '/openid/login'
+ assert_equal 'openid#login', @response.body
end
def test_bookmarks
- with_test_routes do
- get '/bookmark/build'
- assert_equal 'bookmarks#new', @response.body
- assert_equal '/bookmark/build', bookmark_new_path
-
- post '/bookmark/create'
- assert_equal 'bookmarks#create', @response.body
- assert_equal '/bookmark/create', bookmark_path
-
- put '/bookmark/update'
- assert_equal 'bookmarks#update', @response.body
- assert_equal '/bookmark/update', bookmark_update_path
-
- get '/bookmark/remove'
- assert_equal 'bookmarks#destroy', @response.body
- assert_equal '/bookmark/remove', bookmark_remove_path
- end
+ get '/bookmark/build'
+ assert_equal 'bookmarks#new', @response.body
+ assert_equal '/bookmark/build', bookmark_new_path
+
+ post '/bookmark/create'
+ assert_equal 'bookmarks#create', @response.body
+ assert_equal '/bookmark/create', bookmark_path
+
+ put '/bookmark/update'
+ assert_equal 'bookmarks#update', @response.body
+ assert_equal '/bookmark/update', bookmark_update_path
+
+ get '/bookmark/remove'
+ assert_equal 'bookmarks#destroy', @response.body
+ assert_equal '/bookmark/remove', bookmark_remove_path
end
def test_pagemarks
- with_test_routes do
- get '/pagemark/build'
- assert_equal 'pagemarks#new', @response.body
- assert_equal '/pagemark/build', pagemark_new_path
-
- post '/pagemark/create'
- assert_equal 'pagemarks#create', @response.body
- assert_equal '/pagemark/create', pagemark_path
-
- put '/pagemark/update'
- assert_equal 'pagemarks#update', @response.body
- assert_equal '/pagemark/update', pagemark_update_path
-
- get '/pagemark/remove'
- assert_equal 'pagemarks#destroy', @response.body
- assert_equal '/pagemark/remove', pagemark_remove_path
- end
+ get '/pagemark/build'
+ assert_equal 'pagemarks#new', @response.body
+ assert_equal '/pagemark/build', pagemark_new_path
+
+ post '/pagemark/create'
+ assert_equal 'pagemarks#create', @response.body
+ assert_equal '/pagemark/create', pagemark_path
+
+ put '/pagemark/update'
+ assert_equal 'pagemarks#update', @response.body
+ assert_equal '/pagemark/update', pagemark_update_path
+
+ get '/pagemark/remove'
+ assert_equal 'pagemarks#destroy', @response.body
+ assert_equal '/pagemark/remove', pagemark_remove_path
end
def test_admin
- with_test_routes do
- get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
- assert_equal 'queenbee#index', @response.body
+ get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#index', @response.body
- get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'}
- assert_equal 'pass', @response.headers['X-Cascade']
+ get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
- get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
- assert_equal 'queenbee#accounts', @response.body
+ get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#accounts', @response.body
- get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'}
- assert_equal 'pass', @response.headers['X-Cascade']
+ get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
- get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'}
- assert_equal 'queenbee#passwords', @response.body
+ get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#passwords', @response.body
- get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'}
- assert_equal 'pass', @response.headers['X-Cascade']
- end
+ get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
end
def test_global
- with_test_routes do
- get '/global/dashboard'
- assert_equal 'global#dashboard', @response.body
+ get '/global/dashboard'
+ assert_equal 'global#dashboard', @response.body
- get '/global/export'
- assert_equal 'global#export', @response.body
+ get '/global/export'
+ assert_equal 'global#export', @response.body
- get '/global/hide_notice'
- assert_equal 'global#hide_notice', @response.body
+ get '/global/hide_notice'
+ assert_equal 'global#hide_notice', @response.body
- get '/export/123/foo.txt'
- assert_equal 'global#export', @response.body
+ get '/export/123/foo.txt'
+ assert_equal 'global#export', @response.body
- assert_equal '/global/export', export_request_path
- assert_equal '/global/hide_notice', global_hide_notice_path
- assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt')
- end
+ assert_equal '/global/export', export_request_path
+ assert_equal '/global/hide_notice', global_hide_notice_path
+ assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt')
end
def test_local
- with_test_routes do
- get '/local/dashboard'
- assert_equal 'local#dashboard', @response.body
- end
+ get '/local/dashboard'
+ assert_equal 'local#dashboard', @response.body
end
# tests the use of dup in url_for
@@ -864,6 +829,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal original_options, options
end
+ def test_url_for_does_not_modify_controller
+ controller = '/projects'
+ options = {:controller => controller, :action => 'status', :only_path => true}
+ url = url_for(options)
+
+ assert_equal '/projects/status', url
+ assert_equal '/projects', controller
+ end
+
# tests the arguments modification free version of define_hash_access
def test_named_route_with_no_side_effects
original_options = { :host => 'test.host' }
@@ -876,648 +850,567 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_projects_status
- with_test_routes do
- assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true)
- assert_equal '/projects/status.json', url_for(:controller => 'projects', :action => 'status', :format => 'json', :only_path => true)
- end
+ assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true)
+ assert_equal '/projects/status.json', url_for(:controller => 'projects', :action => 'status', :format => 'json', :only_path => true)
end
def test_projects
- with_test_routes do
- get '/projects'
- assert_equal 'project#index', @response.body
- assert_equal '/projects', projects_path
+ get '/projects'
+ assert_equal 'project#index', @response.body
+ assert_equal '/projects', projects_path
- post '/projects'
- assert_equal 'project#create', @response.body
+ post '/projects'
+ assert_equal 'project#create', @response.body
- get '/projects.xml'
- assert_equal 'project#index', @response.body
- assert_equal '/projects.xml', projects_path(:format => 'xml')
+ get '/projects.xml'
+ assert_equal 'project#index', @response.body
+ assert_equal '/projects.xml', projects_path(:format => 'xml')
- get '/projects/new'
- assert_equal 'project#new', @response.body
- assert_equal '/projects/new', new_project_path
+ get '/projects/new'
+ assert_equal 'project#new', @response.body
+ assert_equal '/projects/new', new_project_path
- get '/projects/new.xml'
- assert_equal 'project#new', @response.body
- assert_equal '/projects/new.xml', new_project_path(:format => 'xml')
+ get '/projects/new.xml'
+ assert_equal 'project#new', @response.body
+ assert_equal '/projects/new.xml', new_project_path(:format => 'xml')
- get '/projects/1'
- assert_equal 'project#show', @response.body
- assert_equal '/projects/1', project_path(:id => '1')
+ get '/projects/1'
+ assert_equal 'project#show', @response.body
+ assert_equal '/projects/1', project_path(:id => '1')
- get '/projects/1.xml'
- assert_equal 'project#show', @response.body
- assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml')
+ get '/projects/1.xml'
+ assert_equal 'project#show', @response.body
+ assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml')
- get '/projects/1/edit'
- assert_equal 'project#edit', @response.body
- assert_equal '/projects/1/edit', edit_project_path(:id => '1')
- end
+ get '/projects/1/edit'
+ assert_equal 'project#edit', @response.body
+ assert_equal '/projects/1/edit', edit_project_path(:id => '1')
+ end
+
+ def test_projects_with_post_action_and_new_path_on_collection
+ post '/projects/new'
+ assert_equal "project#new", @response.body
+ assert_equal "/projects/new", new_projects_path
end
def test_projects_involvements
- with_test_routes do
- get '/projects/1/involvements'
- assert_equal 'involvements#index', @response.body
- assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1')
+ get '/projects/1/involvements'
+ assert_equal 'involvements#index', @response.body
+ assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1')
- get '/projects/1/involvements/new'
- assert_equal 'involvements#new', @response.body
- assert_equal '/projects/1/involvements/new', new_project_involvement_path(:project_id => '1')
+ get '/projects/1/involvements/new'
+ assert_equal 'involvements#new', @response.body
+ assert_equal '/projects/1/involvements/new', new_project_involvement_path(:project_id => '1')
- get '/projects/1/involvements/1'
- assert_equal 'involvements#show', @response.body
- assert_equal '/projects/1/involvements/1', project_involvement_path(:project_id => '1', :id => '1')
+ get '/projects/1/involvements/1'
+ assert_equal 'involvements#show', @response.body
+ assert_equal '/projects/1/involvements/1', project_involvement_path(:project_id => '1', :id => '1')
- put '/projects/1/involvements/1'
- assert_equal 'involvements#update', @response.body
+ put '/projects/1/involvements/1'
+ assert_equal 'involvements#update', @response.body
- delete '/projects/1/involvements/1'
- assert_equal 'involvements#destroy', @response.body
+ delete '/projects/1/involvements/1'
+ assert_equal 'involvements#destroy', @response.body
- get '/projects/1/involvements/1/edit'
- assert_equal 'involvements#edit', @response.body
- assert_equal '/projects/1/involvements/1/edit', edit_project_involvement_path(:project_id => '1', :id => '1')
- end
+ get '/projects/1/involvements/1/edit'
+ assert_equal 'involvements#edit', @response.body
+ assert_equal '/projects/1/involvements/1/edit', edit_project_involvement_path(:project_id => '1', :id => '1')
end
def test_projects_attachments
- with_test_routes do
- get '/projects/1/attachments'
- assert_equal 'attachments#index', @response.body
- assert_equal '/projects/1/attachments', project_attachments_path(:project_id => '1')
- end
+ get '/projects/1/attachments'
+ assert_equal 'attachments#index', @response.body
+ assert_equal '/projects/1/attachments', project_attachments_path(:project_id => '1')
end
def test_projects_participants
- with_test_routes do
- get '/projects/1/participants'
- assert_equal 'participants#index', @response.body
- assert_equal '/projects/1/participants', project_participants_path(:project_id => '1')
-
- put '/projects/1/participants/update_all'
- assert_equal 'participants#update_all', @response.body
- assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1')
- end
+ get '/projects/1/participants'
+ assert_equal 'participants#index', @response.body
+ assert_equal '/projects/1/participants', project_participants_path(:project_id => '1')
+
+ put '/projects/1/participants/update_all'
+ assert_equal 'participants#update_all', @response.body
+ assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1')
end
def test_projects_companies
- with_test_routes do
- get '/projects/1/companies'
- assert_equal 'companies#index', @response.body
- assert_equal '/projects/1/companies', project_companies_path(:project_id => '1')
-
- get '/projects/1/companies/1/people'
- assert_equal 'people#index', @response.body
- assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1')
-
- get '/projects/1/companies/1/avatar'
- assert_equal 'avatar#show', @response.body
- assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1')
- end
+ get '/projects/1/companies'
+ assert_equal 'companies#index', @response.body
+ assert_equal '/projects/1/companies', project_companies_path(:project_id => '1')
+
+ get '/projects/1/companies/1/people'
+ assert_equal 'people#index', @response.body
+ assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1')
+
+ get '/projects/1/companies/1/avatar'
+ assert_equal 'avatar#show', @response.body
+ assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1')
end
def test_project_manager
- with_test_routes do
- get '/projects/1/manager'
- assert_equal 'managers#show', @response.body
- assert_equal '/projects/1/manager', project_super_manager_path(:project_id => '1')
-
- get '/projects/1/manager/new'
- assert_equal 'managers#new', @response.body
- assert_equal '/projects/1/manager/new', new_project_super_manager_path(:project_id => '1')
-
- post '/projects/1/manager/fire'
- assert_equal 'managers#fire', @response.body
- assert_equal '/projects/1/manager/fire', fire_project_super_manager_path(:project_id => '1')
- end
+ get '/projects/1/manager'
+ assert_equal 'managers#show', @response.body
+ assert_equal '/projects/1/manager', project_super_manager_path(:project_id => '1')
+
+ get '/projects/1/manager/new'
+ assert_equal 'managers#new', @response.body
+ assert_equal '/projects/1/manager/new', new_project_super_manager_path(:project_id => '1')
+
+ post '/projects/1/manager/fire'
+ assert_equal 'managers#fire', @response.body
+ assert_equal '/projects/1/manager/fire', fire_project_super_manager_path(:project_id => '1')
end
def test_project_images
- with_test_routes do
- get '/projects/1/images'
- assert_equal 'images#index', @response.body
- assert_equal '/projects/1/images', project_funny_images_path(:project_id => '1')
-
- get '/projects/1/images/new'
- assert_equal 'images#new', @response.body
- assert_equal '/projects/1/images/new', new_project_funny_image_path(:project_id => '1')
-
- post '/projects/1/images/1/revise'
- assert_equal 'images#revise', @response.body
- assert_equal '/projects/1/images/1/revise', revise_project_funny_image_path(:project_id => '1', :id => '1')
- end
+ get '/projects/1/images'
+ assert_equal 'images#index', @response.body
+ assert_equal '/projects/1/images', project_funny_images_path(:project_id => '1')
+
+ get '/projects/1/images/new'
+ assert_equal 'images#new', @response.body
+ assert_equal '/projects/1/images/new', new_project_funny_image_path(:project_id => '1')
+
+ post '/projects/1/images/1/revise'
+ assert_equal 'images#revise', @response.body
+ assert_equal '/projects/1/images/1/revise', revise_project_funny_image_path(:project_id => '1', :id => '1')
end
def test_projects_people
- with_test_routes do
- get '/projects/1/people'
- assert_equal 'people#index', @response.body
- assert_equal '/projects/1/people', project_people_path(:project_id => '1')
-
- get '/projects/1/people/1'
- assert_equal 'people#show', @response.body
- assert_equal '/projects/1/people/1', project_person_path(:project_id => '1', :id => '1')
-
- get '/projects/1/people/1/7a2dec8/avatar'
- assert_equal 'avatars#show', @response.body
- assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8')
-
- put '/projects/1/people/1/accessible_projects'
- assert_equal 'people#accessible_projects', @response.body
- assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1')
-
- post '/projects/1/people/1/resend'
- assert_equal 'people#resend', @response.body
- assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1')
-
- post '/projects/1/people/1/generate_new_password'
- assert_equal 'people#generate_new_password', @response.body
- assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1')
- end
+ get '/projects/1/people'
+ assert_equal 'people#index', @response.body
+ assert_equal '/projects/1/people', project_people_path(:project_id => '1')
+
+ get '/projects/1/people/1'
+ assert_equal 'people#show', @response.body
+ assert_equal '/projects/1/people/1', project_person_path(:project_id => '1', :id => '1')
+
+ get '/projects/1/people/1/7a2dec8/avatar'
+ assert_equal 'avatars#show', @response.body
+ assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8')
+
+ put '/projects/1/people/1/accessible_projects'
+ assert_equal 'people#accessible_projects', @response.body
+ assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1')
+
+ post '/projects/1/people/1/resend'
+ assert_equal 'people#resend', @response.body
+ assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1')
+
+ post '/projects/1/people/1/generate_new_password'
+ assert_equal 'people#generate_new_password', @response.body
+ assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1')
end
def test_projects_with_resources_path_names
- with_test_routes do
- get '/projects/info_about_correlation_indexes'
- assert_equal 'project#correlation_indexes', @response.body
- assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path
- end
+ get '/projects/info_about_correlation_indexes'
+ assert_equal 'project#correlation_indexes', @response.body
+ assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path
end
def test_projects_posts
- with_test_routes do
- get '/projects/1/posts'
- assert_equal 'posts#index', @response.body
- assert_equal '/projects/1/posts', project_posts_path(:project_id => '1')
-
- get '/projects/1/posts/archive'
- assert_equal 'posts#archive', @response.body
- assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1')
-
- get '/projects/1/posts/toggle_view'
- assert_equal 'posts#toggle_view', @response.body
- assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1')
-
- post '/projects/1/posts/1/preview'
- assert_equal 'posts#preview', @response.body
- assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1')
-
- get '/projects/1/posts/1/subscription'
- assert_equal 'subscriptions#show', @response.body
- assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1')
-
- get '/projects/1/posts/1/comments'
- assert_equal 'comments#index', @response.body
- assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1')
-
- post '/projects/1/posts/1/comments/preview'
- assert_equal 'comments#preview', @response.body
- assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1')
- end
+ get '/projects/1/posts'
+ assert_equal 'posts#index', @response.body
+ assert_equal '/projects/1/posts', project_posts_path(:project_id => '1')
+
+ get '/projects/1/posts/archive'
+ assert_equal 'posts#archive', @response.body
+ assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1')
+
+ get '/projects/1/posts/toggle_view'
+ assert_equal 'posts#toggle_view', @response.body
+ assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1')
+
+ post '/projects/1/posts/1/preview'
+ assert_equal 'posts#preview', @response.body
+ assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1')
+
+ get '/projects/1/posts/1/subscription'
+ assert_equal 'subscriptions#show', @response.body
+ assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1')
+
+ get '/projects/1/posts/1/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1')
+
+ post '/projects/1/posts/1/comments/preview'
+ assert_equal 'comments#preview', @response.body
+ assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1')
end
def test_replies
- with_test_routes do
- put '/replies/1/answer'
- assert_equal 'replies#mark_as_answer', @response.body
+ put '/replies/1/answer'
+ assert_equal 'replies#mark_as_answer', @response.body
- delete '/replies/1/answer'
- assert_equal 'replies#unmark_as_answer', @response.body
- end
+ delete '/replies/1/answer'
+ assert_equal 'replies#unmark_as_answer', @response.body
end
def test_resource_routes_with_only_and_except
- with_test_routes do
- get '/posts'
- assert_equal 'posts#index', @response.body
- assert_equal '/posts', posts_path
-
- get '/posts/1'
- assert_equal 'posts#show', @response.body
- assert_equal '/posts/1', post_path(:id => 1)
-
- get '/posts/1/comments'
- assert_equal 'comments#index', @response.body
- assert_equal '/posts/1/comments', post_comments_path(:post_id => 1)
-
- post '/posts'
- assert_equal 'pass', @response.headers['X-Cascade']
- put '/posts/1'
- assert_equal 'pass', @response.headers['X-Cascade']
- delete '/posts/1'
- assert_equal 'pass', @response.headers['X-Cascade']
- delete '/posts/1/comments'
- assert_equal 'pass', @response.headers['X-Cascade']
- end
+ get '/posts'
+ assert_equal 'posts#index', @response.body
+ assert_equal '/posts', posts_path
+
+ get '/posts/1'
+ assert_equal 'posts#show', @response.body
+ assert_equal '/posts/1', post_path(:id => 1)
+
+ get '/posts/1/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/posts/1/comments', post_comments_path(:post_id => 1)
+
+ post '/posts'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ put '/posts/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ delete '/posts/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ delete '/posts/1/comments'
+ assert_equal 'pass', @response.headers['X-Cascade']
end
def test_resource_routes_only_create_update_destroy
- with_test_routes do
- delete '/past'
- assert_equal 'pasts#destroy', @response.body
- assert_equal '/past', past_path
-
- put '/present'
- assert_equal 'presents#update', @response.body
- assert_equal '/present', present_path
-
- post '/future'
- assert_equal 'futures#create', @response.body
- assert_equal '/future', future_path
- end
+ delete '/past'
+ assert_equal 'pasts#destroy', @response.body
+ assert_equal '/past', past_path
+
+ patch '/present'
+ assert_equal 'presents#update', @response.body
+ assert_equal '/present', present_path
+
+ put '/present'
+ assert_equal 'presents#update', @response.body
+ assert_equal '/present', present_path
+
+ post '/future'
+ assert_equal 'futures#create', @response.body
+ assert_equal '/future', future_path
end
def test_resources_routes_only_create_update_destroy
- with_test_routes do
- post '/relationships'
- assert_equal 'relationships#create', @response.body
- assert_equal '/relationships', relationships_path
-
- delete '/relationships/1'
- assert_equal 'relationships#destroy', @response.body
- assert_equal '/relationships/1', relationship_path(1)
-
- put '/friendships/1'
- assert_equal 'friendships#update', @response.body
- assert_equal '/friendships/1', friendship_path(1)
- end
+ post '/relationships'
+ assert_equal 'relationships#create', @response.body
+ assert_equal '/relationships', relationships_path
+
+ delete '/relationships/1'
+ assert_equal 'relationships#destroy', @response.body
+ assert_equal '/relationships/1', relationship_path(1)
+
+ patch '/friendships/1'
+ assert_equal 'friendships#update', @response.body
+ assert_equal '/friendships/1', friendship_path(1)
+
+ put '/friendships/1'
+ assert_equal 'friendships#update', @response.body
+ assert_equal '/friendships/1', friendship_path(1)
end
def test_resource_with_slugs_in_ids
- with_test_routes do
- get '/posts/rails-rocks'
- assert_equal 'posts#show', @response.body
- assert_equal '/posts/rails-rocks', post_path(:id => 'rails-rocks')
- end
+ get '/posts/rails-rocks'
+ assert_equal 'posts#show', @response.body
+ assert_equal '/posts/rails-rocks', post_path(:id => 'rails-rocks')
end
def test_resources_for_uncountable_names
- with_test_routes do
- assert_equal '/sheep', sheep_index_path
- assert_equal '/sheep/1', sheep_path(1)
- assert_equal '/sheep/new', new_sheep_path
- assert_equal '/sheep/1/edit', edit_sheep_path(1)
- assert_equal '/sheep/1/_it', _it_sheep_path(1)
- end
+ assert_equal '/sheep', sheep_index_path
+ assert_equal '/sheep/1', sheep_path(1)
+ assert_equal '/sheep/new', new_sheep_path
+ assert_equal '/sheep/1/edit', edit_sheep_path(1)
+ assert_equal '/sheep/1/_it', _it_sheep_path(1)
end
def test_path_names
- with_test_routes do
- get '/pt/projetos'
- assert_equal 'projects#index', @response.body
- assert_equal '/pt/projetos', pt_projects_path
-
- get '/pt/projetos/1/editar'
- assert_equal 'projects#edit', @response.body
- assert_equal '/pt/projetos/1/editar', edit_pt_project_path(1)
-
- get '/pt/administrador'
- assert_equal 'admins#show', @response.body
- assert_equal '/pt/administrador', pt_admin_path
-
- get '/pt/administrador/novo'
- assert_equal 'admins#new', @response.body
- assert_equal '/pt/administrador/novo', new_pt_admin_path
-
- put '/pt/administrador/ativar'
- assert_equal 'admins#activate', @response.body
- assert_equal '/pt/administrador/ativar', activate_pt_admin_path
- end
+ get '/pt/projetos'
+ assert_equal 'projects#index', @response.body
+ assert_equal '/pt/projetos', pt_projects_path
+
+ get '/pt/projetos/1/editar'
+ assert_equal 'projects#edit', @response.body
+ assert_equal '/pt/projetos/1/editar', edit_pt_project_path(1)
+
+ get '/pt/administrador'
+ assert_equal 'admins#show', @response.body
+ assert_equal '/pt/administrador', pt_admin_path
+
+ get '/pt/administrador/novo'
+ assert_equal 'admins#new', @response.body
+ assert_equal '/pt/administrador/novo', new_pt_admin_path
+
+ put '/pt/administrador/ativar'
+ assert_equal 'admins#activate', @response.body
+ assert_equal '/pt/administrador/ativar', activate_pt_admin_path
end
def test_path_option_override
- with_test_routes do
- get '/pt/projetos/novo/abrir'
- assert_equal 'projects#open', @response.body
- assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path
-
- put '/pt/projetos/1/fechar'
- assert_equal 'projects#close', @response.body
- assert_equal '/pt/projetos/1/fechar', close_pt_project_path(1)
- end
+ get '/pt/projetos/novo/abrir'
+ assert_equal 'projects#open', @response.body
+ assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path
+
+ put '/pt/projetos/1/fechar'
+ assert_equal 'projects#close', @response.body
+ assert_equal '/pt/projetos/1/fechar', close_pt_project_path(1)
end
def test_sprockets
- with_test_routes do
- get '/sprockets.js'
- assert_equal 'javascripts', @response.body
- end
+ get '/sprockets.js'
+ assert_equal 'javascripts', @response.body
end
def test_update_person_route
- with_test_routes do
- get '/people/1/update'
- assert_equal 'people#update', @response.body
+ get '/people/1/update'
+ assert_equal 'people#update', @response.body
- assert_equal '/people/1/update', update_person_path(:id => 1)
- end
+ assert_equal '/people/1/update', update_person_path(:id => 1)
end
def test_update_project_person
- with_test_routes do
- get '/projects/1/people/2/update'
- assert_equal 'people#update', @response.body
+ get '/projects/1/people/2/update'
+ assert_equal 'people#update', @response.body
- assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2)
- end
+ assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2)
end
def test_forum_products
- with_test_routes do
- get '/forum'
- assert_equal 'forum/products#index', @response.body
- assert_equal '/forum', forum_products_path
-
- get '/forum/basecamp'
- assert_equal 'forum/products#show', @response.body
- assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp')
-
- get '/forum/basecamp/questions'
- assert_equal 'forum/questions#index', @response.body
- assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp')
-
- get '/forum/basecamp/questions/1'
- assert_equal 'forum/questions#show', @response.body
- assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1)
- end
+ get '/forum'
+ assert_equal 'forum/products#index', @response.body
+ assert_equal '/forum', forum_products_path
+
+ get '/forum/basecamp'
+ assert_equal 'forum/products#show', @response.body
+ assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp')
+
+ get '/forum/basecamp/questions'
+ assert_equal 'forum/questions#index', @response.body
+ assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp')
+
+ get '/forum/basecamp/questions/1'
+ assert_equal 'forum/questions#show', @response.body
+ assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1)
end
def test_articles_perma
- with_test_routes do
- get '/articles/2009/08/18/rails-3'
- assert_equal 'articles#show', @response.body
+ get '/articles/2009/08/18/rails-3'
+ assert_equal 'articles#show', @response.body
- assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3')
- end
+ assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3')
end
def test_account_namespace
- with_test_routes do
- get '/account/subscription'
- assert_equal 'account/subscriptions#show', @response.body
- assert_equal '/account/subscription', account_subscription_path
-
- get '/account/credit'
- assert_equal 'account/credits#show', @response.body
- assert_equal '/account/credit', account_credit_path
-
- get '/account/credit_card'
- assert_equal 'account/credit_cards#show', @response.body
- assert_equal '/account/credit_card', account_credit_card_path
- end
+ get '/account/subscription'
+ assert_equal 'account/subscriptions#show', @response.body
+ assert_equal '/account/subscription', account_subscription_path
+
+ get '/account/credit'
+ assert_equal 'account/credits#show', @response.body
+ assert_equal '/account/credit', account_credit_path
+
+ get '/account/credit_card'
+ assert_equal 'account/credit_cards#show', @response.body
+ assert_equal '/account/credit_card', account_credit_card_path
end
def test_nested_namespace
- with_test_routes do
- get '/account/admin/subscription'
- assert_equal 'account/admin/subscriptions#show', @response.body
- assert_equal '/account/admin/subscription', account_admin_subscription_path
- end
+ get '/account/admin/subscription'
+ assert_equal 'account/admin/subscriptions#show', @response.body
+ assert_equal '/account/admin/subscription', account_admin_subscription_path
end
def test_namespace_nested_in_resources
- with_test_routes do
- get '/clients/1/google/account'
- assert_equal '/clients/1/google/account', client_google_account_path(1)
- assert_equal 'google/accounts#show', @response.body
-
- get '/clients/1/google/account/secret/info'
- assert_equal '/clients/1/google/account/secret/info', client_google_account_secret_info_path(1)
- assert_equal 'google/secret/infos#show', @response.body
- end
+ get '/clients/1/google/account'
+ assert_equal '/clients/1/google/account', client_google_account_path(1)
+ assert_equal 'google/accounts#show', @response.body
+
+ get '/clients/1/google/account/secret/info'
+ assert_equal '/clients/1/google/account/secret/info', client_google_account_secret_info_path(1)
+ assert_equal 'google/secret/infos#show', @response.body
end
def test_namespace_with_options
- with_test_routes do
- get '/usuarios'
- assert_equal '/usuarios', users_root_path
- assert_equal 'users/home#index', @response.body
- end
+ get '/usuarios'
+ assert_equal '/usuarios', users_root_path
+ assert_equal 'users/home#index', @response.body
end
def test_articles_with_id
- with_test_routes do
- get '/articles/rails/1'
- assert_equal 'articles#with_id', @response.body
+ get '/articles/rails/1'
+ assert_equal 'articles#with_id', @response.body
- get '/articles/123/1'
- assert_equal 'pass', @response.headers['X-Cascade']
+ get '/articles/123/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
- assert_equal '/articles/rails/1', article_with_title_path(:title => 'rails', :id => 1)
- end
+ assert_equal '/articles/rails/1', article_with_title_path(:title => 'rails', :id => 1)
end
def test_access_token_rooms
- with_test_routes do
- get '/12345/rooms'
- assert_equal 'rooms#index', @response.body
+ get '/12345/rooms'
+ assert_equal 'rooms#index', @response.body
- get '/12345/rooms/1'
- assert_equal 'rooms#show', @response.body
+ get '/12345/rooms/1'
+ assert_equal 'rooms#show', @response.body
- get '/12345/rooms/1/edit'
- assert_equal 'rooms#edit', @response.body
- end
+ get '/12345/rooms/1/edit'
+ assert_equal 'rooms#edit', @response.body
end
def test_root
- with_test_routes do
- assert_equal '/', root_path
- get '/'
- assert_equal 'projects#index', @response.body
- end
+ assert_equal '/', root_path
+ get '/'
+ assert_equal 'projects#index', @response.body
end
def test_index
- with_test_routes do
- assert_equal '/info', info_path
- get '/info'
- assert_equal 'projects#info', @response.body
- end
+ assert_equal '/info', info_path
+ get '/info'
+ assert_equal 'projects#info', @response.body
end
def test_match_shorthand_with_no_scope
- with_test_routes do
- assert_equal '/account/overview', account_overview_path
- get '/account/overview'
- assert_equal 'account#overview', @response.body
- end
+ assert_equal '/account/overview', account_overview_path
+ get '/account/overview'
+ assert_equal 'account#overview', @response.body
end
def test_match_shorthand_inside_namespace
- with_test_routes do
- assert_equal '/account/shorthand', account_shorthand_path
- get '/account/shorthand'
- assert_equal 'account#shorthand', @response.body
- end
+ assert_equal '/account/shorthand', account_shorthand_path
+ get '/account/shorthand'
+ assert_equal 'account#shorthand', @response.body
end
def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper
- with_test_routes do
- assert_equal '/replies', replies_path
- end
+ assert_equal '/replies', replies_path
end
def test_scoped_controller_with_namespace_and_action
- with_test_routes do
- assert_equal '/account/twitter/callback', account_callback_path("twitter")
- get '/account/twitter/callback'
- assert_equal 'account/callbacks#twitter', @response.body
+ assert_equal '/account/twitter/callback', account_callback_path("twitter")
+ get '/account/twitter/callback'
+ assert_equal 'account/callbacks#twitter', @response.body
- get '/account/whatever/callback'
- assert_equal 'Not Found', @response.body
- end
+ get '/account/whatever/callback'
+ assert_equal 'Not Found', @response.body
end
def test_convention_match_nested_and_with_leading_slash
- with_test_routes do
- assert_equal '/account/nested/overview', account_nested_overview_path
- get '/account/nested/overview'
- assert_equal 'account/nested#overview', @response.body
- end
+ assert_equal '/account/nested/overview', account_nested_overview_path
+ get '/account/nested/overview'
+ assert_equal 'account/nested#overview', @response.body
end
def test_convention_with_explicit_end
- with_test_routes do
- get '/sign_in'
- assert_equal 'sessions#new', @response.body
- assert_equal '/sign_in', sign_in_path
- end
+ get '/sign_in'
+ assert_equal 'sessions#new', @response.body
+ assert_equal '/sign_in', sign_in_path
end
def test_redirect_with_complete_url_and_status
- with_test_routes do
- get '/account/google'
- verify_redirect 'http://www.google.com/', 302
- end
+ get '/account/google'
+ verify_redirect 'http://www.google.com/', 302
end
def test_redirect_with_port
previous_host, self.host = self.host, 'www.example.com:3000'
- with_test_routes do
- get '/account/login'
- verify_redirect 'http://www.example.com:3000/login'
- end
+
+ get '/account/login'
+ verify_redirect 'http://www.example.com:3000/login'
ensure
self.host = previous_host
end
def test_normalize_namespaced_matches
- with_test_routes do
- assert_equal '/account/description', account_description_path
+ assert_equal '/account/description', account_description_path
- get '/account/description'
- assert_equal 'account#description', @response.body
- end
+ get '/account/description'
+ assert_equal 'account#description', @response.body
end
def test_namespaced_roots
- with_test_routes do
- assert_equal '/account', account_root_path
- get '/account'
- assert_equal 'account/account#index', @response.body
- end
+ assert_equal '/account', account_root_path
+ get '/account'
+ assert_equal 'account/account#index', @response.body
end
def test_optional_scoped_root
- with_test_routes do
- assert_equal '/en', root_path("en")
- get '/en'
- assert_equal 'projects#index', @response.body
- end
+ assert_equal '/en', root_path("en")
+ get '/en'
+ assert_equal 'projects#index', @response.body
end
def test_optional_scoped_path
- with_test_routes do
- assert_equal '/en/descriptions', descriptions_path("en")
- assert_equal '/descriptions', descriptions_path(nil)
- assert_equal '/en/descriptions/1', description_path("en", 1)
- assert_equal '/descriptions/1', description_path(nil, 1)
+ assert_equal '/en/descriptions', descriptions_path("en")
+ assert_equal '/descriptions', descriptions_path(nil)
+ assert_equal '/en/descriptions/1', description_path("en", 1)
+ assert_equal '/descriptions/1', description_path(nil, 1)
- get '/en/descriptions'
- assert_equal 'descriptions#index', @response.body
+ get '/en/descriptions'
+ assert_equal 'descriptions#index', @response.body
- get '/descriptions'
- assert_equal 'descriptions#index', @response.body
+ get '/descriptions'
+ assert_equal 'descriptions#index', @response.body
- get '/en/descriptions/1'
- assert_equal 'descriptions#show', @response.body
+ get '/en/descriptions/1'
+ assert_equal 'descriptions#show', @response.body
- get '/descriptions/1'
- assert_equal 'descriptions#show', @response.body
- end
+ get '/descriptions/1'
+ assert_equal 'descriptions#show', @response.body
end
def test_nested_optional_scoped_path
- with_test_routes do
- assert_equal '/admin/en/descriptions', admin_descriptions_path("en")
- assert_equal '/admin/descriptions', admin_descriptions_path(nil)
- assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1)
- assert_equal '/admin/descriptions/1', admin_description_path(nil, 1)
+ assert_equal '/admin/en/descriptions', admin_descriptions_path("en")
+ assert_equal '/admin/descriptions', admin_descriptions_path(nil)
+ assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1)
+ assert_equal '/admin/descriptions/1', admin_description_path(nil, 1)
- get '/admin/en/descriptions'
- assert_equal 'admin/descriptions#index', @response.body
+ get '/admin/en/descriptions'
+ assert_equal 'admin/descriptions#index', @response.body
- get '/admin/descriptions'
- assert_equal 'admin/descriptions#index', @response.body
+ get '/admin/descriptions'
+ assert_equal 'admin/descriptions#index', @response.body
- get '/admin/en/descriptions/1'
- assert_equal 'admin/descriptions#show', @response.body
+ get '/admin/en/descriptions/1'
+ assert_equal 'admin/descriptions#show', @response.body
- get '/admin/descriptions/1'
- assert_equal 'admin/descriptions#show', @response.body
- end
+ get '/admin/descriptions/1'
+ assert_equal 'admin/descriptions#show', @response.body
end
def test_nested_optional_path_shorthand
- with_test_routes do
- get '/registrations/new'
- assert @request.params[:locale].nil?
+ get '/registrations/new'
+ assert_nil @request.params[:locale]
- get '/en/registrations/new'
- assert 'en', @request.params[:locale]
- end
+ get '/en/registrations/new'
+ assert_equal 'en', @request.params[:locale]
end
def test_default_params
- with_test_routes do
- get '/inline_pages'
- assert_equal 'home', @request.params[:id]
+ get '/inline_pages'
+ assert_equal 'home', @request.params[:id]
- get '/default_pages'
- assert_equal 'home', @request.params[:id]
+ get '/default_pages'
+ assert_equal 'home', @request.params[:id]
- get '/scoped_pages'
- assert_equal 'home', @request.params[:id]
- end
+ get '/scoped_pages'
+ assert_equal 'home', @request.params[:id]
end
def test_resource_constraints
- with_test_routes do
- get '/products/1'
- assert_equal 'pass', @response.headers['X-Cascade']
- get '/products'
- assert_equal 'products#root', @response.body
- get '/products/favorite'
- assert_equal 'products#favorite', @response.body
- get '/products/0001'
- assert_equal 'products#show', @response.body
-
- get '/products/1/images'
- assert_equal 'pass', @response.headers['X-Cascade']
- get '/products/0001/images'
- assert_equal 'images#index', @response.body
- get '/products/0001/images/0001'
- assert_equal 'images#show', @response.body
-
- get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'}
- assert_equal 'pass', @response.headers['X-Cascade']
- get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'}
- assert_equal 'dashboards#show', @response.body
- end
+ get '/products/1'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ get '/products'
+ assert_equal 'products#root', @response.body
+ get '/products/favorite'
+ assert_equal 'products#favorite', @response.body
+ get '/products/0001'
+ assert_equal 'products#show', @response.body
+
+ get '/products/1/images'
+ assert_equal 'pass', @response.headers['X-Cascade']
+ get '/products/0001/images'
+ assert_equal 'images#index', @response.body
+ get '/products/0001/images/0001'
+ assert_equal 'images#show', @response.body
+
+ get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
+ get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'dashboards#show', @response.body
end
def test_root_works_in_the_resources_scope
@@ -1527,731 +1420,651 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
def test_module_scope
- with_test_routes do
- get '/token'
- assert_equal 'api/tokens#show', @response.body
- assert_equal '/token', token_path
- end
+ get '/token'
+ assert_equal 'api/tokens#show', @response.body
+ assert_equal '/token', token_path
end
def test_path_scope
- with_test_routes do
- get '/api/me'
- assert_equal 'mes#show', @response.body
- assert_equal '/api/me', me_path
+ get '/api/me'
+ assert_equal 'mes#show', @response.body
+ assert_equal '/api/me', me_path
- get '/api'
- assert_equal 'mes#index', @response.body
- end
+ get '/api'
+ assert_equal 'mes#index', @response.body
end
def test_url_generator_for_generic_route
- with_test_routes do
- get 'whatever/foo/bar'
- assert_equal 'foo#bar', @response.body
+ get 'whatever/foo/bar'
+ assert_equal 'foo#bar', @response.body
- assert_equal 'http://www.example.com/whatever/foo/bar/1',
- url_for(:controller => "foo", :action => "bar", :id => 1)
- end
+ assert_equal 'http://www.example.com/whatever/foo/bar/1',
+ url_for(:controller => "foo", :action => "bar", :id => 1)
end
def test_url_generator_for_namespaced_generic_route
- with_test_routes do
- get 'whatever/foo/bar/show'
- assert_equal 'foo/bar#show', @response.body
+ get 'whatever/foo/bar/show'
+ assert_equal 'foo/bar#show', @response.body
- get 'whatever/foo/bar/show/1'
- assert_equal 'foo/bar#show', @response.body
+ get 'whatever/foo/bar/show/1'
+ assert_equal 'foo/bar#show', @response.body
- assert_equal 'http://www.example.com/whatever/foo/bar/show',
- url_for(:controller => "foo/bar", :action => "show")
+ assert_equal 'http://www.example.com/whatever/foo/bar/show',
+ url_for(:controller => "foo/bar", :action => "show")
- assert_equal 'http://www.example.com/whatever/foo/bar/show/1',
- url_for(:controller => "foo/bar", :action => "show", :id => '1')
- end
+ assert_equal 'http://www.example.com/whatever/foo/bar/show/1',
+ url_for(:controller => "foo/bar", :action => "show", :id => '1')
end
def test_assert_recognizes_account_overview
- with_test_routes do
- assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview")
- end
+ assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview")
end
def test_resource_new_actions
- with_test_routes do
- assert_equal '/replies/new/preview', preview_new_reply_path
- assert_equal '/pt/projetos/novo/preview', preview_new_pt_project_path
- assert_equal '/pt/administrador/novo/preview', preview_new_pt_admin_path
- assert_equal '/pt/products/novo/preview', preview_new_pt_product_path
- assert_equal '/profile/new/preview', preview_new_profile_path
+ assert_equal '/replies/new/preview', preview_new_reply_path
+ assert_equal '/pt/projetos/novo/preview', preview_new_pt_project_path
+ assert_equal '/pt/administrador/novo/preview', preview_new_pt_admin_path
+ assert_equal '/pt/products/novo/preview', preview_new_pt_product_path
+ assert_equal '/profile/new/preview', preview_new_profile_path
- post '/replies/new/preview'
- assert_equal 'replies#preview', @response.body
+ post '/replies/new/preview'
+ assert_equal 'replies#preview', @response.body
- post '/pt/projetos/novo/preview'
- assert_equal 'projects#preview', @response.body
+ post '/pt/projetos/novo/preview'
+ assert_equal 'projects#preview', @response.body
- post '/pt/administrador/novo/preview'
- assert_equal 'admins#preview', @response.body
+ post '/pt/administrador/novo/preview'
+ assert_equal 'admins#preview', @response.body
- post '/pt/products/novo/preview'
- assert_equal 'products#preview', @response.body
+ post '/pt/products/novo/preview'
+ assert_equal 'products#preview', @response.body
- post '/profile/new/preview'
- assert_equal 'profiles#preview', @response.body
- end
+ post '/profile/new/preview'
+ assert_equal 'profiles#preview', @response.body
end
def test_resource_merges_options_from_scope
- with_test_routes do
- assert_raise(NameError) { new_account_path }
+ assert_raise(NameError) { new_account_path }
- get '/account/new'
- assert_equal 404, status
- end
+ get '/account/new'
+ assert_equal 404, status
end
def test_resources_merges_options_from_scope
- with_test_routes do
- assert_raise(NoMethodError) { edit_product_path('1') }
+ assert_raise(NoMethodError) { edit_product_path('1') }
- get '/products/1/edit'
- assert_equal 404, status
+ get '/products/1/edit'
+ assert_equal 404, status
- assert_raise(NoMethodError) { edit_product_image_path('1', '2') }
+ assert_raise(NoMethodError) { edit_product_image_path('1', '2') }
- post '/products/1/images/2/edit'
- assert_equal 404, status
- end
+ post '/products/1/images/2/edit'
+ assert_equal 404, status
end
def test_shallow_nested_resources
- with_test_routes do
-
- get '/api/teams'
- assert_equal 'api/teams#index', @response.body
- assert_equal '/api/teams', api_teams_path
+ get '/api/teams'
+ assert_equal 'api/teams#index', @response.body
+ assert_equal '/api/teams', api_teams_path
- get '/api/teams/new'
- assert_equal 'api/teams#new', @response.body
- assert_equal '/api/teams/new', new_api_team_path
+ get '/api/teams/new'
+ assert_equal 'api/teams#new', @response.body
+ assert_equal '/api/teams/new', new_api_team_path
- get '/api/teams/1'
- assert_equal 'api/teams#show', @response.body
- assert_equal '/api/teams/1', api_team_path(:id => '1')
+ get '/api/teams/1'
+ assert_equal 'api/teams#show', @response.body
+ assert_equal '/api/teams/1', api_team_path(:id => '1')
- get '/api/teams/1/edit'
- assert_equal 'api/teams#edit', @response.body
- assert_equal '/api/teams/1/edit', edit_api_team_path(:id => '1')
+ get '/api/teams/1/edit'
+ assert_equal 'api/teams#edit', @response.body
+ assert_equal '/api/teams/1/edit', edit_api_team_path(:id => '1')
- get '/api/teams/1/players'
- assert_equal 'api/players#index', @response.body
- assert_equal '/api/teams/1/players', api_team_players_path(:team_id => '1')
+ get '/api/teams/1/players'
+ assert_equal 'api/players#index', @response.body
+ assert_equal '/api/teams/1/players', api_team_players_path(:team_id => '1')
- get '/api/teams/1/players/new'
- assert_equal 'api/players#new', @response.body
- assert_equal '/api/teams/1/players/new', new_api_team_player_path(:team_id => '1')
+ get '/api/teams/1/players/new'
+ assert_equal 'api/players#new', @response.body
+ assert_equal '/api/teams/1/players/new', new_api_team_player_path(:team_id => '1')
- get '/api/players/2'
- assert_equal 'api/players#show', @response.body
- assert_equal '/api/players/2', api_player_path(:id => '2')
+ get '/api/players/2'
+ assert_equal 'api/players#show', @response.body
+ assert_equal '/api/players/2', api_player_path(:id => '2')
- get '/api/players/2/edit'
- assert_equal 'api/players#edit', @response.body
- assert_equal '/api/players/2/edit', edit_api_player_path(:id => '2')
+ get '/api/players/2/edit'
+ assert_equal 'api/players#edit', @response.body
+ assert_equal '/api/players/2/edit', edit_api_player_path(:id => '2')
- get '/api/teams/1/captain'
- assert_equal 'api/captains#show', @response.body
- assert_equal '/api/teams/1/captain', api_team_captain_path(:team_id => '1')
+ get '/api/teams/1/captain'
+ assert_equal 'api/captains#show', @response.body
+ assert_equal '/api/teams/1/captain', api_team_captain_path(:team_id => '1')
- get '/api/teams/1/captain/new'
- assert_equal 'api/captains#new', @response.body
- assert_equal '/api/teams/1/captain/new', new_api_team_captain_path(:team_id => '1')
+ get '/api/teams/1/captain/new'
+ assert_equal 'api/captains#new', @response.body
+ assert_equal '/api/teams/1/captain/new', new_api_team_captain_path(:team_id => '1')
- get '/api/teams/1/captain/edit'
- assert_equal 'api/captains#edit', @response.body
- assert_equal '/api/teams/1/captain/edit', edit_api_team_captain_path(:team_id => '1')
+ get '/api/teams/1/captain/edit'
+ assert_equal 'api/captains#edit', @response.body
+ assert_equal '/api/teams/1/captain/edit', edit_api_team_captain_path(:team_id => '1')
- get '/threads'
- assert_equal 'threads#index', @response.body
- assert_equal '/threads', threads_path
+ get '/threads'
+ assert_equal 'threads#index', @response.body
+ assert_equal '/threads', threads_path
- get '/threads/new'
- assert_equal 'threads#new', @response.body
- assert_equal '/threads/new', new_thread_path
+ get '/threads/new'
+ assert_equal 'threads#new', @response.body
+ assert_equal '/threads/new', new_thread_path
- get '/threads/1'
- assert_equal 'threads#show', @response.body
- assert_equal '/threads/1', thread_path(:id => '1')
+ get '/threads/1'
+ assert_equal 'threads#show', @response.body
+ assert_equal '/threads/1', thread_path(:id => '1')
- get '/threads/1/edit'
- assert_equal 'threads#edit', @response.body
- assert_equal '/threads/1/edit', edit_thread_path(:id => '1')
+ get '/threads/1/edit'
+ assert_equal 'threads#edit', @response.body
+ assert_equal '/threads/1/edit', edit_thread_path(:id => '1')
- get '/threads/1/owner'
- assert_equal 'owners#show', @response.body
- assert_equal '/threads/1/owner', thread_owner_path(:thread_id => '1')
+ get '/threads/1/owner'
+ assert_equal 'owners#show', @response.body
+ assert_equal '/threads/1/owner', thread_owner_path(:thread_id => '1')
- get '/threads/1/messages'
- assert_equal 'messages#index', @response.body
- assert_equal '/threads/1/messages', thread_messages_path(:thread_id => '1')
+ get '/threads/1/messages'
+ assert_equal 'messages#index', @response.body
+ assert_equal '/threads/1/messages', thread_messages_path(:thread_id => '1')
- get '/threads/1/messages/new'
- assert_equal 'messages#new', @response.body
- assert_equal '/threads/1/messages/new', new_thread_message_path(:thread_id => '1')
+ get '/threads/1/messages/new'
+ assert_equal 'messages#new', @response.body
+ assert_equal '/threads/1/messages/new', new_thread_message_path(:thread_id => '1')
- get '/messages/2'
- assert_equal 'messages#show', @response.body
- assert_equal '/messages/2', message_path(:id => '2')
+ get '/messages/2'
+ assert_equal 'messages#show', @response.body
+ assert_equal '/messages/2', message_path(:id => '2')
- get '/messages/2/edit'
- assert_equal 'messages#edit', @response.body
- assert_equal '/messages/2/edit', edit_message_path(:id => '2')
+ get '/messages/2/edit'
+ assert_equal 'messages#edit', @response.body
+ assert_equal '/messages/2/edit', edit_message_path(:id => '2')
- get '/messages/2/comments'
- assert_equal 'comments#index', @response.body
- assert_equal '/messages/2/comments', message_comments_path(:message_id => '2')
+ get '/messages/2/comments'
+ assert_equal 'comments#index', @response.body
+ assert_equal '/messages/2/comments', message_comments_path(:message_id => '2')
- get '/messages/2/comments/new'
- assert_equal 'comments#new', @response.body
- assert_equal '/messages/2/comments/new', new_message_comment_path(:message_id => '2')
+ get '/messages/2/comments/new'
+ assert_equal 'comments#new', @response.body
+ assert_equal '/messages/2/comments/new', new_message_comment_path(:message_id => '2')
- get '/comments/3'
- assert_equal 'comments#show', @response.body
- assert_equal '/comments/3', comment_path(:id => '3')
+ get '/comments/3'
+ assert_equal 'comments#show', @response.body
+ assert_equal '/comments/3', comment_path(:id => '3')
- get '/comments/3/edit'
- assert_equal 'comments#edit', @response.body
- assert_equal '/comments/3/edit', edit_comment_path(:id => '3')
+ get '/comments/3/edit'
+ assert_equal 'comments#edit', @response.body
+ assert_equal '/comments/3/edit', edit_comment_path(:id => '3')
- post '/comments/3/preview'
- assert_equal 'comments#preview', @response.body
- assert_equal '/comments/3/preview', preview_comment_path(:id => '3')
- end
+ post '/comments/3/preview'
+ assert_equal 'comments#preview', @response.body
+ assert_equal '/comments/3/preview', preview_comment_path(:id => '3')
end
def test_shallow_nested_resources_within_scope
- with_test_routes do
+ get '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#index', @response.body
+ assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1)
- get '/hello/notes/1/trackbacks'
- assert_equal 'trackbacks#index', @response.body
- assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1)
+ get '/hello/notes/1/edit'
+ assert_equal 'notes#edit', @response.body
+ assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1')
- get '/hello/notes/1/edit'
- assert_equal 'notes#edit', @response.body
- assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1')
+ get '/hello/notes/1/trackbacks/new'
+ assert_equal 'trackbacks#new', @response.body
+ assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1)
- get '/hello/notes/1/trackbacks/new'
- assert_equal 'trackbacks#new', @response.body
- assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1)
+ get '/hello/trackbacks/1'
+ assert_equal 'trackbacks#show', @response.body
+ assert_equal '/hello/trackbacks/1', trackback_path(:id => '1')
- get '/hello/trackbacks/1'
- assert_equal 'trackbacks#show', @response.body
- assert_equal '/hello/trackbacks/1', trackback_path(:id => '1')
+ get '/hello/trackbacks/1/edit'
+ assert_equal 'trackbacks#edit', @response.body
+ assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1')
- get '/hello/trackbacks/1/edit'
- assert_equal 'trackbacks#edit', @response.body
- assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1')
+ put '/hello/trackbacks/1'
+ assert_equal 'trackbacks#update', @response.body
- put '/hello/trackbacks/1'
- assert_equal 'trackbacks#update', @response.body
+ post '/hello/notes/1/trackbacks'
+ assert_equal 'trackbacks#create', @response.body
- post '/hello/notes/1/trackbacks'
- assert_equal 'trackbacks#create', @response.body
+ delete '/hello/trackbacks/1'
+ assert_equal 'trackbacks#destroy', @response.body
- delete '/hello/trackbacks/1'
- assert_equal 'trackbacks#destroy', @response.body
+ get '/hello/notes'
+ assert_equal 'notes#index', @response.body
- get '/hello/notes'
- assert_equal 'notes#index', @response.body
+ post '/hello/notes'
+ assert_equal 'notes#create', @response.body
- post '/hello/notes'
- assert_equal 'notes#create', @response.body
+ get '/hello/notes/new'
+ assert_equal 'notes#new', @response.body
+ assert_equal '/hello/notes/new', new_note_path
- get '/hello/notes/new'
- assert_equal 'notes#new', @response.body
- assert_equal '/hello/notes/new', new_note_path
+ get '/hello/notes/1'
+ assert_equal 'notes#show', @response.body
+ assert_equal '/hello/notes/1', note_path(:id => 1)
- get '/hello/notes/1'
- assert_equal 'notes#show', @response.body
- assert_equal '/hello/notes/1', note_path(:id => 1)
+ put '/hello/notes/1'
+ assert_equal 'notes#update', @response.body
- put '/hello/notes/1'
- assert_equal 'notes#update', @response.body
-
- delete '/hello/notes/1'
- assert_equal 'notes#destroy', @response.body
- end
+ delete '/hello/notes/1'
+ assert_equal 'notes#destroy', @response.body
end
def test_custom_resource_routes_are_scoped
- with_test_routes do
- assert_equal '/customers/recent', recent_customers_path
- assert_equal '/customers/1/profile', profile_customer_path(:id => '1')
- assert_equal '/customers/1/secret/profile', secret_profile_customer_path(:id => '1')
- assert_equal '/customers/new/preview', another_preview_new_customer_path
- assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg)
- assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1')
- assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2')
- assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1')
- assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1')
- assert_equal '/notes/1/print', print_note_path(:id => '1')
- assert_equal '/api/customers/recent', recent_api_customers_path
- assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1')
- assert_equal '/api/customers/new/preview', preview_new_api_customer_path
-
- get '/customers/1/invoices/overdue'
- assert_equal 'invoices#overdue', @response.body
-
- get '/customers/1/secret/profile'
- assert_equal 'customers#secret', @response.body
- end
+ assert_equal '/customers/recent', recent_customers_path
+ assert_equal '/customers/1/profile', profile_customer_path(:id => '1')
+ assert_equal '/customers/1/secret/profile', secret_profile_customer_path(:id => '1')
+ assert_equal '/customers/new/preview', another_preview_new_customer_path
+ assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg)
+ assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1')
+ assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2')
+ assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1')
+ assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1')
+ assert_equal '/notes/1/print', print_note_path(:id => '1')
+ assert_equal '/api/customers/recent', recent_api_customers_path
+ assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1')
+ assert_equal '/api/customers/new/preview', preview_new_api_customer_path
+
+ get '/customers/1/invoices/overdue'
+ assert_equal 'invoices#overdue', @response.body
+
+ get '/customers/1/secret/profile'
+ assert_equal 'customers#secret', @response.body
end
def test_shallow_nested_routes_ignore_module
- with_test_routes do
- get '/errors/1/notices'
- assert_equal 'api/notices#index', @response.body
- assert_equal '/errors/1/notices', error_notices_path(:error_id => '1')
-
- get '/notices/1'
- assert_equal 'api/notices#show', @response.body
- assert_equal '/notices/1', notice_path(:id => '1')
- end
+ get '/errors/1/notices'
+ assert_equal 'api/notices#index', @response.body
+ assert_equal '/errors/1/notices', error_notices_path(:error_id => '1')
+
+ get '/notices/1'
+ assert_equal 'api/notices#show', @response.body
+ assert_equal '/notices/1', notice_path(:id => '1')
end
def test_non_greedy_regexp
- with_test_routes do
- get '/api/1.0/users'
- assert_equal 'api/users#index', @response.body
- assert_equal '/api/1.0/users', api_users_path(:version => '1.0')
-
- get '/api/1.0/users.json'
- assert_equal 'api/users#index', @response.body
- assert_equal true, @request.format.json?
- assert_equal '/api/1.0/users.json', api_users_path(:version => '1.0', :format => :json)
-
- get '/api/1.0/users/first.last'
- assert_equal 'api/users#show', @response.body
- assert_equal 'first.last', @request.params[:id]
- assert_equal '/api/1.0/users/first.last', api_user_path(:version => '1.0', :id => 'first.last')
-
- get '/api/1.0/users/first.last.xml'
- assert_equal 'api/users#show', @response.body
- assert_equal 'first.last', @request.params[:id]
- assert_equal true, @request.format.xml?
- assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml)
- end
+ get '/api/1.0/users'
+ assert_equal 'api/users#index', @response.body
+ assert_equal '/api/1.0/users', api_users_path(:version => '1.0')
+
+ get '/api/1.0/users.json'
+ assert_equal 'api/users#index', @response.body
+ assert_equal true, @request.format.json?
+ assert_equal '/api/1.0/users.json', api_users_path(:version => '1.0', :format => :json)
+
+ get '/api/1.0/users/first.last'
+ assert_equal 'api/users#show', @response.body
+ assert_equal 'first.last', @request.params[:id]
+ assert_equal '/api/1.0/users/first.last', api_user_path(:version => '1.0', :id => 'first.last')
+
+ get '/api/1.0/users/first.last.xml'
+ assert_equal 'api/users#show', @response.body
+ assert_equal 'first.last', @request.params[:id]
+ assert_equal true, @request.format.xml?
+ assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml)
end
def test_glob_parameter_accepts_regexp
- with_test_routes do
- get '/en/path/to/existing/file.html'
- assert_equal 200, @response.status
- end
+ get '/en/path/to/existing/file.html'
+ assert_equal 200, @response.status
end
def test_resources_controller_name_is_not_pluralized
- with_test_routes do
- get '/content'
- assert_equal 'content#index', @response.body
- end
+ get '/content'
+ assert_equal 'content#index', @response.body
end
def test_url_generator_for_optional_prefix_dynamic_segment
- with_test_routes do
- get '/bob/followers'
- assert_equal 'followers#index', @response.body
- assert_equal 'http://www.example.com/bob/followers',
- url_for(:controller => "followers", :action => "index", :username => "bob")
-
- get '/followers'
- assert_equal 'followers#index', @response.body
- assert_equal 'http://www.example.com/followers',
- url_for(:controller => "followers", :action => "index", :username => nil)
- end
+ get '/bob/followers'
+ assert_equal 'followers#index', @response.body
+ assert_equal 'http://www.example.com/bob/followers',
+ url_for(:controller => "followers", :action => "index", :username => "bob")
+
+ get '/followers'
+ assert_equal 'followers#index', @response.body
+ assert_equal 'http://www.example.com/followers',
+ url_for(:controller => "followers", :action => "index", :username => nil)
end
def test_url_generator_for_optional_suffix_static_and_dynamic_segment
- with_test_routes do
- get '/groups/user/bob'
- assert_equal 'groups#index', @response.body
- assert_equal 'http://www.example.com/groups/user/bob',
- url_for(:controller => "groups", :action => "index", :username => "bob")
-
- get '/groups'
- assert_equal 'groups#index', @response.body
- assert_equal 'http://www.example.com/groups',
- url_for(:controller => "groups", :action => "index", :username => nil)
- end
+ get '/groups/user/bob'
+ assert_equal 'groups#index', @response.body
+ assert_equal 'http://www.example.com/groups/user/bob',
+ url_for(:controller => "groups", :action => "index", :username => "bob")
+
+ get '/groups'
+ assert_equal 'groups#index', @response.body
+ assert_equal 'http://www.example.com/groups',
+ url_for(:controller => "groups", :action => "index", :username => nil)
end
def test_url_generator_for_optional_prefix_static_and_dynamic_segment
- with_test_routes do
- get 'user/bob/photos'
- assert_equal 'photos#index', @response.body
- assert_equal 'http://www.example.com/user/bob/photos',
- url_for(:controller => "photos", :action => "index", :username => "bob")
-
- get 'photos'
- assert_equal 'photos#index', @response.body
- assert_equal 'http://www.example.com/photos',
- url_for(:controller => "photos", :action => "index", :username => nil)
- end
+ get 'user/bob/photos'
+ assert_equal 'photos#index', @response.body
+ assert_equal 'http://www.example.com/user/bob/photos',
+ url_for(:controller => "photos", :action => "index", :username => "bob")
+
+ get 'photos'
+ assert_equal 'photos#index', @response.body
+ assert_equal 'http://www.example.com/photos',
+ url_for(:controller => "photos", :action => "index", :username => nil)
end
def test_url_recognition_for_optional_static_segments
- with_test_routes do
- get '/groups/discussions/messages'
- assert_equal 'messages#index', @response.body
+ get '/groups/discussions/messages'
+ assert_equal 'messages#index', @response.body
- get '/groups/discussions/messages/1'
- assert_equal 'messages#show', @response.body
+ get '/groups/discussions/messages/1'
+ assert_equal 'messages#show', @response.body
- get '/groups/messages'
- assert_equal 'messages#index', @response.body
+ get '/groups/messages'
+ assert_equal 'messages#index', @response.body
- get '/groups/messages/1'
- assert_equal 'messages#show', @response.body
+ get '/groups/messages/1'
+ assert_equal 'messages#show', @response.body
- get '/discussions/messages'
- assert_equal 'messages#index', @response.body
+ get '/discussions/messages'
+ assert_equal 'messages#index', @response.body
- get '/discussions/messages/1'
- assert_equal 'messages#show', @response.body
+ get '/discussions/messages/1'
+ assert_equal 'messages#show', @response.body
- get '/messages'
- assert_equal 'messages#index', @response.body
+ get '/messages'
+ assert_equal 'messages#index', @response.body
- get '/messages/1'
- assert_equal 'messages#show', @response.body
- end
+ get '/messages/1'
+ assert_equal 'messages#show', @response.body
end
def test_router_removes_invalid_conditions
- with_test_routes do
- get '/tickets'
- assert_equal 'tickets#index', @response.body
- assert_equal '/tickets', tickets_path
- end
+ get '/tickets'
+ assert_equal 'tickets#index', @response.body
+ assert_equal '/tickets', tickets_path
end
def test_constraints_are_merged_from_scope
- with_test_routes do
- get '/movies/0001'
- assert_equal 'movies#show', @response.body
- assert_equal '/movies/0001', movie_path(:id => '0001')
-
- get '/movies/00001'
- assert_equal 'Not Found', @response.body
- assert_raises(ActionController::RoutingError){ movie_path(:id => '00001') }
-
- get '/movies/0001/reviews'
- assert_equal 'reviews#index', @response.body
- assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001')
-
- get '/movies/00001/reviews'
- assert_equal 'Not Found', @response.body
- assert_raises(ActionController::RoutingError){ movie_reviews_path(:movie_id => '00001') }
-
- get '/movies/0001/reviews/0001'
- assert_equal 'reviews#show', @response.body
- assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001')
-
- get '/movies/00001/reviews/0001'
- assert_equal 'Not Found', @response.body
- assert_raises(ActionController::RoutingError){ movie_path(:movie_id => '00001', :id => '00001') }
-
- get '/movies/0001/trailer'
- assert_equal 'trailers#show', @response.body
- assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001')
-
- get '/movies/00001/trailer'
- assert_equal 'Not Found', @response.body
- assert_raises(ActionController::RoutingError){ movie_trailer_path(:movie_id => '00001') }
- end
+ get '/movies/0001'
+ assert_equal 'movies#show', @response.body
+ assert_equal '/movies/0001', movie_path(:id => '0001')
+
+ get '/movies/00001'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_path(:id => '00001') }
+
+ get '/movies/0001/reviews'
+ assert_equal 'reviews#index', @response.body
+ assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001')
+
+ get '/movies/00001/reviews'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_reviews_path(:movie_id => '00001') }
+
+ get '/movies/0001/reviews/0001'
+ assert_equal 'reviews#show', @response.body
+ assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001')
+
+ get '/movies/00001/reviews/0001'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_path(:movie_id => '00001', :id => '00001') }
+
+ get '/movies/0001/trailer'
+ assert_equal 'trailers#show', @response.body
+ assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001')
+
+ get '/movies/00001/trailer'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_trailer_path(:movie_id => '00001') }
end
def test_only_should_be_read_from_scope
- with_test_routes do
- get '/only/clubs'
- assert_equal 'only/clubs#index', @response.body
- assert_equal '/only/clubs', only_clubs_path
-
- get '/only/clubs/1/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_only_club_path(:id => '1') }
-
- get '/only/clubs/1/players'
- assert_equal 'only/players#index', @response.body
- assert_equal '/only/clubs/1/players', only_club_players_path(:club_id => '1')
-
- get '/only/clubs/1/players/2/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_only_club_player_path(:club_id => '1', :id => '2') }
-
- get '/only/clubs/1/chairman'
- assert_equal 'only/chairmen#show', @response.body
- assert_equal '/only/clubs/1/chairman', only_club_chairman_path(:club_id => '1')
-
- get '/only/clubs/1/chairman/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_only_club_chairman_path(:club_id => '1') }
- end
+ get '/only/clubs'
+ assert_equal 'only/clubs#index', @response.body
+ assert_equal '/only/clubs', only_clubs_path
+
+ get '/only/clubs/1/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_path(:id => '1') }
+
+ get '/only/clubs/1/players'
+ assert_equal 'only/players#index', @response.body
+ assert_equal '/only/clubs/1/players', only_club_players_path(:club_id => '1')
+
+ get '/only/clubs/1/players/2/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_player_path(:club_id => '1', :id => '2') }
+
+ get '/only/clubs/1/chairman'
+ assert_equal 'only/chairmen#show', @response.body
+ assert_equal '/only/clubs/1/chairman', only_club_chairman_path(:club_id => '1')
+
+ get '/only/clubs/1/chairman/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_only_club_chairman_path(:club_id => '1') }
end
def test_except_should_be_read_from_scope
- with_test_routes do
- get '/except/clubs'
- assert_equal 'except/clubs#index', @response.body
- assert_equal '/except/clubs', except_clubs_path
-
- get '/except/clubs/1/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_except_club_path(:id => '1') }
-
- get '/except/clubs/1/players'
- assert_equal 'except/players#index', @response.body
- assert_equal '/except/clubs/1/players', except_club_players_path(:club_id => '1')
-
- get '/except/clubs/1/players/2/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_except_club_player_path(:club_id => '1', :id => '2') }
-
- get '/except/clubs/1/chairman'
- assert_equal 'except/chairmen#show', @response.body
- assert_equal '/except/clubs/1/chairman', except_club_chairman_path(:club_id => '1')
-
- get '/except/clubs/1/chairman/edit'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { edit_except_club_chairman_path(:club_id => '1') }
- end
+ get '/except/clubs'
+ assert_equal 'except/clubs#index', @response.body
+ assert_equal '/except/clubs', except_clubs_path
+
+ get '/except/clubs/1/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_path(:id => '1') }
+
+ get '/except/clubs/1/players'
+ assert_equal 'except/players#index', @response.body
+ assert_equal '/except/clubs/1/players', except_club_players_path(:club_id => '1')
+
+ get '/except/clubs/1/players/2/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_player_path(:club_id => '1', :id => '2') }
+
+ get '/except/clubs/1/chairman'
+ assert_equal 'except/chairmen#show', @response.body
+ assert_equal '/except/clubs/1/chairman', except_club_chairman_path(:club_id => '1')
+
+ get '/except/clubs/1/chairman/edit'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { edit_except_club_chairman_path(:club_id => '1') }
end
def test_only_option_should_override_scope
- with_test_routes do
- get '/only/sectors'
- assert_equal 'only/sectors#index', @response.body
- assert_equal '/only/sectors', only_sectors_path
-
- get '/only/sectors/1'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { only_sector_path(:id => '1') }
- end
+ get '/only/sectors'
+ assert_equal 'only/sectors#index', @response.body
+ assert_equal '/only/sectors', only_sectors_path
+
+ get '/only/sectors/1'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_path(:id => '1') }
end
def test_only_option_should_not_inherit
- with_test_routes do
- get '/only/sectors/1/companies/2'
- assert_equal 'only/companies#show', @response.body
- assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2')
-
- get '/only/sectors/1/leader'
- assert_equal 'only/leaders#show', @response.body
- assert_equal '/only/sectors/1/leader', only_sector_leader_path(:sector_id => '1')
- end
+ get '/only/sectors/1/companies/2'
+ assert_equal 'only/companies#show', @response.body
+ assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2')
+
+ get '/only/sectors/1/leader'
+ assert_equal 'only/leaders#show', @response.body
+ assert_equal '/only/sectors/1/leader', only_sector_leader_path(:sector_id => '1')
end
def test_except_option_should_override_scope
- with_test_routes do
- get '/except/sectors'
- assert_equal 'except/sectors#index', @response.body
- assert_equal '/except/sectors', except_sectors_path
-
- get '/except/sectors/1'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { except_sector_path(:id => '1') }
- end
+ get '/except/sectors'
+ assert_equal 'except/sectors#index', @response.body
+ assert_equal '/except/sectors', except_sectors_path
+
+ get '/except/sectors/1'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_path(:id => '1') }
end
def test_except_option_should_not_inherit
- with_test_routes do
- get '/except/sectors/1/companies/2'
- assert_equal 'except/companies#show', @response.body
- assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2')
-
- get '/except/sectors/1/leader'
- assert_equal 'except/leaders#show', @response.body
- assert_equal '/except/sectors/1/leader', except_sector_leader_path(:sector_id => '1')
- end
+ get '/except/sectors/1/companies/2'
+ assert_equal 'except/companies#show', @response.body
+ assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2')
+
+ get '/except/sectors/1/leader'
+ assert_equal 'except/leaders#show', @response.body
+ assert_equal '/except/sectors/1/leader', except_sector_leader_path(:sector_id => '1')
end
def test_except_option_should_override_scoped_only
- with_test_routes do
- get '/only/sectors/1/managers'
- assert_equal 'only/managers#index', @response.body
- assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1')
-
- get '/only/sectors/1/managers/2'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { only_sector_manager_path(:sector_id => '1', :id => '2') }
- end
+ get '/only/sectors/1/managers'
+ assert_equal 'only/managers#index', @response.body
+ assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1')
+
+ get '/only/sectors/1/managers/2'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_manager_path(:sector_id => '1', :id => '2') }
end
def test_only_option_should_override_scoped_except
- with_test_routes do
- get '/except/sectors/1/managers'
- assert_equal 'except/managers#index', @response.body
- assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1')
-
- get '/except/sectors/1/managers/2'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { except_sector_manager_path(:sector_id => '1', :id => '2') }
- end
+ get '/except/sectors/1/managers'
+ assert_equal 'except/managers#index', @response.body
+ assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1')
+
+ get '/except/sectors/1/managers/2'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_manager_path(:sector_id => '1', :id => '2') }
end
def test_only_scope_should_override_parent_scope
- with_test_routes do
- get '/only/sectors/1/companies/2/divisions'
- assert_equal 'only/divisions#index', @response.body
- assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
-
- get '/only/sectors/1/companies/2/divisions/3'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { only_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
- end
+ get '/only/sectors/1/companies/2/divisions'
+ assert_equal 'only/divisions#index', @response.body
+ assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
+
+ get '/only/sectors/1/companies/2/divisions/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
end
def test_except_scope_should_override_parent_scope
- with_test_routes do
- get '/except/sectors/1/companies/2/divisions'
- assert_equal 'except/divisions#index', @response.body
- assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
-
- get '/except/sectors/1/companies/2/divisions/3'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { except_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
- end
+ get '/except/sectors/1/companies/2/divisions'
+ assert_equal 'except/divisions#index', @response.body
+ assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2')
+
+ get '/except/sectors/1/companies/2/divisions/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') }
end
def test_except_scope_should_override_parent_only_scope
- with_test_routes do
- get '/only/sectors/1/companies/2/departments'
- assert_equal 'only/departments#index', @response.body
- assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2')
-
- get '/only/sectors/1/companies/2/departments/3'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { only_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
- end
+ get '/only/sectors/1/companies/2/departments'
+ assert_equal 'only/departments#index', @response.body
+ assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2')
+
+ get '/only/sectors/1/companies/2/departments/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { only_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
end
def test_only_scope_should_override_parent_except_scope
- with_test_routes do
- get '/except/sectors/1/companies/2/departments'
- assert_equal 'except/departments#index', @response.body
- assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2')
-
- get '/except/sectors/1/companies/2/departments/3'
- assert_equal 'Not Found', @response.body
- assert_raise(NoMethodError) { except_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
- end
+ get '/except/sectors/1/companies/2/departments'
+ assert_equal 'except/departments#index', @response.body
+ assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2')
+
+ get '/except/sectors/1/companies/2/departments/3'
+ assert_equal 'Not Found', @response.body
+ assert_raise(NoMethodError) { except_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') }
end
def test_resources_are_not_pluralized
- with_test_routes do
- get '/transport/taxis'
- assert_equal 'transport/taxis#index', @response.body
- assert_equal '/transport/taxis', transport_taxis_path
+ get '/transport/taxis'
+ assert_equal 'transport/taxis#index', @response.body
+ assert_equal '/transport/taxis', transport_taxis_path
- get '/transport/taxis/new'
- assert_equal 'transport/taxis#new', @response.body
- assert_equal '/transport/taxis/new', new_transport_taxi_path
+ get '/transport/taxis/new'
+ assert_equal 'transport/taxis#new', @response.body
+ assert_equal '/transport/taxis/new', new_transport_taxi_path
- post '/transport/taxis'
- assert_equal 'transport/taxis#create', @response.body
+ post '/transport/taxis'
+ assert_equal 'transport/taxis#create', @response.body
- get '/transport/taxis/1'
- assert_equal 'transport/taxis#show', @response.body
- assert_equal '/transport/taxis/1', transport_taxi_path(:id => '1')
+ get '/transport/taxis/1'
+ assert_equal 'transport/taxis#show', @response.body
+ assert_equal '/transport/taxis/1', transport_taxi_path(:id => '1')
- get '/transport/taxis/1/edit'
- assert_equal 'transport/taxis#edit', @response.body
- assert_equal '/transport/taxis/1/edit', edit_transport_taxi_path(:id => '1')
+ get '/transport/taxis/1/edit'
+ assert_equal 'transport/taxis#edit', @response.body
+ assert_equal '/transport/taxis/1/edit', edit_transport_taxi_path(:id => '1')
- put '/transport/taxis/1'
- assert_equal 'transport/taxis#update', @response.body
+ put '/transport/taxis/1'
+ assert_equal 'transport/taxis#update', @response.body
- delete '/transport/taxis/1'
- assert_equal 'transport/taxis#destroy', @response.body
- end
+ delete '/transport/taxis/1'
+ assert_equal 'transport/taxis#destroy', @response.body
end
def test_singleton_resources_are_not_singularized
- with_test_routes do
- get '/medical/taxis/new'
- assert_equal 'medical/taxes#new', @response.body
- assert_equal '/medical/taxis/new', new_medical_taxis_path
+ get '/medical/taxis/new'
+ assert_equal 'medical/taxis#new', @response.body
+ assert_equal '/medical/taxis/new', new_medical_taxis_path
- post '/medical/taxis'
- assert_equal 'medical/taxes#create', @response.body
+ post '/medical/taxis'
+ assert_equal 'medical/taxis#create', @response.body
- get '/medical/taxis'
- assert_equal 'medical/taxes#show', @response.body
- assert_equal '/medical/taxis', medical_taxis_path
+ get '/medical/taxis'
+ assert_equal 'medical/taxis#show', @response.body
+ assert_equal '/medical/taxis', medical_taxis_path
- get '/medical/taxis/edit'
- assert_equal 'medical/taxes#edit', @response.body
- assert_equal '/medical/taxis/edit', edit_medical_taxis_path
+ get '/medical/taxis/edit'
+ assert_equal 'medical/taxis#edit', @response.body
+ assert_equal '/medical/taxis/edit', edit_medical_taxis_path
- put '/medical/taxis'
- assert_equal 'medical/taxes#update', @response.body
+ put '/medical/taxis'
+ assert_equal 'medical/taxis#update', @response.body
- delete '/medical/taxis'
- assert_equal 'medical/taxes#destroy', @response.body
- end
+ delete '/medical/taxis'
+ assert_equal 'medical/taxis#destroy', @response.body
end
def test_greedy_resource_id_regexp_doesnt_match_edit_and_custom_action
- with_test_routes do
- get '/sections/1/edit'
- assert_equal 'sections#edit', @response.body
- assert_equal '/sections/1/edit', edit_section_path(:id => '1')
-
- get '/sections/1/preview'
- assert_equal 'sections#preview', @response.body
- assert_equal '/sections/1/preview', preview_section_path(:id => '1')
- end
+ get '/sections/1/edit'
+ assert_equal 'sections#edit', @response.body
+ assert_equal '/sections/1/edit', edit_section_path(:id => '1')
+
+ get '/sections/1/preview'
+ assert_equal 'sections#preview', @response.body
+ assert_equal '/sections/1/preview', preview_section_path(:id => '1')
end
def test_resource_constraints_are_pushed_to_scope
- with_test_routes do
- get '/wiki/articles/Ruby_on_Rails_3.0'
- assert_equal 'wiki/articles#show', @response.body
- assert_equal '/wiki/articles/Ruby_on_Rails_3.0', wiki_article_path(:id => 'Ruby_on_Rails_3.0')
-
- get '/wiki/articles/Ruby_on_Rails_3.0/comments/new'
- assert_equal 'wiki/comments#new', @response.body
- assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments/new', new_wiki_article_comment_path(:article_id => 'Ruby_on_Rails_3.0')
-
- post '/wiki/articles/Ruby_on_Rails_3.0/comments'
- assert_equal 'wiki/comments#create', @response.body
- assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments', wiki_article_comments_path(:article_id => 'Ruby_on_Rails_3.0')
- end
+ get '/wiki/articles/Ruby_on_Rails_3.0'
+ assert_equal 'wiki/articles#show', @response.body
+ assert_equal '/wiki/articles/Ruby_on_Rails_3.0', wiki_article_path(:id => 'Ruby_on_Rails_3.0')
+
+ get '/wiki/articles/Ruby_on_Rails_3.0/comments/new'
+ assert_equal 'wiki/comments#new', @response.body
+ assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments/new', new_wiki_article_comment_path(:article_id => 'Ruby_on_Rails_3.0')
+
+ post '/wiki/articles/Ruby_on_Rails_3.0/comments'
+ assert_equal 'wiki/comments#create', @response.body
+ assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments', wiki_article_comments_path(:article_id => 'Ruby_on_Rails_3.0')
end
def test_resources_path_can_be_a_symbol
- with_test_routes do
- get '/pages'
- assert_equal 'wiki_pages#index', @response.body
- assert_equal '/pages', wiki_pages_path
-
- get '/pages/Ruby_on_Rails'
- assert_equal 'wiki_pages#show', @response.body
- assert_equal '/pages/Ruby_on_Rails', wiki_page_path(:id => 'Ruby_on_Rails')
-
- get '/my_account'
- assert_equal 'wiki_accounts#show', @response.body
- assert_equal '/my_account', wiki_account_path
- end
+ get '/pages'
+ assert_equal 'wiki_pages#index', @response.body
+ assert_equal '/pages', wiki_pages_path
+
+ get '/pages/Ruby_on_Rails'
+ assert_equal 'wiki_pages#show', @response.body
+ assert_equal '/pages/Ruby_on_Rails', wiki_page_path(:id => 'Ruby_on_Rails')
+
+ get '/my_account'
+ assert_equal 'wiki_accounts#show', @response.body
+ assert_equal '/my_account', wiki_account_path
end
def test_redirect_https
- with_test_routes do
- with_https do
- get '/secure'
- verify_redirect 'https://www.example.com/secure/login'
- end
+ with_https do
+ get '/secure'
+ verify_redirect 'https://www.example.com/secure/login'
end
end
@@ -2329,7 +2142,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_named_routes_collision_is_avoided_unless_explicitly_given_as
assert_equal "/c/1", routes_collision_path(1)
- assert_equal "/forced_collision", routes_forced_collision_path
+ assert_equal "/fc/1", routes_forced_collision_path(1)
+ end
+
+ def test_redirect_argument_error
+ routes = Class.new { include ActionDispatch::Routing::Redirection }.new
+ assert_raises(ArgumentError) { routes.redirect Object.new }
end
def test_explicitly_avoiding_the_named_route
@@ -2412,11 +2230,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal "/posts/1/admin", post_admin_root_path(:post_id => '1')
end
-private
- def with_test_routes
- yield
+ def test_custom_param
+ get '/profiles/bob'
+ assert_equal 'profiles#show', @response.body
+ assert_equal 'bob', @request.params[:username]
+
+ get '/profiles/bob/details'
+ assert_equal 'bob', @request.params[:username]
+
+ get '/profiles/bob/messages/34'
+ assert_equal 'bob', @request.params[:profile_username]
+ assert_equal '34', @request.params[:id]
end
+private
def with_https
old_https = https?
https!
@@ -2441,16 +2268,17 @@ class TestAppendingRoutes < ActionDispatch::IntegrationTest
lambda { |e| [ 200, { 'Content-Type' => 'text/plain' }, [resp] ] }
end
- setup do
+ def setup
+ super
s = self
@app = ActionDispatch::Routing::RouteSet.new
@app.append do
- match '/hello' => s.simple_app('fail')
- match '/goodbye' => s.simple_app('goodbye')
+ get '/hello' => s.simple_app('fail')
+ get '/goodbye' => s.simple_app('goodbye')
end
@app.draw do
- match '/hello' => s.simple_app('hello')
+ get '/hello' => s.simple_app('hello')
end
end
@@ -2470,6 +2298,81 @@ class TestAppendingRoutes < ActionDispatch::IntegrationTest
end
end
+class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest
+ module ::Admin
+ class StorageFilesController < ActionController::Base
+ def index
+ render :text => "admin/storage_files#index"
+ end
+ end
+ end
+
+ DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new
+ DefaultScopeRoutes.draw do
+ namespace :admin do
+ resources :storage_files, :controller => "StorageFiles"
+ end
+ end
+
+ def app
+ DefaultScopeRoutes
+ end
+
+ def test_controller_options
+ get '/admin/storage_files'
+ assert_equal "admin/storage_files#index", @response.body
+ end
+end
+
+class TestDrawExternalFile < ActionDispatch::IntegrationTest
+ class ExternalController < ActionController::Base
+ def index
+ render :text => "external#index"
+ end
+ end
+
+ DRAW_PATH = Pathname.new(File.expand_path('../../fixtures/routes', __FILE__))
+
+ DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw_paths << DRAW_PATH
+ end
+
+ def app
+ DefaultScopeRoutes
+ end
+
+ def test_draw_external_file
+ DefaultScopeRoutes.draw do
+ scope :module => 'test_draw_external_file' do
+ draw :external
+ end
+ end
+
+ get '/external'
+ assert_equal "external#index", @response.body
+ end
+
+ def test_draw_nonexistent_file
+ exception = assert_raise ArgumentError do
+ DefaultScopeRoutes.draw do
+ draw :nonexistent
+ end
+ end
+ assert_match 'Your router tried to #draw the external file nonexistent.rb', exception.message
+ assert_match DRAW_PATH.to_s, exception.message
+ end
+
+ def test_draw_bogus_file
+ exception = assert_raise NoMethodError do
+ DefaultScopeRoutes.draw do
+ draw :bogus
+ end
+ end
+ assert_match "undefined method `wrong'", exception.message
+ assert_match 'test/fixtures/routes/bogus.rb:1', exception.backtrace.first
+ end
+end
+
class TestDefaultScope < ActionDispatch::IntegrationTest
module ::Blog
class PostsController < ActionController::Base
@@ -2532,12 +2435,12 @@ end
class TestUriPathEscaping < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
app.draw do
- match '/:segment' => lambda { |env|
+ get '/:segment' => lambda { |env|
path_params = env['action_dispatch.request.path_parameters']
[200, { 'Content-Type' => 'text/plain' }, [path_params[:segment]]]
}, :as => :segment
- match '/*splat' => lambda { |env|
+ get '/*splat' => lambda { |env|
path_params = env['action_dispatch.request.path_parameters']
[200, { 'Content-Type' => 'text/plain' }, [path_params[:splat]]]
}, :as => :splat
@@ -2565,3 +2468,232 @@ class TestUriPathEscaping < ActionDispatch::IntegrationTest
assert_equal 'a b/c+d', @response.body
end
end
+
+class TestUnicodePaths < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ get "/#{Rack::Utils.escape("ほげ")}" => lambda { |env|
+ [200, { 'Content-Type' => 'text/plain' }, []]
+ }, :as => :unicode_path
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test 'recognizes unicode path' do
+ get "/#{Rack::Utils.escape("ほげ")}"
+ assert_equal "200", @response.code
+ end
+end
+
+class TestMultipleNestedController < ActionDispatch::IntegrationTest
+ module ::Foo
+ module Bar
+ class BazController < ActionController::Base
+ def index
+ render :inline => "<%= url_for :controller => '/pooh', :action => 'index' %>"
+ end
+ end
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ namespace :foo do
+ namespace :bar do
+ get "baz" => "baz#index"
+ end
+ end
+ get "pooh" => "pooh#index"
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test "controller option which starts with '/' from multiple nested controller" do
+ get "/foo/bar/baz"
+ assert_equal "/pooh", @response.body
+ end
+end
+
+class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/~user" => ok
+ get "/young-and-fine" => ok
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test 'recognizes tilde path' do
+ get "/~user"
+ assert_equal "200", @response.code
+ end
+
+ test 'recognizes minus path' do
+ get "/young-and-fine"
+ assert_equal "200", @response.code
+ end
+
+end
+
+class TestRedirectInterpolation < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/foo/:id" => redirect("/foo/bar/%{id}")
+ get "/bar/:id" => redirect(:path => "/foo/bar/%{id}")
+ get "/foo/bar/:id" => ok
+ end
+ end
+
+ def app; Routes end
+
+ test "redirect escapes interpolated parameters with redirect proc" do
+ get "/foo/1%3E"
+ verify_redirect "http://www.example.com/foo/bar/1%3E"
+ end
+
+ test "redirect escapes interpolated parameters with option proc" do
+ get "/bar/1%3E"
+ verify_redirect "http://www.example.com/foo/bar/1%3E"
+ end
+
+private
+ def verify_redirect(url, status=301)
+ assert_equal status, @response.status
+ assert_equal url, @response.headers['Location']
+ assert_equal expected_redirect_body(url), @response.body
+ end
+
+ def expected_redirect_body(url)
+ %(<html><body>You are being <a href="#{ERB::Util.h(url)}">redirected</a>.</body></html>)
+ end
+end
+
+class TestConstraintsAccessingParameters < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get "/:foo" => ok, :constraints => lambda { |r| r.params[:foo] == 'foo' }
+ get "/:bar" => ok
+ end
+ end
+
+ def app; Routes end
+
+ test "parameters are reset between constraint checks" do
+ get "/bar"
+ assert_equal nil, @request.params[:foo]
+ assert_equal "bar", @request.params[:bar]
+ end
+end
+
+class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+ get '/foo' => ok, as: :foo
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test 'enabled when not mounted and default_url_options is empty' do
+ assert Routes.url_helpers.optimize_routes_generation?
+ end
+
+ test 'named route called as singleton method' do
+ assert_equal '/foo', Routes.url_helpers.foo_path
+ end
+
+ test 'named route called on included module' do
+ assert_equal '/foo', foo_path
+ end
+end
+
+class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest
+ class CategoriesController < ActionController::Base
+ def show
+ render :text => "categories#show"
+ end
+ end
+
+ class ProductsController < ActionController::Base
+ def show
+ render :text => "products#show"
+ end
+ end
+
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ scope :module => "test_named_route_url_helpers" do
+ get "/categories/:id" => 'categories#show', :as => :category
+ get "/products/:id" => 'products#show', :as => :product
+ end
+ end
+ end
+
+ def app; Routes end
+
+ include Routes.url_helpers
+
+ test "url helpers do not ignore nil parameters when using non-optimized routes" do
+ Routes.stubs(:optimize_routes_generation?).returns(false)
+
+ get "/categories/1"
+ assert_response :success
+ assert_raises(ActionController::RoutingError) { product_path(nil) }
+ end
+end
+
+class TestUrlConstraints < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new.tap do |app|
+ app.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ constraints :subdomain => 'admin' do
+ get '/' => ok, :as => :admin_root
+ end
+
+ scope :constraints => { :protocol => 'https://' } do
+ get '/' => ok, :as => :secure_root
+ end
+
+ get '/' => ok, :as => :alternate_root, :constraints => { :port => 8080 }
+ end
+ end
+
+ include Routes.url_helpers
+ def app; Routes end
+
+ test "constraints are copied to defaults when using constraints method" do
+ assert_equal 'http://admin.example.com/', admin_root_url
+
+ get 'http://admin.example.com/'
+ assert_response :success
+ end
+
+ test "constraints are copied to defaults when using scope constraints hash" do
+ assert_equal 'https://www.example.com/', secure_root_url
+
+ get 'https://www.example.com/'
+ assert_response :success
+ end
+
+ test "constraints are copied to defaults when using route constraints hash" do
+ assert_equal 'http://www.example.com:8080/', alternate_root_url
+
+ get 'http://www.example.com:8080/'
+ assert_response :success
+ end
+end
diff --git a/actionpack/test/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb
new file mode 100644
index 0000000000..8daf3d3f5e
--- /dev/null
+++ b/actionpack/test/dispatch/session/abstract_store_test.rb
@@ -0,0 +1,56 @@
+require 'abstract_unit'
+require 'action_dispatch/middleware/session/abstract_store'
+
+module ActionDispatch
+ module Session
+ class AbstractStoreTest < ActiveSupport::TestCase
+ class MemoryStore < AbstractStore
+ def initialize(app)
+ @sessions = {}
+ super
+ end
+
+ def get_session(env, sid)
+ sid ||= 1
+ session = @sessions[sid] ||= {}
+ [sid, session]
+ end
+
+ def set_session(env, sid, session, options)
+ @sessions[sid] = session
+ end
+ end
+
+ def test_session_is_set
+ env = {}
+ as = MemoryStore.new app
+ as.call(env)
+
+ assert @env
+ assert Request::Session.find @env
+ end
+
+ def test_new_session_object_is_merged_with_old
+ env = {}
+ as = MemoryStore.new app
+ as.call(env)
+
+ assert @env
+ session = Request::Session.find @env
+ session['foo'] = 'bar'
+
+ as.call(@env)
+ session1 = Request::Session.find @env
+
+ refute_equal session, session1
+ assert_equal session.to_hash, session1.to_hash
+ end
+
+ private
+ def app(&block)
+ @env = nil
+ lambda { |env| @env = env }
+ end
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
new file mode 100644
index 0000000000..a74e165826
--- /dev/null
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -0,0 +1,179 @@
+require 'abstract_unit'
+
+class CacheStoreTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def no_session_access
+ head :ok
+ end
+
+ def set_session_value
+ session[:foo] = "bar"
+ head :ok
+ end
+
+ def set_serialized_session_value
+ session[:foo] = SessionAutoloadTest::Foo.new
+ head :ok
+ end
+
+ def get_session_value
+ render :text => "foo: #{session[:foo].inspect}"
+ end
+
+ def get_session_id
+ render :text => "#{request.session_options[:id]}"
+ end
+
+ def call_reset_session
+ session[:bar]
+ reset_session
+ session[:bar] = "baz"
+ head :ok
+ end
+ end
+
+ def test_setting_and_getting_session_value
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: "bar"', response.body
+ end
+ end
+
+ def test_getting_nil_session_value
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ end
+ end
+
+ def test_getting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_cookie = cookies.send(:hash_for)['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from cache"
+ end
+ end
+
+ def test_getting_from_nonexistent_session
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ assert_nil cookies['_session_id'], "should only create session on write, not read"
+ end
+ end
+
+ def test_setting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_id = cookies['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+
+ get '/get_session_id'
+ assert_response :success
+ assert_not_equal session_id, response.body
+ end
+ end
+
+ def test_getting_session_id
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_id = cookies['_session_id']
+
+ get '/get_session_id'
+ assert_response :success
+ assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
+ end
+ end
+
+ def test_deserializes_unloaded_class
+ with_test_route_set do
+ with_autoload_path "session_autoload_test" do
+ get '/set_serialized_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_id'
+ assert_response :success
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
+ end
+ end
+ end
+
+ def test_doesnt_write_session_cookie_if_session_id_is_already_exists
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
+ end
+ end
+
+ def test_prevents_session_fixation
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ session_id = cookies['_session_id']
+
+ reset!
+
+ get '/set_session_value', :_session_id => session_id
+ assert_response :success
+ assert_not_equal session_id, cookies['_session_id']
+ end
+ end
+
+ private
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ get ':action', :to => ::CacheStoreTest::TestController
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ cache = ActiveSupport::Cache::MemoryStore.new
+ middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => cache
+ middleware.delete "ActionDispatch::ShowExceptions"
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index 92df6967d6..631974d6c4 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -54,8 +54,6 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
request.session_options[:renew] = true
head :ok
end
-
- def rescue_action(e) raise end
end
def test_setting_session_value
@@ -319,7 +317,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set(options = {})
with_routing do |set|
set.draw do
- match ':action', :to => ::CookieStoreTest::TestController
+ get ':action', :to => ::CookieStoreTest::TestController
end
options = { :key => SessionKey }.merge!(options)
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 8502bc547b..03234612ab 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -31,8 +31,6 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
session[:bar] = "baz"
head :ok
end
-
- def rescue_action(e) raise end
end
begin
@@ -175,7 +173,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do
- match ':action', :to => ::MemCacheStoreTest::TestController
+ get ':action', :to => ::MemCacheStoreTest::TestController
end
@app = self.class.build_app(set) do |middleware|
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 42f6c7f79f..45f8fc11b3 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -2,32 +2,33 @@ require 'abstract_unit'
class ShowExceptionsTest < ActionDispatch::IntegrationTest
- Boomer = lambda do |env|
- req = ActionDispatch::Request.new(env)
- case req.path
- when "/not_found"
- raise ActionController::UnknownAction
- when "/runtime_error"
- raise RuntimeError
- when "/method_not_allowed"
- raise ActionController::MethodNotAllowed
- when "/not_implemented"
- raise ActionController::NotImplemented
- when "/unprocessable_entity"
- raise ActionController::InvalidAuthenticityToken
- when "/not_found_original_exception"
- raise ActionView::Template::Error.new('template', {}, AbstractController::ActionNotFound.new)
- else
- raise "puke!"
+ class Boomer
+ def call(env)
+ req = ActionDispatch::Request.new(env)
+ case req.path
+ when "/not_found"
+ raise AbstractController::ActionNotFound
+ when "/method_not_allowed"
+ raise ActionController::MethodNotAllowed
+ when "/not_found_original_exception"
+ raise ActionView::Template::Error.new('template', AbstractController::ActionNotFound.new)
+ else
+ raise "puke!"
+ end
end
end
- ProductionApp = ActionDispatch::ShowExceptions.new(Boomer, false)
- DevelopmentApp = ActionDispatch::ShowExceptions.new(Boomer, true)
+ ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public"))
- test "rescue in public from a remote ip" do
+ test "skip exceptions app if not showing exceptions" do
+ @app = ProductionApp
+ assert_raise RuntimeError do
+ get "/", {}, {'action_dispatch.show_exceptions' => false}
+ end
+ end
+
+ test "rescue with error page" do
@app = ProductionApp
- self.remote_addr = '208.77.188.166'
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_response 500
@@ -42,32 +43,11 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
assert_equal "", body
end
- test "rescue locally from a local request" do
- @app = ProductionApp
- ['127.0.0.1', '127.0.0.127', '::1', '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1%0'].each do |ip_address|
- self.remote_addr = ip_address
-
- get "/", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 500
- assert_match(/puke/, body)
-
- get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 404
- assert_match(/#{ActionController::UnknownAction.name}/, body)
-
- get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 405
- assert_match(/ActionController::MethodNotAllowed/, body)
- end
- end
-
- test "localize public rescue message" do
- # Change locale
+ test "localize rescue error page" do
old_locale, I18n.locale = I18n.locale, :da
begin
@app = ProductionApp
- self.remote_addr = '208.77.188.166'
get "/", {}, {'action_dispatch.show_exceptions' => true}
assert_response 500
@@ -81,67 +61,42 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
end
end
- test "always rescue locally in development mode" do
- @app = DevelopmentApp
- self.remote_addr = '208.77.188.166'
+ test "sets the HTTP charset parameter" do
+ @app = ProductionApp
get "/", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 500
- assert_match(/puke/, body)
-
- get "/not_found", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 404
- assert_match(/#{ActionController::UnknownAction.name}/, body)
-
- get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
- assert_response 405
- assert_match(/ActionController::MethodNotAllowed/, body)
- end
-
- test "does not show filtered parameters" do
- @app = DevelopmentApp
-
- get "/", {"foo"=>"bar"}, {'action_dispatch.show_exceptions' => true,
- 'action_dispatch.parameter_filter' => [:foo]}
- assert_response 500
- assert_match("&quot;foo&quot;=&gt;&quot;[FILTERED]&quot;", body)
+ assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
end
- test "show registered original exception for wrapped exceptions when consider_all_requests_local is false" do
+ test "show registered original exception for wrapped exceptions" do
@app = ProductionApp
- self.remote_addr = '208.77.188.166'
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
assert_response 404
assert_match(/404 error/, body)
end
- test "show registered original exception for wrapped exceptions when consider_all_requests_local is true" do
- @app = DevelopmentApp
+ test "calls custom exceptions app" do
+ exceptions_app = lambda do |env|
+ assert_kind_of AbstractController::ActionNotFound, env["action_dispatch.exception"]
+ assert_equal "/404", env["PATH_INFO"]
+ [404, { "Content-Type" => "text/plain" }, ["YOU FAILED BRO"]]
+ end
+ @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app)
get "/not_found_original_exception", {}, {'action_dispatch.show_exceptions' => true}
assert_response 404
- assert_match(/AbstractController::ActionNotFound/, body)
- end
-
- test "show the controller name in the diagnostics template when controller name is present" do
- @app = ProductionApp
- get("/runtime_error", {}, {
- 'action_dispatch.show_exceptions' => true,
- 'action_dispatch.request.parameters' => {
- 'action' => 'show',
- 'id' => 'unknown',
- 'controller' => 'featured_tile'
- }
- })
- assert_response 500
- assert_match(/RuntimeError\n in FeaturedTileController/, body)
+ assert_equal "YOU FAILED BRO", body
end
- test "sets the HTTP charset parameter" do
- @app = DevelopmentApp
+ test "returns an empty response if custom exceptions app returns X-Cascade pass" do
+ exceptions_app = lambda do |env|
+ [404, { "X-Cascade" => "pass" }, []]
+ end
- get "/", {}, {'action_dispatch.show_exceptions' => true}
- assert_equal "text/html; charset=utf-8", response.headers["Content-Type"]
+ @app = ActionDispatch::ShowExceptions.new(Boomer.new, exceptions_app)
+ get "/method_not_allowed", {}, {'action_dispatch.show_exceptions' => true}
+ assert_response 405
+ assert_equal "", body
end
end
diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb
new file mode 100644
index 0000000000..6f075a9074
--- /dev/null
+++ b/actionpack/test/dispatch/ssl_test.rb
@@ -0,0 +1,157 @@
+require 'abstract_unit'
+
+class SSLTest < ActionDispatch::IntegrationTest
+ def default_app
+ lambda { |env|
+ headers = {'Content-Type' => "text/html"}
+ headers['Set-Cookie'] = "id=1; path=/\ntoken=abc; path=/; secure; HttpOnly"
+ [200, headers, ["OK"]]
+ }
+ end
+
+ def app
+ @app ||= ActionDispatch::SSL.new(default_app)
+ end
+ attr_writer :app
+
+ def test_allows_https_url
+ get "https://example.org/path?key=value"
+ assert_response :success
+ end
+
+ def test_allows_https_proxy_header_url
+ get "http://example.org/", {}, 'HTTP_X_FORWARDED_PROTO' => "https"
+ assert_response :success
+ end
+
+ def test_redirects_http_to_https
+ get "http://example.org/path?key=value"
+ assert_response :redirect
+ assert_equal "https://example.org/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_hsts_header_by_default
+ get "https://example.org/"
+ assert_equal "max-age=31536000",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_hsts_header
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => true)
+ get "https://example.org/"
+ assert_equal "max-age=31536000",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_disable_hsts_header
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => false)
+ get "https://example.org/"
+ refute response.headers['Strict-Transport-Security']
+ end
+
+ def test_hsts_expires
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => { :expires => 500 })
+ get "https://example.org/"
+ assert_equal "max-age=500",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_hsts_include_subdomains
+ self.app = ActionDispatch::SSL.new(default_app, :hsts => { :subdomains => true })
+ get "https://example.org/"
+ assert_equal "max-age=31536000; includeSubDomains",
+ response.headers['Strict-Transport-Security']
+ end
+
+ def test_flag_cookies_as_secure
+ get "https://example.org/"
+ assert_equal ["id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly" ],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_at_end_of_line
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; HttpOnly; secure"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; HttpOnly; secure"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_with_more_spaces_before
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; HttpOnly; secure"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; HttpOnly; secure"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_flag_cookies_as_secure_with_more_spaces_after
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ headers = {
+ 'Content-Type' => "text/html",
+ 'Set-Cookie' => "problem=def; path=/; secure; HttpOnly"
+ }
+ [200, headers, ["OK"]]
+ })
+
+ get "https://example.org/"
+ assert_equal ["problem=def; path=/; secure; HttpOnly"],
+ response.headers['Set-Cookie'].split("\n")
+ end
+
+ def test_no_cookies
+ self.app = ActionDispatch::SSL.new(lambda { |env|
+ [200, {'Content-Type' => "text/html"}, ["OK"]]
+ })
+ get "https://example.org/"
+ assert !response.headers['Set-Cookie']
+ end
+
+ def test_redirect_to_host
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org")
+ get "http://example.org/path?key=value"
+ assert_equal "https://ssl.example.org/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_port
+ self.app = ActionDispatch::SSL.new(default_app, :port => 8443)
+ get "http://example.org/path?key=value"
+ assert_equal "https://example.org:8443/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_host_and_port
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org", :port => 8443)
+ get "http://example.org/path?key=value"
+ assert_equal "https://ssl.example.org:8443/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_secure_host_when_on_subdomain
+ self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org")
+ get "http://ssl.example.org/path?key=value"
+ assert_equal "https://ssl.example.org/path?key=value",
+ response.headers['Location']
+ end
+
+ def test_redirect_to_secure_subdomain_when_on_deep_subdomain
+ self.app = ActionDispatch::SSL.new(default_app, :host => "example.co.uk")
+ get "http://double.rainbow.what.does.it.mean.example.co.uk/path?key=value"
+ assert_equal "https://example.co.uk/path?key=value",
+ response.headers['Location']
+ end
+end
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 9f3cbd19ef..112f470786 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -1,10 +1,16 @@
+# encoding: utf-8
require 'abstract_unit'
+require 'rbconfig'
module StaticTests
def test_serves_dynamic_content
assert_equal "Hello, World!", get("/nofile").body
end
+ def test_handles_urls_with_bad_encoding
+ assert_equal "Hello, World!", get("/doorkeeper%E3E4").body
+ end
+
def test_sets_cache_control
response = get("/index.html")
assert_html "/index.html", response
@@ -30,6 +36,91 @@ module StaticTests
assert_html "/foo/index.html", get("/foo")
end
+ def test_served_static_file_with_non_english_filename
+ assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("こんにちは.html")}")
+ end
+
+
+ def test_serves_static_file_with_exclamation_mark_in_filename
+ with_static_file "/foo/foo!bar.html" do |file|
+ assert_html file, get("/foo/foo%21bar.html")
+ assert_html file, get("/foo/foo!bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_dollar_sign_in_filename
+ with_static_file "/foo/foo$bar.html" do |file|
+ assert_html file, get("/foo/foo%24bar.html")
+ assert_html file, get("/foo/foo$bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_ampersand_in_filename
+ with_static_file "/foo/foo&bar.html" do |file|
+ assert_html file, get("/foo/foo%26bar.html")
+ assert_html file, get("/foo/foo&bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_apostrophe_in_filename
+ with_static_file "/foo/foo'bar.html" do |file|
+ assert_html file, get("/foo/foo%27bar.html")
+ assert_html file, get("/foo/foo'bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_parentheses_in_filename
+ with_static_file "/foo/foo(bar).html" do |file|
+ assert_html file, get("/foo/foo%28bar%29.html")
+ assert_html file, get("/foo/foo(bar).html")
+ end
+ end
+
+ def test_serves_static_file_with_plus_sign_in_filename
+ with_static_file "/foo/foo+bar.html" do |file|
+ assert_html file, get("/foo/foo%2Bbar.html")
+ assert_html file, get("/foo/foo+bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_comma_in_filename
+ with_static_file "/foo/foo,bar.html" do |file|
+ assert_html file, get("/foo/foo%2Cbar.html")
+ assert_html file, get("/foo/foo,bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_semi_colon_in_filename
+ with_static_file "/foo/foo;bar.html" do |file|
+ assert_html file, get("/foo/foo%3Bbar.html")
+ assert_html file, get("/foo/foo;bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_at_symbol_in_filename
+ with_static_file "/foo/foo@bar.html" do |file|
+ assert_html file, get("/foo/foo%40bar.html")
+ assert_html file, get("/foo/foo@bar.html")
+ end
+ end
+
+ # Windows doesn't allow \ / : * ? " < > | in filenames
+ unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+ def test_serves_static_file_with_colon
+ with_static_file "/foo/foo:bar.html" do |file|
+ assert_html file, get("/foo/foo%3Abar.html")
+ assert_html file, get("/foo/foo:bar.html")
+ end
+ end
+
+ def test_serves_static_file_with_asterisk
+ with_static_file "/foo/foo*bar.html" do |file|
+ assert_html file, get("/foo/foo%2Abar.html")
+ assert_html file, get("/foo/foo*bar.html")
+ end
+ end
+ end
+
private
def assert_html(body, response)
@@ -40,6 +131,14 @@ module StaticTests
def get(path)
Rack::MockRequest.new(@app).request("GET", path)
end
+
+ def with_static_file(file)
+ path = "#{FIXTURE_LOAD_PATH}/public" + file
+ File.open(path, "wb+") { |f| f.write(file) }
+ yield file
+ ensure
+ File.delete(path)
+ end
end
class StaticTest < ActiveSupport::TestCase
@@ -53,4 +152,4 @@ class StaticTest < ActiveSupport::TestCase
end
include StaticTests
-end \ No newline at end of file
+end
diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb
index 7e4a1519fb..e69c1fbed4 100644
--- a/actionpack/test/dispatch/uploaded_file_test.rb
+++ b/actionpack/test/dispatch/uploaded_file_test.rb
@@ -12,12 +12,10 @@ module ActionDispatch
uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new)
assert_equal 'foo', uf.original_filename
end
-
- if "ruby".encoding_aware?
- def test_filename_should_be_in_utf_8
- uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new)
- assert_equal "UTF-8", uf.original_filename.encoding.to_s
- end
+
+ def test_filename_should_be_in_utf_8
+ uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new)
+ assert_equal "UTF-8", uf.original_filename.encoding.to_s
end
def test_content_type
@@ -67,6 +65,12 @@ module ActionDispatch
end
end
+ def test_delegate_eof_to_tempfile
+ tf = Class.new { def eof?; true end; }
+ uf = Http::UploadedFile.new(:tempfile => tf.new)
+ assert uf.eof?
+ end
+
def test_respond_to?
tf = Class.new { def read; yield end }
uf = Http::UploadedFile.new(:tempfile => tf.new)
diff --git a/actionpack/test/dispatch/url_generation_test.rb b/actionpack/test/dispatch/url_generation_test.rb
index 2b54bc62b0..e56e8ddc57 100644
--- a/actionpack/test/dispatch/url_generation_test.rb
+++ b/actionpack/test/dispatch/url_generation_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
module TestUrlGeneration
class WithMountPoint < ActionDispatch::IntegrationTest
Routes = ActionDispatch::Routing::RouteSet.new
- Routes.draw { match "/foo", :to => "my_route_generating#index", :as => :foo }
+ include Routes.url_helpers
class ::MyRouteGeneratingController < ActionController::Base
include Routes.url_helpers
@@ -12,7 +12,11 @@ module TestUrlGeneration
end
end
- include Routes.url_helpers
+ Routes.draw do
+ get "/foo", :to => "my_route_generating#index", :as => :foo
+
+ mount MyRouteGeneratingController.action(:index), at: '/bar'
+ end
def _routes
Routes
@@ -30,11 +34,16 @@ module TestUrlGeneration
assert_equal "/bar/foo", foo_path(:script_name => "/bar")
end
- test "the request's SCRIPT_NAME takes precedence over the routes'" do
+ test "the request's SCRIPT_NAME takes precedence over the route" do
get "/foo", {}, 'SCRIPT_NAME' => "/new", 'action_dispatch.routes' => Routes
assert_equal "/new/foo", response.body
end
+ test "the request's SCRIPT_NAME wraps the mounted app's" do
+ get '/new/bar/foo', {}, 'SCRIPT_NAME' => '/new', 'PATH_INFO' => '/bar/foo', 'action_dispatch.routes' => Routes
+ assert_equal "/new/bar/foo", response.body
+ end
+
test "handling http protocol with https set" do
https!
assert_equal "http://www.example.com/foo", foo_url(:protocol => "http")
diff --git a/actionpack/test/fixtures/addresses/list.erb b/actionpack/test/fixtures/addresses/list.erb
deleted file mode 100644
index c75e01eece..0000000000
--- a/actionpack/test/fixtures/addresses/list.erb
+++ /dev/null
@@ -1 +0,0 @@
-We only need to get this far!
diff --git a/actionpack/test/fixtures/company.rb b/actionpack/test/fixtures/company.rb
index cd39ea7898..e29978801e 100644
--- a/actionpack/test/fixtures/company.rb
+++ b/actionpack/test/fixtures/company.rb
@@ -1,10 +1,10 @@
class Company < ActiveRecord::Base
has_one :mascot
attr_protected :rating
- set_sequence_name :companies_nonstd_seq
+ self.sequence_name = :companies_nonstd_seq
validates_presence_of :name
def validate
errors.add('rating', 'rating should not be 2') if rating == 2
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/fixtures/developer.rb b/actionpack/test/fixtures/developer.rb
index c70eda34c6..dd14548fac 100644
--- a/actionpack/test/fixtures/developer.rb
+++ b/actionpack/test/fixtures/developer.rb
@@ -5,5 +5,5 @@ class Developer < ActiveRecord::Base
end
class DeVeLoPeR < ActiveRecord::Base
- set_table_name "developers"
+ self.table_name = "developers"
end
diff --git a/actionpack/test/fixtures/developers.yml b/actionpack/test/fixtures/developers.yml
index 308bf75de2..3656564f63 100644
--- a/actionpack/test/fixtures/developers.yml
+++ b/actionpack/test/fixtures/developers.yml
@@ -8,7 +8,7 @@ jamis:
name: Jamis
salary: 150000
-<% for digit in 3..10 %>
+<% (3..10).each do |digit| %>
dev_<%= digit %>:
id: <%= digit %>
name: fixture_<%= digit %>
diff --git a/actionpack/test/fixtures/fun/games/_game.erb b/actionpack/test/fixtures/fun/games/_game.erb
index d51b7b3ebc..f0f542ff92 100644
--- a/actionpack/test/fixtures/fun/games/_game.erb
+++ b/actionpack/test/fixtures/fun/games/_game.erb
@@ -1 +1 @@
-<%= game.name %> \ No newline at end of file
+Fun <%= game.name %>
diff --git a/actionpack/test/fixtures/fun/serious/games/_game.erb b/actionpack/test/fixtures/fun/serious/games/_game.erb
index d51b7b3ebc..523bc55bd7 100644
--- a/actionpack/test/fixtures/fun/serious/games/_game.erb
+++ b/actionpack/test/fixtures/fun/serious/games/_game.erb
@@ -1 +1 @@
-<%= game.name %> \ No newline at end of file
+Serious <%= game.name %>
diff --git a/actionpack/test/fixtures/games/_game.erb b/actionpack/test/fixtures/games/_game.erb
new file mode 100644
index 0000000000..1aeb81fcba
--- /dev/null
+++ b/actionpack/test/fixtures/games/_game.erb
@@ -0,0 +1 @@
+Just <%= game.name %>
diff --git a/actionpack/test/fixtures/layouts/with_html_partial.html.erb b/actionpack/test/fixtures/layouts/with_html_partial.html.erb
new file mode 100644
index 0000000000..fd2896aeaa
--- /dev/null
+++ b/actionpack/test/fixtures/layouts/with_html_partial.html.erb
@@ -0,0 +1 @@
+<%= render :partial => "partial_only_html" %><%= yield %>
diff --git a/actionpack/test/fixtures/plain_text.raw b/actionpack/test/fixtures/plain_text.raw
new file mode 100644
index 0000000000..b13985337f
--- /dev/null
+++ b/actionpack/test/fixtures/plain_text.raw
@@ -0,0 +1 @@
+<%= hello_world %>
diff --git a/actionpack/test/fixtures/plain_text_with_characters.raw b/actionpack/test/fixtures/plain_text_with_characters.raw
new file mode 100644
index 0000000000..1e86e44fb4
--- /dev/null
+++ b/actionpack/test/fixtures/plain_text_with_characters.raw
@@ -0,0 +1 @@
+Here are some characters: !@#$%^&*()-="'}{`
diff --git a/actionpack/test/fixtures/public/foo/こんにちは.html b/actionpack/test/fixtures/public/foo/こんにちは.html
new file mode 100644
index 0000000000..1df9166522
--- /dev/null
+++ b/actionpack/test/fixtures/public/foo/こんにちは.html
@@ -0,0 +1 @@
+means hello in Japanese
diff --git a/actionpack/test/fixtures/reply.rb b/actionpack/test/fixtures/reply.rb
index 19cba93673..0d3b0a7c98 100644
--- a/actionpack/test/fixtures/reply.rb
+++ b/actionpack/test/fixtures/reply.rb
@@ -1,5 +1,5 @@
class Reply < ActiveRecord::Base
- scope :base
+ scope :base, -> { scoped }
belongs_to :topic, :include => [:replies]
belongs_to :developer
diff --git a/actionpack/test/fixtures/routes/bogus.rb b/actionpack/test/fixtures/routes/bogus.rb
new file mode 100644
index 0000000000..41fbf0cd64
--- /dev/null
+++ b/actionpack/test/fixtures/routes/bogus.rb
@@ -0,0 +1 @@
+wrong :route
diff --git a/actionpack/test/fixtures/routes/external.rb b/actionpack/test/fixtures/routes/external.rb
new file mode 100644
index 0000000000..d103c39f53
--- /dev/null
+++ b/actionpack/test/fixtures/routes/external.rb
@@ -0,0 +1 @@
+get '/external' => 'external#index'
diff --git a/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css b/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css
deleted file mode 100644
index bfb90bfa48..0000000000
--- a/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css
+++ /dev/null
@@ -1 +0,0 @@
-/* Different from other style.css */ \ No newline at end of file
diff --git a/actionpack/test/fixtures/sprockets/app/images/logo.png b/actionpack/test/fixtures/sprockets/app/images/logo.png
deleted file mode 100644
index d5edc04e65..0000000000
--- a/actionpack/test/fixtures/sprockets/app/images/logo.png
+++ /dev/null
Binary files differ
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/application.js b/actionpack/test/fixtures/sprockets/app/javascripts/application.js
deleted file mode 100644
index e611d2b129..0000000000
--- a/actionpack/test/fixtures/sprockets/app/javascripts/application.js
+++ /dev/null
@@ -1 +0,0 @@
-//= require xmlhr
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js b/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js
deleted file mode 100644
index e69de29bb2..0000000000
--- a/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js
+++ /dev/null
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/extra.js b/actionpack/test/fixtures/sprockets/app/javascripts/extra.js
deleted file mode 100644
index e69de29bb2..0000000000
--- a/actionpack/test/fixtures/sprockets/app/javascripts/extra.js
+++ /dev/null
diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js b/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js
deleted file mode 100644
index e69de29bb2..0000000000
--- a/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js
+++ /dev/null
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/application.css b/actionpack/test/fixtures/sprockets/app/stylesheets/application.css
deleted file mode 100644
index 2365eaa4cd..0000000000
--- a/actionpack/test/fixtures/sprockets/app/stylesheets/application.css
+++ /dev/null
@@ -1 +0,0 @@
-/*= require style */
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css b/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css
deleted file mode 100644
index e69de29bb2..0000000000
--- a/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css
+++ /dev/null
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css b/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css
deleted file mode 100644
index e69de29bb2..0000000000
--- a/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css
+++ /dev/null
diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/style.css b/actionpack/test/fixtures/sprockets/app/stylesheets/style.css
deleted file mode 100644
index e69de29bb2..0000000000
--- a/actionpack/test/fixtures/sprockets/app/stylesheets/style.css
+++ /dev/null
diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial.html.erb b/actionpack/test/fixtures/test/_b_layout_for_partial.html.erb
new file mode 100644
index 0000000000..e918ba8f83
--- /dev/null
+++ b/actionpack/test/fixtures/test/_b_layout_for_partial.html.erb
@@ -0,0 +1 @@
+<b><%= yield %></b> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb
new file mode 100644
index 0000000000..bdd53014cd
--- /dev/null
+++ b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object.html.erb
@@ -0,0 +1 @@
+<b class="<%= customer.name.downcase %>"><%= yield %></b> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb
new file mode 100644
index 0000000000..44d6121297
--- /dev/null
+++ b/actionpack/test/fixtures/test/_b_layout_for_partial_with_object_counter.html.erb
@@ -0,0 +1 @@
+<b data-counter="<%= customer_counter %>"><%= yield %></b> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_content_tag_nested_in_content_tag.erb b/actionpack/test/fixtures/test/_content_tag_nested_in_content_tag.erb
new file mode 100644
index 0000000000..2f21a75dd9
--- /dev/null
+++ b/actionpack/test/fixtures/test/_content_tag_nested_in_content_tag.erb
@@ -0,0 +1,3 @@
+<%= content_tag 'p' do %>
+ <%= content_tag 'b', 'Hello' %>
+<% end %>
diff --git a/actionpack/test/fixtures/test/_label_with_block.erb b/actionpack/test/fixtures/test/_label_with_block.erb
new file mode 100644
index 0000000000..40117e594e
--- /dev/null
+++ b/actionpack/test/fixtures/test/_label_with_block.erb
@@ -0,0 +1,4 @@
+<%= label 'post', 'message' do %>
+ Message
+ <%= text_field 'post', 'message' %>
+<% end %>
diff --git a/actionpack/test/fixtures/test/_partial_html_erb.html.erb b/actionpack/test/fixtures/test/_partial_html_erb.html.erb
new file mode 100644
index 0000000000..4b54875782
--- /dev/null
+++ b/actionpack/test/fixtures/test/_partial_html_erb.html.erb
@@ -0,0 +1 @@
+<%= "partial.html.erb" %>
diff --git a/actionpack/test/fixtures/test/_partial_only_html.html b/actionpack/test/fixtures/test/_partial_only_html.html
new file mode 100644
index 0000000000..d2d630bd40
--- /dev/null
+++ b/actionpack/test/fixtures/test/_partial_only_html.html
@@ -0,0 +1 @@
+only html partial \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello_world_with_partial.html.erb b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb
new file mode 100644
index 0000000000..ec31545356
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_world_with_partial.html.erb
@@ -0,0 +1,2 @@
+Hello world!
+<%= render '/test/partial' %>
diff --git a/actionpack/test/fixtures/test/one.html.erb b/actionpack/test/fixtures/test/one.html.erb
new file mode 100644
index 0000000000..0151874809
--- /dev/null
+++ b/actionpack/test/fixtures/test/one.html.erb
@@ -0,0 +1 @@
+<%= render :partial => "test/two" %> world \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/with_html_partial.html.erb b/actionpack/test/fixtures/test/with_html_partial.html.erb
new file mode 100644
index 0000000000..d84d909d64
--- /dev/null
+++ b/actionpack/test/fixtures/test/with_html_partial.html.erb
@@ -0,0 +1 @@
+<strong><%= render :partial => "partial_only_html" %></strong>
diff --git a/actionpack/test/fixtures/test/with_partial.html.erb b/actionpack/test/fixtures/test/with_partial.html.erb
new file mode 100644
index 0000000000..7502364cf5
--- /dev/null
+++ b/actionpack/test/fixtures/test/with_partial.html.erb
@@ -0,0 +1 @@
+<strong><%= render :partial => "partial_only" %></strong>
diff --git a/actionpack/test/fixtures/test/with_partial.text.erb b/actionpack/test/fixtures/test/with_partial.text.erb
new file mode 100644
index 0000000000..5f068ebf27
--- /dev/null
+++ b/actionpack/test/fixtures/test/with_partial.text.erb
@@ -0,0 +1 @@
+**<%= render :partial => "partial_only" %>**
diff --git a/actionpack/test/fixtures/test/with_xml_template.html.erb b/actionpack/test/fixtures/test/with_xml_template.html.erb
new file mode 100644
index 0000000000..e54a7cd001
--- /dev/null
+++ b/actionpack/test/fixtures/test/with_xml_template.html.erb
@@ -0,0 +1 @@
+<%= render :template => "test/greeting", :formats => :xml %>
diff --git a/actionpack/test/fixtures/translations/templates/default.erb b/actionpack/test/fixtures/translations/templates/default.erb
new file mode 100644
index 0000000000..8b70031071
--- /dev/null
+++ b/actionpack/test/fixtures/translations/templates/default.erb
@@ -0,0 +1 @@
+<%= t('.missing', :default => :'.foo') %>
diff --git a/actionpack/test/fixtures/with_format.json.erb b/actionpack/test/fixtures/with_format.json.erb
new file mode 100644
index 0000000000..a7f480ab1d
--- /dev/null
+++ b/actionpack/test/fixtures/with_format.json.erb
@@ -0,0 +1 @@
+<%= render :partial => 'missing', :formats => [:json] %>
diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb
index 09692f77b5..1a2863b689 100644
--- a/actionpack/test/lib/controller/fake_controllers.rb
+++ b/actionpack/test/lib/controller/fake_controllers.rb
@@ -1,11 +1,7 @@
-class << Object; alias_method :const_available?, :const_defined?; end
-
class ContentController < ActionController::Base; end
module Admin
- class << self; alias_method :const_available?, :const_defined?; end
class AccountsController < ActionController::Base; end
- class NewsFeedController < ActionController::Base; end
class PostsController < ActionController::Base; end
class StuffController < ActionController::Base; end
class UserController < ActionController::Base; end
@@ -17,46 +13,23 @@ module Api
class ProductsController < ActionController::Base; end
end
-# TODO: Reduce the number of test controllers we use
class AccountController < ActionController::Base; end
-class AddressesController < ActionController::Base; end
class ArchiveController < ActionController::Base; end
class ArticlesController < ActionController::Base; end
class BarController < ActionController::Base; end
class BlogController < ActionController::Base; end
class BooksController < ActionController::Base; end
-class BraveController < ActionController::Base; end
class CarsController < ActionController::Base; end
class CcController < ActionController::Base; end
class CController < ActionController::Base; end
-class ElsewhereController < ActionController::Base; end
class FooController < ActionController::Base; end
class GeocodeController < ActionController::Base; end
-class HiController < ActionController::Base; end
-class ImageController < ActionController::Base; end
class NewsController < ActionController::Base; end
class NotesController < ActionController::Base; end
+class PagesController < ActionController::Base; end
class PeopleController < ActionController::Base; end
class PostsController < ActionController::Base; end
-class SessionsController < ActionController::Base; end
-class StuffController < ActionController::Base; end
class SubpathBooksController < ActionController::Base; end
class SymbolsController < ActionController::Base; end
class UserController < ActionController::Base; end
-class WeblogController < ActionController::Base; end
-
-# For speed test
-class SpeedController < ActionController::Base; end
-class SearchController < SpeedController; end
-class VideosController < SpeedController; end
-class VideoFileController < SpeedController; end
-class VideoSharesController < SpeedController; end
-class VideoAbusesController < SpeedController; end
-class VideoUploadsController < SpeedController; end
-class VideoVisitsController < SpeedController; end
-class UsersController < SpeedController; end
-class SettingsController < SpeedController; end
-class ChannelsController < SpeedController; end
-class ChannelVideosController < SpeedController; end
-class LostPasswordsController < SpeedController; end
-class PagesController < SpeedController; end
+class UsersController < ActionController::Base; end
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index 363403092b..bbb4cc5ef3 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -34,6 +34,16 @@ end
class GoodCustomer < Customer
end
+class ValidatedCustomer < Customer
+ def errors
+ if name =~ /Sikachu/i
+ []
+ else
+ [{:name => "is invalid"}]
+ end
+ end
+end
+
module Quiz
class Question < Struct.new(:name, :id)
extend ActiveModel::Naming
@@ -175,8 +185,8 @@ class HashBackedAuthor < Hash
end
module Blog
- def self._railtie
- self
+ def self.use_relative_model_naming?
+ true
end
class Post < Struct.new(:title, :id)
diff --git a/actionpack/test/lib/testing_sandbox.rb b/actionpack/test/lib/testing_sandbox.rb
deleted file mode 100644
index c36585104f..0000000000
--- a/actionpack/test/lib/testing_sandbox.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module TestingSandbox
- # Temporarily replaces KCODE for the block
- def with_kcode(kcode)
- if RUBY_VERSION < '1.9'
- old_kcode, $KCODE = $KCODE, kcode
- begin
- yield
- ensure
- $KCODE = old_kcode
- end
- else
- yield
- end
- end
-end
diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb
new file mode 100644
index 0000000000..a5588d95fa
--- /dev/null
+++ b/actionpack/test/routing/helper_test.rb
@@ -0,0 +1,31 @@
+require 'abstract_unit'
+
+module ActionDispatch
+ module Routing
+ class HelperTest < ActiveSupport::TestCase
+ class Duck
+ def to_param
+ nil
+ end
+ end
+
+ def test_exception
+ rs = ::ActionDispatch::Routing::RouteSet.new
+ rs.draw do
+ resources :ducks do
+ member do
+ get :pond
+ end
+ end
+ end
+
+ x = Class.new {
+ include rs.url_helpers
+ }
+ assert_raises ActionController::RoutingError do
+ x.new.pond_duck_path Duck.new
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb
index 8530a72a82..24511df444 100644
--- a/actionpack/test/template/active_model_helper_test.rb
+++ b/actionpack/test/template/active_model_helper_test.rb
@@ -4,7 +4,7 @@ class ActiveModelHelperTest < ActionView::TestCase
tests ActionView::Helpers::ActiveModelHelper
silence_warnings do
- class Post < Struct.new(:author_name, :body)
+ class Post < Struct.new(:author_name, :body, :updated_at)
include ActiveModel::Conversion
include ActiveModel::Validations
@@ -20,25 +20,48 @@ class ActiveModelHelperTest < ActionView::TestCase
@post = Post.new
@post.errors[:author_name] << "can't be empty"
@post.errors[:body] << "foo"
+ @post.errors[:updated_at] << "bar"
@post.author_name = ""
@post.body = "Back to the hill and over it again!"
+ @post.updated_at = Date.new(2004, 6, 15)
end
def test_text_area_with_errors
assert_dom_equal(
- %(<div class="field_with_errors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div>),
+ %(<div class="field_with_errors"><textarea id="post_body" name="post[body]">\nBack to the hill and over it again!</textarea></div>),
text_area("post", "body")
)
end
def test_text_field_with_errors
assert_dom_equal(
- %(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" size="30" type="text" value="" /></div>),
+ %(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" type="text" value="" /></div>),
text_field("post", "author_name")
)
end
+ def test_date_select_with_errors
+ assert_dom_equal(
+ %(<div class="field_with_errors"><select id="post_updated_at_1i" name="post[updated_at(1i)]">\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n</select>\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" />\n</div>),
+ date_select("post", "updated_at", :discard_month => true, :discard_day => true, :start_year => 2004, :end_year => 2005)
+ )
+ end
+
+ def test_datetime_select_with_errors
+ assert_dom_equal(
+ %(<div class="field_with_errors"><input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="2004" />\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" />\n<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n<option selected="selected" value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_updated_at_5i" name="post[updated_at(5i)]">\n<option selected="selected" value="00">00</option>\n</select>\n</div>),
+ datetime_select("post", "updated_at", :discard_year => true, :discard_month => true, :discard_day => true, :minute_step => 60)
+ )
+ end
+
+ def test_time_select_with_errors
+ assert_dom_equal(
+ %(<div class="field_with_errors"><input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="2004" />\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="15" />\n<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n<option selected="selected" value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_updated_at_5i" name="post[updated_at(5i)]">\n<option selected="selected" value="00">00</option>\n</select>\n</div>),
+ time_select("post", "updated_at", :minute_step => 60)
+ )
+ end
+
def test_hidden_field_does_not_render_errors
assert_dom_equal(
%(<input id="post_author_name" name="post[author_name]" type="hidden" value="" />),
@@ -53,7 +76,7 @@ class ActiveModelHelperTest < ActionView::TestCase
end
assert_dom_equal(
- %(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" size="30" type="text" value="" /> <span class="error">can't be empty</span></div>),
+ %(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" type="text" value="" /> <span class="error">can't be empty</span></div>),
text_field("post", "author_name")
)
ensure
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index aa7304b3ed..7cc567c72d 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -38,6 +38,7 @@ class AssetTagHelperTest < ActionView::TestCase
@controller = BasicController.new
@request = Class.new do
+ attr_accessor :script_name
def protocol() 'http://' end
def ssl?() false end
def host_with_port() 'localhost' end
@@ -88,21 +89,33 @@ class AssetTagHelperTest < ActionView::TestCase
%(path_to_javascript("/super/xmlhr.js")) => %(/super/xmlhr.js)
}
+ JavascriptUrlToTag = {
+ %(javascript_url("xmlhr")) => %(http://www.example.com/javascripts/xmlhr.js),
+ %(javascript_url("super/xmlhr")) => %(http://www.example.com/javascripts/super/xmlhr.js),
+ %(javascript_url("/super/xmlhr.js")) => %(http://www.example.com/super/xmlhr.js)
+ }
+
+ UrlToJavascriptToTag = {
+ %(url_to_javascript("xmlhr")) => %(http://www.example.com/javascripts/xmlhr.js),
+ %(url_to_javascript("super/xmlhr")) => %(http://www.example.com/javascripts/super/xmlhr.js),
+ %(url_to_javascript("/super/xmlhr.js")) => %(http://www.example.com/super/xmlhr.js)
+ }
+
JavascriptIncludeToTag = {
- %(javascript_include_tag("bank")) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>),
- %(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>),
- %(javascript_include_tag("bank", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/bank.js" type="text/javascript"></script>),
- %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>),
- %(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag(:all)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag(:all, :recursive => true)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag(:defaults, "bank")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag(:defaults, "application")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag("bank", :defaults)) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
-
- %(javascript_include_tag("http://example.com/all")) => %(<script src="http://example.com/all" type="text/javascript"></script>),
- %(javascript_include_tag("http://example.com/all.js")) => %(<script src="http://example.com/all.js" type="text/javascript"></script>),
- %(javascript_include_tag("//example.com/all.js")) => %(<script src="//example.com/all.js" type="text/javascript"></script>),
+ %(javascript_include_tag("bank")) => %(<script src="/javascripts/bank.js" ></script>),
+ %(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" ></script>),
+ %(javascript_include_tag("bank", :lang => "vbscript")) => %(<script lang="vbscript" src="/javascripts/bank.js" ></script>),
+ %(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" ></script>\n<script src="/elsewhere/cools.js" ></script>),
+ %(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" ></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>),
+ %(javascript_include_tag(:all)) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
+ %(javascript_include_tag(:all, :recursive => true)) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
+ %(javascript_include_tag(:defaults, "bank")) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/application.js"></script>),
+ %(javascript_include_tag(:defaults, "application")) => %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>),
+ %(javascript_include_tag("bank", :defaults)) => %(<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>),
+
+ %(javascript_include_tag("http://example.com/all")) => %(<script src="http://example.com/all"></script>),
+ %(javascript_include_tag("http://example.com/all.js")) => %(<script src="http://example.com/all.js"></script>),
+ %(javascript_include_tag("//example.com/all.js")) => %(<script src="//example.com/all.js"></script>),
}
StylePathToTag = {
@@ -119,20 +132,34 @@ class AssetTagHelperTest < ActionView::TestCase
%(path_to_stylesheet('/dir/file.rcss')) => %(/dir/file.rcss)
}
+ StyleUrlToTag = {
+ %(stylesheet_url("bank")) => %(http://www.example.com/stylesheets/bank.css),
+ %(stylesheet_url("bank.css")) => %(http://www.example.com/stylesheets/bank.css),
+ %(stylesheet_url('subdir/subdir')) => %(http://www.example.com/stylesheets/subdir/subdir.css),
+ %(stylesheet_url('/subdir/subdir.css')) => %(http://www.example.com/subdir/subdir.css)
+ }
+
+ UrlToStyleToTag = {
+ %(url_to_stylesheet("style")) => %(http://www.example.com/stylesheets/style.css),
+ %(url_to_stylesheet("style.css")) => %(http://www.example.com/stylesheets/style.css),
+ %(url_to_stylesheet('dir/file')) => %(http://www.example.com/stylesheets/dir/file.css),
+ %(url_to_stylesheet('/dir/file.rcss')) => %(http://www.example.com/dir/file.rcss)
+ }
+
StyleLinkToTag = {
- %(stylesheet_link_tag("bank")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("bank.css")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("/elsewhere/file")) => %(<link href="/elsewhere/file.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("subdir/subdir")) => %(<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("bank", :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag(:all)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag(:all, :recursive => true)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag(:all, :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="all" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("random.styles", "/elsewhere/file")) => %(<link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />\n<link href="/elsewhere/file.css" media="screen" rel="stylesheet" type="text/css" />),
-
- %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("http://www.example.com/styles/style.css")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("//www.example.com/styles/style.css")) => %(<link href="//www.example.com/styles/style.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag("bank")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("bank.css")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("/elsewhere/file")) => %(<link href="/elsewhere/file.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("subdir/subdir")) => %(<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("bank", :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" />),
+ %(stylesheet_link_tag(:all)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag(:all, :recursive => true)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag(:all, :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="all" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="all" rel="stylesheet" />),
+ %(stylesheet_link_tag("random.styles", "/elsewhere/file")) => %(<link href="/stylesheets/random.styles" media="screen" rel="stylesheet" />\n<link href="/elsewhere/file.css" media="screen" rel="stylesheet" />),
+
+ %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("http://www.example.com/styles/style.css")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" />),
+ %(stylesheet_link_tag("//www.example.com/styles/style.css")) => %(<link href="//www.example.com/styles/style.css" media="screen" rel="stylesheet" />),
}
ImagePathToTag = {
@@ -149,6 +176,20 @@ class AssetTagHelperTest < ActionView::TestCase
%(path_to_image("/dir/xml.png")) => %(/dir/xml.png)
}
+ ImageUrlToTag = {
+ %(image_url("xml")) => %(http://www.example.com/images/xml),
+ %(image_url("xml.png")) => %(http://www.example.com/images/xml.png),
+ %(image_url("dir/xml.png")) => %(http://www.example.com/images/dir/xml.png),
+ %(image_url("/dir/xml.png")) => %(http://www.example.com/dir/xml.png)
+ }
+
+ UrlToImageToTag = {
+ %(url_to_image("xml")) => %(http://www.example.com/images/xml),
+ %(url_to_image("xml.png")) => %(http://www.example.com/images/xml.png),
+ %(url_to_image("dir/xml.png")) => %(http://www.example.com/images/dir/xml.png),
+ %(url_to_image("/dir/xml.png")) => %(http://www.example.com/dir/xml.png)
+ }
+
ImageLinkToTag = {
%(image_tag("xml.png")) => %(<img alt="Xml" src="/images/xml.png" />),
%(image_tag("rss.gif", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.gif" />),
@@ -162,13 +203,13 @@ class AssetTagHelperTest < ActionView::TestCase
%(image_tag(".pdf.png")) => %(<img alt=".pdf" src="/images/.pdf.png" />),
%(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />),
%(image_tag("//www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="//www.rubyonrails.com/images/rails.png" />),
- %(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />),
- %(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />),
- %(image_tag("mouse.png", :alt => nil)) => %(<img src="/images/mouse.png" />)
+ %(image_tag("mouse.png", :alt => nil)) => %(<img src="/images/mouse.png" />),
+ %(image_tag("data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", :alt => nil)) => %(<img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" />),
+ %(image_tag("")) => %(<img src="" />)
}
FaviconLinkToTag = {
- %(favicon_link_tag) => %(<link href="/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
+ %(favicon_link_tag) => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
%(favicon_link_tag 'favicon.ico') => %(<link href="/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />),
%(favicon_link_tag 'favicon.ico', :rel => 'foo') => %(<link href="/images/favicon.ico" rel="foo" type="image/vnd.microsoft.icon" />),
%(favicon_link_tag 'favicon.ico', :rel => 'foo', :type => 'bar') => %(<link href="/images/favicon.ico" rel="foo" type="bar" />),
@@ -189,23 +230,38 @@ class AssetTagHelperTest < ActionView::TestCase
%(path_to_video("/dir/xml.ogg")) => %(/dir/xml.ogg)
}
+ VideoUrlToTag = {
+ %(video_url("xml")) => %(http://www.example.com/videos/xml),
+ %(video_url("xml.ogg")) => %(http://www.example.com/videos/xml.ogg),
+ %(video_url("dir/xml.ogg")) => %(http://www.example.com/videos/dir/xml.ogg),
+ %(video_url("/dir/xml.ogg")) => %(http://www.example.com/dir/xml.ogg)
+ }
+
+ UrlToVideoToTag = {
+ %(url_to_video("xml")) => %(http://www.example.com/videos/xml),
+ %(url_to_video("xml.ogg")) => %(http://www.example.com/videos/xml.ogg),
+ %(url_to_video("dir/xml.ogg")) => %(http://www.example.com/videos/dir/xml.ogg),
+ %(url_to_video("/dir/xml.ogg")) => %(http://www.example.com/dir/xml.ogg)
+ }
+
VideoLinkToTag = {
- %(video_tag("xml.ogg")) => %(<video src="/videos/xml.ogg" />),
- %(video_tag("rss.m4v", :autoplay => true, :controls => true)) => %(<video autoplay="autoplay" controls="controls" src="/videos/rss.m4v" />),
- %(video_tag("rss.m4v", :autobuffer => true)) => %(<video autobuffer="autobuffer" src="/videos/rss.m4v" />),
- %(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160" />),
- %(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320" />),
- %(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg" />),
- %(video_tag("error.avi", "size" => "100")) => %(<video src="/videos/error.avi" />),
- %(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi" />),
- %(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi" />),
- %(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov" />),
- %(video_tag("//media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="//media.rubyonrails.org/video/rails_blog_2.mov" />),
- %(video_tag(["multiple.ogg", "multiple.avi"])) => %(<video><source src="multiple.ogg" /><source src="multiple.avi" /></video>),
- %(video_tag(["multiple.ogg", "multiple.avi"], :size => "160x120", :controls => true)) => %(<video controls="controls" height="120" width="160"><source src="multiple.ogg" /><source src="multiple.avi" /></video>)
+ %(video_tag("xml.ogg")) => %(<video src="/videos/xml.ogg"></video>),
+ %(video_tag("rss.m4v", :autoplay => true, :controls => true)) => %(<video autoplay="autoplay" controls="controls" src="/videos/rss.m4v"></video>),
+ %(video_tag("rss.m4v", :autobuffer => true)) => %(<video autobuffer="autobuffer" src="/videos/rss.m4v"></video>),
+ %(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160"></video>),
+ %(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320"></video>),
+ %(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg"></video>),
+ %(video_tag("error.avi", "size" => "100")) => %(<video src="/videos/error.avi"></video>),
+ %(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi"></video>),
+ %(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi"></video>),
+ %(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov"></video>),
+ %(video_tag("//media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="//media.rubyonrails.org/video/rails_blog_2.mov"></video>),
+ %(video_tag("multiple.ogg", "multiple.avi")) => %(<video><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>),
+ %(video_tag(["multiple.ogg", "multiple.avi"])) => %(<video><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>),
+ %(video_tag(["multiple.ogg", "multiple.avi"], :size => "160x120", :controls => true)) => %(<video controls="controls" height="120" width="160"><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>)
}
- AudioPathToTag = {
+ AudioPathToTag = {
%(audio_path("xml")) => %(/audios/xml),
%(audio_path("xml.wav")) => %(/audios/xml.wav),
%(audio_path("dir/xml.wav")) => %(/audios/dir/xml.wav),
@@ -219,11 +275,28 @@ class AssetTagHelperTest < ActionView::TestCase
%(path_to_audio("/dir/xml.wav")) => %(/dir/xml.wav)
}
+ AudioUrlToTag = {
+ %(audio_url("xml")) => %(http://www.example.com/audios/xml),
+ %(audio_url("xml.wav")) => %(http://www.example.com/audios/xml.wav),
+ %(audio_url("dir/xml.wav")) => %(http://www.example.com/audios/dir/xml.wav),
+ %(audio_url("/dir/xml.wav")) => %(http://www.example.com/dir/xml.wav)
+ }
+
+ UrlToAudioToTag = {
+ %(url_to_audio("xml")) => %(http://www.example.com/audios/xml),
+ %(url_to_audio("xml.wav")) => %(http://www.example.com/audios/xml.wav),
+ %(url_to_audio("dir/xml.wav")) => %(http://www.example.com/audios/dir/xml.wav),
+ %(url_to_audio("/dir/xml.wav")) => %(http://www.example.com/dir/xml.wav)
+ }
+
AudioLinkToTag = {
- %(audio_tag("xml.wav")) => %(<audio src="/audios/xml.wav" />),
- %(audio_tag("rss.wav", :autoplay => true, :controls => true)) => %(<audio autoplay="autoplay" controls="controls" src="/audios/rss.wav" />),
- %(audio_tag("http://media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="http://media.rubyonrails.org/audio/rails_blog_2.mov" />),
- %(audio_tag("//media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="//media.rubyonrails.org/audio/rails_blog_2.mov" />),
+ %(audio_tag("xml.wav")) => %(<audio src="/audios/xml.wav"></audio>),
+ %(audio_tag("rss.wav", :autoplay => true, :controls => true)) => %(<audio autoplay="autoplay" controls="controls" src="/audios/rss.wav"></audio>),
+ %(audio_tag("http://media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="http://media.rubyonrails.org/audio/rails_blog_2.mov"></audio>),
+ %(audio_tag("//media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="//media.rubyonrails.org/audio/rails_blog_2.mov"></audio>),
+ %(audio_tag("audio.mp3", "audio.ogg")) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>),
+ %(audio_tag(["audio.mp3", "audio.ogg"])) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>),
+ %(audio_tag(["audio.mp3", "audio.ogg"], :autobuffer => true, :controls => true)) => %(<audio autobuffer="autobuffer" controls="controls"><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>)
}
def test_auto_discovery_link_tag
@@ -238,6 +311,14 @@ class AssetTagHelperTest < ActionView::TestCase
PathToJavascriptToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_javascript_url
+ JavascriptUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_javascript_alias_for_javascript_url
+ UrlToJavascriptToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_javascript_include_tag_with_blank_asset_id
ENV["RAILS_ASSET_ID"] = ""
JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
@@ -259,7 +340,7 @@ class AssetTagHelperTest < ActionView::TestCase
def test_javascript_include_tag_with_given_asset_id
ENV["RAILS_ASSET_ID"] = "1"
- assert_dom_equal(%(<script src="/javascripts/prototype.js?1" type="text/javascript"></script>\n<script src="/javascripts/effects.js?1" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js?1" type="text/javascript"></script>\n<script src="/javascripts/controls.js?1" type="text/javascript"></script>\n<script src="/javascripts/rails.js?1" type="text/javascript"></script>\n<script src="/javascripts/application.js?1" type="text/javascript"></script>), javascript_include_tag(:defaults))
+ assert_dom_equal(%(<script src="/javascripts/prototype.js?1"></script>\n<script src="/javascripts/effects.js?1"></script>\n<script src="/javascripts/dragdrop.js?1"></script>\n<script src="/javascripts/controls.js?1"></script>\n<script src="/javascripts/rails.js?1"></script>\n<script src="/javascripts/application.js?1"></script>), javascript_include_tag(:defaults))
end
def test_javascript_include_tag_is_html_safe
@@ -270,35 +351,35 @@ class AssetTagHelperTest < ActionView::TestCase
def test_custom_javascript_expansions
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"]
- assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>), javascript_include_tag('controls', :robbery, 'effects')
+ assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>), javascript_include_tag('controls', :robbery, 'effects')
end
def test_custom_javascript_expansions_return_unique_set
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :defaults => %w(prototype effects dragdrop controls rails application)
- assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
+ assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag(:defaults)
end
def test_custom_javascript_expansions_and_defaults_puts_application_js_at_the_end
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"]
- assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag('controls',:defaults, :robbery, 'effects')
+ assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls',:defaults, :robbery, 'effects')
end
def test_javascript_include_tag_should_not_output_the_same_asset_twice
ENV["RAILS_ASSET_ID"] = ""
- assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag('prototype', 'effects', :defaults)
+ assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('prototype', 'effects', :defaults)
end
def test_javascript_include_tag_should_not_output_the_same_expansion_twice
ENV["RAILS_ASSET_ID"] = ""
- assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults, :defaults)
+ assert_dom_equal %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag(:defaults, :defaults)
end
def test_single_javascript_asset_keys_should_take_precedence_over_expansions
ENV["RAILS_ASSET_ID"] = ""
- assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag('controls', :defaults, 'effects')
- assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag('controls', 'effects', :defaults)
+ assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls', :defaults, 'effects')
+ assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/rails.js"></script>\n<script src="/javascripts/application.js"></script>), javascript_include_tag('controls', 'effects', :defaults)
end
def test_registering_javascript_expansions_merges_with_existing_expansions
@@ -306,7 +387,7 @@ class AssetTagHelperTest < ActionView::TestCase
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['bank']
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['robber']
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :can_merge => ['bank']
- assert_dom_equal %(<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>), javascript_include_tag(:can_merge)
+ assert_dom_equal %(<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>), javascript_include_tag(:can_merge)
end
def test_custom_javascript_expansions_with_undefined_symbol
@@ -315,20 +396,20 @@ class AssetTagHelperTest < ActionView::TestCase
def test_custom_javascript_expansions_with_nil_value
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :monkey => nil
- assert_dom_equal %(<script src="/javascripts/first.js" type="text/javascript"></script>\n<script src="/javascripts/last.js" type="text/javascript"></script>), javascript_include_tag('first', :monkey, 'last')
+ assert_dom_equal %(<script src="/javascripts/first.js"></script>\n<script src="/javascripts/last.js"></script>), javascript_include_tag('first', :monkey, 'last')
end
def test_custom_javascript_expansions_with_empty_array_value
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :monkey => []
- assert_dom_equal %(<script src="/javascripts/first.js" type="text/javascript"></script>\n<script src="/javascripts/last.js" type="text/javascript"></script>), javascript_include_tag('first', :monkey, 'last')
+ assert_dom_equal %(<script src="/javascripts/first.js"></script>\n<script src="/javascripts/last.js"></script>), javascript_include_tag('first', :monkey, 'last')
end
def test_custom_javascript_and_stylesheet_expansion_with_same_name
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"]
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :robbery => ["money", "security"]
- assert_dom_equal %(<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>), javascript_include_tag('controls', :robbery, 'effects')
- assert_dom_equal %(<link href="/stylesheets/style.css" rel="stylesheet" type="text/css" media="screen" />\n<link href="/stylesheets/money.css" rel="stylesheet" type="text/css" media="screen" />\n<link href="/stylesheets/security.css" rel="stylesheet" type="text/css" media="screen" />\n<link href="/stylesheets/print.css" rel="stylesheet" type="text/css" media="screen" />), stylesheet_link_tag('style', :robbery, 'print')
+ assert_dom_equal %(<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/effects.js"></script>), javascript_include_tag('controls', :robbery, 'effects')
+ assert_dom_equal %(<link href="/stylesheets/style.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/money.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/security.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/print.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('style', :robbery, 'print')
end
def test_reset_javascript_expansions
@@ -345,6 +426,15 @@ class AssetTagHelperTest < ActionView::TestCase
PathToStyleToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_stylesheet_url
+ ENV["RAILS_ASSET_ID"] = ""
+ StyleUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_stylesheet_alias_for_stylesheet_url
+ UrlToStyleToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_stylesheet_link_tag
ENV["RAILS_ASSET_ID"] = ""
StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
@@ -367,36 +457,36 @@ class AssetTagHelperTest < ActionView::TestCase
end
def test_stylesheet_link_tag_escapes_options
- assert_dom_equal %(<link href="/file.css" media="&lt;script&gt;" rel="stylesheet" type="text/css" />), stylesheet_link_tag('/file', :media => '<script>')
+ assert_dom_equal %(<link href="/file.css" media="&lt;script&gt;" rel="stylesheet" />), stylesheet_link_tag('/file', :media => '<script>')
end
def test_custom_stylesheet_expansions
ENV["RAILS_ASSET_ID"] = ''
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :robbery => ["bank", "robber"]
- assert_dom_equal %(<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag('version.1.0', :robbery, 'subdir/subdir')
+ assert_dom_equal %(<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('version.1.0', :robbery, 'subdir/subdir')
end
def test_custom_stylesheet_expansions_return_unique_set
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london)
- assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag(:cities)
+ assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:cities)
end
def test_stylesheet_link_tag_should_not_output_the_same_asset_twice
ENV["RAILS_ASSET_ID"] = ""
- assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam')
+ assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('wellington', 'wellington', 'amsterdam')
end
def test_stylesheet_link_tag_should_not_output_the_same_expansion_twice
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london)
- assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag(:cities, :cities)
+ assert_dom_equal %(<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:cities, :cities)
end
def test_single_stylesheet_asset_keys_should_take_precedence_over_expansions
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :cities => %w(wellington amsterdam london)
- assert_dom_equal %(<link href="/stylesheets/london.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag('london', :cities)
+ assert_dom_equal %(<link href="/stylesheets/london.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/wellington.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/amsterdam.css" media="screen" rel="stylesheet" />), stylesheet_link_tag('london', :cities)
end
def test_custom_stylesheet_expansions_with_unknown_symbol
@@ -405,12 +495,12 @@ class AssetTagHelperTest < ActionView::TestCase
def test_custom_stylesheet_expansions_with_nil_value
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :monkey => nil
- assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" type="text/css" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" type="text/css" media="screen" />), stylesheet_link_tag('first', :monkey, 'last')
+ assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('first', :monkey, 'last')
end
def test_custom_stylesheet_expansions_with_empty_array_value
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :monkey => []
- assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" type="text/css" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" type="text/css" media="screen" />), stylesheet_link_tag('first', :monkey, 'last')
+ assert_dom_equal %(<link href="/stylesheets/first.css" rel="stylesheet" media="screen" />\n<link href="/stylesheets/last.css" rel="stylesheet" media="screen" />), stylesheet_link_tag('first', :monkey, 'last')
end
def test_registering_stylesheet_expansions_merges_with_existing_expansions
@@ -418,7 +508,7 @@ class AssetTagHelperTest < ActionView::TestCase
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['bank']
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['robber']
ActionView::Helpers::AssetTagHelper::register_stylesheet_expansion :can_merge => ['bank']
- assert_dom_equal %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag(:can_merge)
+ assert_dom_equal %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />), stylesheet_link_tag(:can_merge)
end
def test_image_path
@@ -429,6 +519,14 @@ class AssetTagHelperTest < ActionView::TestCase
PathToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_image_url
+ ImageUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_image_alias_for_image_url
+ UrlToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_image_alt
[nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix|
assert_equal 'Rails', image_alt("#{prefix}rails.png")
@@ -441,6 +539,12 @@ class AssetTagHelperTest < ActionView::TestCase
ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_image_tag_does_not_modify_options
+ options = {:size => '16x10'}
+ image_tag('icon', options)
+ assert_equal({:size => '16x10'}, options)
+ end
+
def test_favicon_link_tag
FaviconLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
@@ -468,6 +572,14 @@ class AssetTagHelperTest < ActionView::TestCase
PathToVideoToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_video_url
+ VideoUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_video_alias_for_video_url
+ UrlToVideoToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_video_tag
VideoLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
@@ -480,10 +592,26 @@ class AssetTagHelperTest < ActionView::TestCase
PathToAudioToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_audio_url
+ AudioUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
+ def test_url_to_audio_alias_for_audio_url
+ UrlToAudioToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
+ end
+
def test_audio_tag
AudioLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
end
+ def test_video_audio_tag_does_not_modify_options
+ options = {:autoplay => true}
+ video_tag('video', options)
+ assert_equal({:autoplay => true}, options)
+ audio_tag('audio', options)
+ assert_equal({:autoplay => true}, options)
+ end
+
def test_timebased_asset_id
expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s
assert_equal %(<img alt="Rails" src="/images/rails.png?#{expected_time}" />), image_tag("rails.png")
@@ -520,6 +648,13 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal %(<img alt="Rails" src="#{@controller.config.relative_url_root}/images/rails.png?#{expected_time}" />), image_tag("rails.png")
end
+ # Same as above, but with script_name
+ def test_timebased_asset_id_with_script_name
+ @request.script_name = "/collaboration/hieraki"
+ expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s
+ assert_equal %(<img alt="Rails" src="#{@request.script_name}/images/rails.png?#{expected_time}" />), image_tag("rails.png")
+ end
+
def test_should_skip_asset_id_on_complete_url
assert_equal %(<img alt="Rails" src="http://www.example.com/rails.png" />), image_tag("http://www.example.com/rails.png")
end
@@ -555,7 +690,6 @@ class AssetTagHelperTest < ActionView::TestCase
end
end
-
@controller.request.stubs(:ssl?).returns(false)
assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png")
@@ -569,21 +703,21 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<script src="http://a0.example.com/javascripts/all.js" type="text/javascript"></script>),
+ %(<script src="http://a0.example.com/javascripts/all.js"></script>),
javascript_include_tag(:all, :cache => true)
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
- %(<script src="http://a0.example.com/javascripts/money.js" type="text/javascript"></script>),
+ %(<script src="http://a0.example.com/javascripts/money.js"></script>),
javascript_include_tag(:all, :cache => "money")
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
assert_dom_equal(
- %(<script src="http://a0.example.com/absolute/test.js" type="text/javascript"></script>),
+ %(<script src="http://a0.example.com/absolute/test.js"></script>),
javascript_include_tag(:all, :cache => "/absolute/test")
)
@@ -602,7 +736,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/javascripts/scripts.js'.length, 23
assert_dom_equal(
- %(<script src="http://a23.example.com/javascripts/scripts.js" type="text/javascript"></script>),
+ %(<script src="http://a23.example.com/javascripts/scripts.js"></script>),
javascript_include_tag(:all, :cache => 'scripts')
)
@@ -625,7 +759,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/javascripts/vanilla.js'.length, 23
assert_dom_equal(
- %(<script src="http://assets23.example.com/javascripts/vanilla.js" type="text/javascript"></script>),
+ %(<script src="http://assets23.example.com/javascripts/vanilla.js"></script>),
javascript_include_tag(:all, :cache => 'vanilla')
)
@@ -638,7 +772,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/javascripts/secure.js'.length, 22
assert_dom_equal(
- %(<script src="https://localhost/javascripts/secure.js" type="text/javascript"></script>),
+ %(<script src="https://localhost/javascripts/secure.js"></script>),
javascript_include_tag(:all, :cache => 'secure')
)
@@ -665,7 +799,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/javascripts/vanilla.js'.length, 23
assert_dom_equal(
- %(<script src="http://assets23.example.com/javascripts/vanilla.js" type="text/javascript"></script>),
+ %(<script src="http://assets23.example.com/javascripts/vanilla.js"></script>),
javascript_include_tag(:all, :cache => 'vanilla')
)
@@ -678,7 +812,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/javascripts/secure.js'.length, 22
assert_dom_equal(
- %(<script src="https://localhost/javascripts/secure.js" type="text/javascript"></script>),
+ %(<script src="https://localhost/javascripts/secure.js"></script>),
javascript_include_tag(:all, :cache => 'secure')
)
@@ -696,7 +830,7 @@ class AssetTagHelperTest < ActionView::TestCase
number = Zlib.crc32('/javascripts/cache/money.js') % 4
assert_dom_equal(
- %(<script src="http://a#{number}.example.com/javascripts/cache/money.js" type="text/javascript"></script>),
+ %(<script src="http://a#{number}.example.com/javascripts/cache/money.js"></script>),
javascript_include_tag(:all, :cache => "cache/money")
)
@@ -711,7 +845,7 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<script src="http://a0.example.com/javascripts/combined.js" type="text/javascript"></script>),
+ %(<script src="http://a0.example.com/javascripts/combined.js"></script>),
javascript_include_tag(:all, :cache => "combined", :recursive => true)
)
@@ -732,7 +866,7 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<script src="http://a0.example.com/javascripts/combined.js" type="text/javascript"></script>),
+ %(<script src="http://a0.example.com/javascripts/combined.js"></script>),
javascript_include_tag(:all, :cache => "combined")
)
@@ -753,14 +887,39 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/all.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/all.js"></script>),
javascript_include_tag(:all, :cache => true)
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/money.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/money.js"></script>),
+ javascript_include_tag(:all, :cache => "money")
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
+
+ ensure
+ FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
+ FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
+ end
+
+ # Same as above, but with script_name
+ def test_caching_javascript_include_tag_with_script_name
+ ENV["RAILS_ASSET_ID"] = ""
+ @request.script_name = "/collaboration/hieraki"
+ config.perform_caching = true
+
+ assert_dom_equal(
+ %(<script src="/collaboration/hieraki/javascripts/all.js"></script>),
+ javascript_include_tag(:all, :cache => true)
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
+
+ assert_dom_equal(
+ %(<script src="/collaboration/hieraki/javascripts/money.js"></script>),
javascript_include_tag(:all, :cache => "money")
)
@@ -777,14 +936,35 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = false
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>),
javascript_include_tag('robber', :cache => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
- %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>),
+ %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>),
+ javascript_include_tag('robber', :cache => "money", :recursive => true)
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
+ end
+
+ # Same as above, but with script_name
+ def test_caching_javascript_include_tag_with_named_paths_and_script_name_when_caching_off
+ ENV["RAILS_ASSET_ID"] = ""
+ @request.script_name = "/collaboration/hieraki"
+ config.perform_caching = false
+
+ assert_dom_equal(
+ %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>),
+ javascript_include_tag('robber', :cache => true)
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
+
+ assert_dom_equal(
+ %(<script src="/collaboration/hieraki/javascripts/robber.js"></script>),
javascript_include_tag('robber', :cache => "money", :recursive => true)
)
@@ -796,24 +976,24 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = false
assert_dom_equal(
- %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
+ %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
javascript_include_tag(:all, :cache => true)
)
assert_dom_equal(
- %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
+ %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
javascript_include_tag(:all, :cache => true, :recursive => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
- %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
+ %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
javascript_include_tag(:all, :cache => "money")
)
assert_dom_equal(
- %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
+ %(<script src="/javascripts/prototype.js"></script>\n<script src="/javascripts/effects.js"></script>\n<script src="/javascripts/dragdrop.js"></script>\n<script src="/javascripts/controls.js"></script>\n<script src="/javascripts/bank.js"></script>\n<script src="/javascripts/robber.js"></script>\n<script src="/javascripts/subdir/subdir.js"></script>\n<script src="/javascripts/version.1.0.js"></script>\n<script src="/javascripts/application.js"></script>),
javascript_include_tag(:all, :cache => "money", :recursive => true)
)
@@ -871,7 +1051,7 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<link href="http://a0.example.com/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="http://a0.example.com/stylesheets/all.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => true)
)
@@ -885,14 +1065,14 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal expected_size, File.size(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="http://a0.example.com/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="http://a0.example.com/stylesheets/money.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "money")
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
assert_dom_equal(
- %(<link href="http://a0.example.com/absolute/test.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="http://a0.example.com/absolute/test.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "/absolute/test")
)
@@ -907,7 +1087,7 @@ class AssetTagHelperTest < ActionView::TestCase
ENV["RAILS_ASSET_ID"] = ""
assert_dom_equal(
- %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/all.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :concat => true)
)
@@ -915,14 +1095,14 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal expected, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/money.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :concat => "money")
)
assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
assert_dom_equal(
- %(<link href="/absolute/test.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/absolute/test.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :concat => "/absolute/test")
)
@@ -982,7 +1162,7 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal '/stylesheets/styles.css'.length, 23
assert_dom_equal(
- %(<link href="http://a23.example.com/stylesheets/styles.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="http://a23.example.com/stylesheets/styles.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => 'styles')
)
@@ -998,7 +1178,7 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = true
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => true)
)
@@ -1008,7 +1188,34 @@ class AssetTagHelperTest < ActionView::TestCase
assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" />),
+ stylesheet_link_tag(:all, :cache => "money")
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
+ ensure
+ FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
+ FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
+ end
+
+ # Same as above, but with script_name
+ def test_caching_stylesheet_link_tag_with_script_name
+ ENV["RAILS_ASSET_ID"] = ""
+ @request.script_name = "/collaboration/hieraki"
+ config.perform_caching = true
+
+ assert_dom_equal(
+ %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" />),
+ stylesheet_link_tag(:all, :cache => true)
+ )
+
+ files_to_be_joined = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/[^all]*.css"]
+
+ expected_mtime = files_to_be_joined.map { |p| File.mtime(p) }.max
+ assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
+
+ assert_dom_equal(
+ %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "money")
)
@@ -1025,14 +1232,35 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = false
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />),
+ stylesheet_link_tag('robber', :cache => true)
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
+
+ assert_dom_equal(
+ %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />),
+ stylesheet_link_tag('robber', :cache => "money")
+ )
+
+ assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
+ end
+
+ # Same as above, but with script_name
+ def test_caching_stylesheet_link_tag_with_named_paths_and_script_name_when_caching_off
+ ENV["RAILS_ASSET_ID"] = ""
+ @request.script_name = "/collaboration/hieraki"
+ config.perform_caching = false
+
+ assert_dom_equal(
+ %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag('robber', :cache => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag('robber', :cache => "money")
)
@@ -1047,24 +1275,24 @@ class AssetTagHelperTest < ActionView::TestCase
config.perform_caching = false
assert_dom_equal(
- %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => true)
)
assert_dom_equal(
- %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => true, :recursive => true)
)
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
- %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "money")
)
assert_dom_equal(
- %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" />),
stylesheet_link_tag(:all, :cache => "money", :recursive => true)
)
@@ -1095,8 +1323,6 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_dom_equal(%(/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
assert_dom_equal(%(/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
assert_dom_equal(%(/collaboration/hieraki/images/xml.png), image_path("xml.png"))
- assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='/collaboration/hieraki/images/mouse.png'" src="/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png"))
- assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='/collaboration/hieraki/images/mouse2.png'" src="/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
end
def test_should_ignore_relative_root_path_on_complete_url
@@ -1109,8 +1335,6 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png"))
- assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png"))
- assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
end
def test_should_compute_proper_path_with_asset_host_and_default_protocol
@@ -1119,33 +1343,50 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr"))
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style"))
assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png"))
- assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png"))
- assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png")))
+ end
+
+ def test_should_compute_proper_url_with_asset_host
+ @controller.config.asset_host = "assets.example.com"
+ assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag)
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_url("xmlhr"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_url("style"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_url("xml.png"))
+ end
+
+ def test_should_compute_proper_url_with_asset_host_and_default_protocol
+ @controller.config.asset_host = "assets.example.com"
+ @controller.config.default_asset_host_protocol = :request
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_url("xmlhr"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_url("style"))
+ assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_url("xml.png"))
end
def test_should_ignore_asset_host_on_complete_url
@controller.config.asset_host = "http://assets.example.com"
- assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css"))
+ assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css"))
end
def test_should_ignore_asset_host_on_scheme_relative_url
@controller.config.asset_host = "http://assets.example.com"
- assert_dom_equal(%(<link href="//bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag("//bar.example.com/stylesheets/style.css"))
+ assert_dom_equal(%(<link href="//bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" />), stylesheet_link_tag("//bar.example.com/stylesheets/style.css"))
end
def test_should_wildcard_asset_host_between_zero_and_four
@controller.config.asset_host = 'http://a%d.example.com'
assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png'))
+ assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_url('xml.png'))
end
def test_asset_host_without_protocol_should_be_protocol_relative
@controller.config.asset_host = 'a.example.com'
assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png')
+ assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_url('xml.png')
end
def test_asset_host_without_protocol_should_be_protocol_relative_even_if_path_present
@controller.config.asset_host = 'a.example.com/files/go/here'
assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png')
+ assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_url('xml.png')
end
def test_assert_css_and_js_of_the_same_name_return_correct_extension
diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb
index 81d7444cf8..89aae4ac56 100644
--- a/actionpack/test/template/atom_feed_helper_test.rb
+++ b/actionpack/test/template/atom_feed_helper_test.rb
@@ -16,7 +16,7 @@ class ScrollsController < ActionController::Base
feed.title("My great blog!")
feed.updated((@scrolls.first.created_at))
- @scrolls.each do |scroll|
+ @scrolls.each do |scroll|
feed.entry(scroll) do |entry|
entry.title(scroll.title)
entry.content(scroll.body, :type => 'html')
@@ -45,6 +45,23 @@ class ScrollsController < ActionController::Base
end
end
EOT
+ FEEDS["entry_type_options"] = <<-EOT
+ atom_feed(:schema_date => '2008') do |feed|
+ feed.title("My great blog!")
+ feed.updated((@scrolls.first.created_at))
+
+ @scrolls.each do |scroll|
+ feed.entry(scroll, :type => 'text/xml') do |entry|
+ entry.title(scroll.title)
+ entry.content(scroll.body, :type => 'html')
+
+ entry.author do |author|
+ author.name("DHH")
+ end
+ end
+ end
+ end
+ EOT
FEEDS["xml_block"] = <<-EOT
atom_feed do |feed|
feed.title("My great blog!")
@@ -185,11 +202,6 @@ class ScrollsController < ActionController::Base
render :inline => FEEDS[params[:id]], :type => :builder
end
-
- protected
- def rescue_action(e)
- raise(e)
- end
end
class AtomFeedTest < ActionController::TestCase
@@ -311,6 +323,20 @@ class AtomFeedTest < ActionController::TestCase
end
end
+ def test_feed_entry_type_option_default_to_text_html
+ with_restful_routing(:scrolls) do
+ get :index, :id => 'defaults'
+ assert_select "entry link[rel=alternate][type=text/html]"
+ end
+ end
+
+ def test_feed_entry_type_option_specified
+ with_restful_routing(:scrolls) do
+ get :index, :id => 'entry_type_options'
+ assert_select "entry link[rel=alternate][type=text/xml]"
+ end
+ end
+
private
def with_restful_routing(resources)
with_routing do |set|
diff --git a/actionpack/test/template/benchmark_helper_test.rb b/actionpack/test/template/benchmark_helper_test.rb
new file mode 100644
index 0000000000..8c198d2562
--- /dev/null
+++ b/actionpack/test/template/benchmark_helper_test.rb
@@ -0,0 +1,24 @@
+require 'abstract_unit'
+require 'stringio'
+
+class BenchmarkHelperTest < ActionView::TestCase
+ include RenderERBUtils
+ tests ActionView::Helpers::BenchmarkHelper
+
+ def test_output_in_erb
+ output = render_erb("Hello <%= benchmark do %>world<% end %>")
+ expected = 'Hello world'
+ assert_equal expected, output
+ end
+
+ def test_returns_value_from_block
+ assert_equal 'test', benchmark { 'test' }
+ end
+
+ def test_default_message
+ log = StringIO.new
+ self.stubs(:logger).returns(Logger.new(log))
+ benchmark {}
+ assert_match(/Benchmarking \(\d+.\d+ms\)/, log.rewind && log.read)
+ end
+end
diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb
index 13e2d5b595..17469f8c3e 100644
--- a/actionpack/test/template/capture_helper_test.rb
+++ b/actionpack/test/template/capture_helper_test.rb
@@ -53,6 +53,13 @@ class CaptureHelperTest < ActionView::TestCase
assert_equal 'foobar', content_for(:title)
end
+ def test_content_for_with_multiple_calls_and_flush
+ assert ! content_for?(:title)
+ content_for :title, 'foo'
+ content_for :title, 'bar', true
+ assert_equal 'bar', content_for(:title)
+ end
+
def test_content_for_with_block
assert ! content_for?(:title)
content_for :title do
@@ -63,6 +70,39 @@ class CaptureHelperTest < ActionView::TestCase
assert_equal 'foobar', content_for(:title)
end
+ def test_content_for_with_block_and_multiple_calls_with_flush
+ assert ! content_for?(:title)
+ content_for :title do
+ 'foo'
+ end
+ content_for :title, true do
+ 'bar'
+ end
+ assert_equal 'bar', content_for(:title)
+ end
+
+ def test_content_for_with_block_and_multiple_calls_with_flush_nil_content
+ assert ! content_for?(:title)
+ content_for :title do
+ 'foo'
+ end
+ content_for :title, nil, true do
+ 'bar'
+ end
+ assert_equal 'bar', content_for(:title)
+ end
+
+ def test_content_for_with_block_and_multiple_calls_without_flush
+ assert ! content_for?(:title)
+ content_for :title do
+ 'foo'
+ end
+ content_for :title, false do
+ 'bar'
+ end
+ assert_equal 'foobar', content_for(:title)
+ end
+
def test_content_for_with_whitespace_block
assert ! content_for?(:title)
content_for :title, 'foo'
@@ -74,12 +114,27 @@ class CaptureHelperTest < ActionView::TestCase
assert_equal 'foobar', content_for(:title)
end
+ def test_content_for_with_whitespace_block_and_flush
+ assert ! content_for?(:title)
+ content_for :title, 'foo'
+ content_for :title, true do
+ output_buffer << " \n "
+ nil
+ end
+ content_for :title, 'bar', true
+ assert_equal 'bar', content_for(:title)
+ end
+
def test_content_for_returns_nil_when_writing
assert ! content_for?(:title)
assert_equal nil, content_for(:title, 'foo')
assert_equal nil, content_for(:title) { output_buffer << 'bar'; nil }
assert_equal nil, content_for(:title) { output_buffer << " \n "; nil }
assert_equal 'foobar', content_for(:title)
+ assert_equal nil, content_for(:title, 'foo', true)
+ assert_equal nil, content_for(:title, true) { output_buffer << 'bar'; nil }
+ assert_equal nil, content_for(:title, true) { output_buffer << " \n "; nil }
+ assert_equal 'bar', content_for(:title)
end
def test_content_for_question_mark
@@ -131,17 +186,15 @@ class CaptureHelperTest < ActionView::TestCase
assert buffer.equal?(@av.output_buffer)
end
- unless RUBY_VERSION < '1.9'
- def test_with_output_buffer_sets_proper_encoding
- @av.output_buffer = ActionView::OutputBuffer.new
+ def test_with_output_buffer_sets_proper_encoding
+ @av.output_buffer = ActionView::OutputBuffer.new
- # Ensure we set the output buffer to an encoding different than the default one.
- alt_encoding = alt_encoding(@av.output_buffer)
- @av.output_buffer.force_encoding(alt_encoding)
+ # Ensure we set the output buffer to an encoding different than the default one.
+ alt_encoding = alt_encoding(@av.output_buffer)
+ @av.output_buffer.force_encoding(alt_encoding)
- @av.with_output_buffer do
- assert_equal alt_encoding, @av.output_buffer.encoding
- end
+ @av.with_output_buffer do
+ assert_equal alt_encoding, @av.output_buffer.encoding
end
end
@@ -165,14 +218,12 @@ class CaptureHelperTest < ActionView::TestCase
assert_equal '', view.output_buffer
end
- unless RUBY_VERSION < '1.9'
- def test_flush_output_buffer_preserves_the_encoding_of_the_output_buffer
- view = view_with_controller
- alt_encoding = alt_encoding(view.output_buffer)
- view.output_buffer.force_encoding(alt_encoding)
- flush_output_buffer
- assert_equal alt_encoding, view.output_buffer.encoding
- end
+ def test_flush_output_buffer_preserves_the_encoding_of_the_output_buffer
+ view = view_with_controller
+ alt_encoding = alt_encoding(view.output_buffer)
+ view.output_buffer.force_encoding(alt_encoding)
+ flush_output_buffer
+ assert_equal alt_encoding, view.output_buffer.encoding
end
def alt_encoding(output_buffer)
diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb
index 8fc78283d8..f5dc2fbb33 100644
--- a/actionpack/test/template/compiled_templates_test.rb
+++ b/actionpack/test/template/compiled_templates_test.rb
@@ -1,8 +1,12 @@
require 'abstract_unit'
require 'controller/fake_models'
-class CompiledTemplatesTest < Test::Unit::TestCase
+class CompiledTemplatesTest < ActiveSupport::TestCase
def setup
+ # Clean up any details key cached to expose failures
+ # that otherwise would appear just on isolated tests
+ ActionView::LookupContext::DetailsKey.clear
+
@compiled_templates = ActionView::CompiledTemplates
@compiled_templates.instance_methods.each do |m|
@compiled_templates.send(:remove_method, m) if m =~ /^_render_template_/
diff --git a/actionpack/test/template/compressors_test.rb b/actionpack/test/template/compressors_test.rb
deleted file mode 100644
index a273f15bd7..0000000000
--- a/actionpack/test/template/compressors_test.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'abstract_unit'
-require 'sprockets/compressors'
-
-class CompressorsTest < ActiveSupport::TestCase
- def test_register_css_compressor
- Sprockets::Compressors.register_css_compressor(:null, Sprockets::NullCompressor)
- compressor = Sprockets::Compressors.registered_css_compressor(:null)
- assert_kind_of Sprockets::NullCompressor, compressor
- end
-
- def test_register_js_compressor
- Sprockets::Compressors.register_js_compressor(:uglifier, 'Uglifier', :require => 'uglifier')
- compressor = Sprockets::Compressors.registered_js_compressor(:uglifier)
- assert_kind_of Uglifier, compressor
- end
-
- def test_register_default_css_compressor
- Sprockets::Compressors.register_css_compressor(:null, Sprockets::NullCompressor, :default => true)
- compressor = Sprockets::Compressors.registered_css_compressor(:default)
- assert_kind_of Sprockets::NullCompressor, compressor
- end
-
- def test_register_default_js_compressor
- Sprockets::Compressors.register_js_compressor(:null, Sprockets::NullCompressor, :default => true)
- compressor = Sprockets::Compressors.registered_js_compressor(:default)
- assert_kind_of Sprockets::NullCompressor, compressor
- end
-end
diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb
index d45215acfd..63066d40cd 100644
--- a/actionpack/test/template/date_helper_i18n_test.rb
+++ b/actionpack/test/template/date_helper_i18n_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
+class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase
include ActionView::Helpers::DateHelper
attr_reader :request
@@ -12,24 +12,24 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
def test_distance_of_time_in_words_calls_i18n
{ # with include_seconds
- [2.seconds, true] => [:'less_than_x_seconds', 5],
- [9.seconds, true] => [:'less_than_x_seconds', 10],
- [19.seconds, true] => [:'less_than_x_seconds', 20],
- [30.seconds, true] => [:'half_a_minute', nil],
- [59.seconds, true] => [:'less_than_x_minutes', 1],
- [60.seconds, true] => [:'x_minutes', 1],
+ [2.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 5],
+ [9.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 10],
+ [19.seconds, { :include_seconds => true }] => [:'less_than_x_seconds', 20],
+ [30.seconds, { :include_seconds => true }] => [:'half_a_minute', nil],
+ [59.seconds, { :include_seconds => true }] => [:'less_than_x_minutes', 1],
+ [60.seconds, { :include_seconds => true }] => [:'x_minutes', 1],
# without include_seconds
- [29.seconds, false] => [:'less_than_x_minutes', 1],
- [60.seconds, false] => [:'x_minutes', 1],
- [44.minutes, false] => [:'x_minutes', 44],
- [61.minutes, false] => [:'about_x_hours', 1],
- [24.hours, false] => [:'x_days', 1],
- [30.days, false] => [:'about_x_months', 1],
- [60.days, false] => [:'x_months', 2],
- [1.year, false] => [:'about_x_years', 1],
- [3.years + 6.months, false] => [:'over_x_years', 3],
- [3.years + 10.months, false] => [:'almost_x_years', 4]
+ [29.seconds, { :include_seconds => false }] => [:'less_than_x_minutes', 1],
+ [60.seconds, { :include_seconds => false }] => [:'x_minutes', 1],
+ [44.minutes, { :include_seconds => false }] => [:'x_minutes', 44],
+ [61.minutes, { :include_seconds => false }] => [:'about_x_hours', 1],
+ [24.hours, { :include_seconds => false }] => [:'x_days', 1],
+ [30.days, { :include_seconds => false }] => [:'about_x_months', 1],
+ [60.days, { :include_seconds => false }] => [:'x_months', 2],
+ [1.year, { :include_seconds => false }] => [:'about_x_years', 1],
+ [3.years + 6.months, { :include_seconds => false }] => [:'over_x_years', 3],
+ [3.years + 10.months, { :include_seconds => false }] => [:'almost_x_years', 4]
}.each do |passed, expected|
assert_distance_of_time_in_words_translates_key passed, expected
@@ -37,7 +37,7 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
end
def assert_distance_of_time_in_words_translates_key(passed, expected)
- diff, include_seconds = *passed
+ diff, passed_options = *passed
key, count = *expected
to = @from + diff
@@ -45,7 +45,12 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
options[:count] = count if count
I18n.expects(:t).with(key, options)
- distance_of_time_in_words(@from, to, include_seconds, :locale => 'en')
+ distance_of_time_in_words(@from, to, passed_options.merge(:locale => 'en'))
+ end
+
+ def test_time_ago_in_words_passes_locale
+ I18n.expects(:t).with(:less_than_x_minutes, :scope => :'datetime.distance_in_words', :count => 1, :locale => 'ru')
+ time_ago_in_words(15.seconds.ago, :locale => 'ru')
end
def test_distance_of_time_pluralizations
@@ -71,7 +76,7 @@ class DateHelperDistanceOfTimeInWordsI18nTests < Test::Unit::TestCase
end
end
-class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
+class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase
include ActionView::Helpers::DateHelper
attr_reader :request
@@ -103,7 +108,7 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
I18n.expects(:translate).with(('datetime.prompts.' + key.to_s).to_sym, :locale => 'en').returns prompt
end
- I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:year, :month, :day]
+ I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day]
datetime_select('post', 'updated_at', :locale => 'en', :include_seconds => true, :prompt => true)
end
@@ -115,7 +120,15 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase
end
def test_date_or_time_select_given_no_order_options_translates_order
- I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:year, :month, :day]
+ I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day]
datetime_select('post', 'updated_at', :locale => 'en')
end
+
+ def test_date_or_time_select_given_invalid_order
+ I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:invalid, :month, :day]
+
+ assert_raise StandardError do
+ datetime_select('post', 'updated_at', :locale => 'en')
+ end
+ end
end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index af30ec9892..ff85a675a2 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -21,56 +21,80 @@ class DateHelperTest < ActionView::TestCase
def assert_distance_of_time_in_words(from, to=nil)
to ||= from
- # 0..1 with include_seconds
- assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 0.seconds, true)
- assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 4.seconds, true)
- assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 5.seconds, true)
- assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 9.seconds, true)
- assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 10.seconds, true)
- assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 19.seconds, true)
- assert_equal "half a minute", distance_of_time_in_words(from, to + 20.seconds, true)
- assert_equal "half a minute", distance_of_time_in_words(from, to + 39.seconds, true)
- assert_equal "less than a minute", distance_of_time_in_words(from, to + 40.seconds, true)
- assert_equal "less than a minute", distance_of_time_in_words(from, to + 59.seconds, true)
- assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, true)
- assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, true)
-
- # First case 0..1
+ # 0..1 minute with :include_seconds => true
+ assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 0.seconds, :include_seconds => true)
+ assert_equal "less than 5 seconds", distance_of_time_in_words(from, to + 4.seconds, :include_seconds => true)
+ assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 5.seconds, :include_seconds => true)
+ assert_equal "less than 10 seconds", distance_of_time_in_words(from, to + 9.seconds, :include_seconds => true)
+ assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 10.seconds, :include_seconds => true)
+ assert_equal "less than 20 seconds", distance_of_time_in_words(from, to + 19.seconds, :include_seconds => true)
+ assert_equal "half a minute", distance_of_time_in_words(from, to + 20.seconds, :include_seconds => true)
+ assert_equal "half a minute", distance_of_time_in_words(from, to + 39.seconds, :include_seconds => true)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 40.seconds, :include_seconds => true)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 59.seconds, :include_seconds => true)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, :include_seconds => true)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, :include_seconds => true)
+
+ # 0..1 minute with :include_seconds => false
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 4.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 5.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 9.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 10.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 19.seconds, :include_seconds => false)
+ assert_equal "less than a minute", distance_of_time_in_words(from, to + 20.seconds, :include_seconds => false)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 39.seconds, :include_seconds => false)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 40.seconds, :include_seconds => false)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 59.seconds, :include_seconds => false)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 60.seconds, :include_seconds => false)
+ assert_equal "1 minute", distance_of_time_in_words(from, to + 89.seconds, :include_seconds => false)
+
+ # Note that we are including a 30-second boundary around the interval we
+ # want to test. For instance, "1 minute" is actually 30s to 1m29s. The
+ # reason for doing this is simple -- in `distance_of_time_to_words`, when we
+ # take the distance between our two Time objects in seconds and convert it
+ # to minutes, we round the number. So 29s gets rounded down to 0m, 30s gets
+ # rounded up to 1m, and 1m29s gets rounded down to 1m. A similar thing
+ # happens with the other cases.
+
+ # First case 0..1 minute
assert_equal "less than a minute", distance_of_time_in_words(from, to + 0.seconds)
assert_equal "less than a minute", distance_of_time_in_words(from, to + 29.seconds)
assert_equal "1 minute", distance_of_time_in_words(from, to + 30.seconds)
assert_equal "1 minute", distance_of_time_in_words(from, to + 1.minutes + 29.seconds)
- # 2..44
+ # 2 minutes up to 45 minutes
assert_equal "2 minutes", distance_of_time_in_words(from, to + 1.minutes + 30.seconds)
assert_equal "44 minutes", distance_of_time_in_words(from, to + 44.minutes + 29.seconds)
- # 45..89
+ # 45 minutes up to 90 minutes
assert_equal "about 1 hour", distance_of_time_in_words(from, to + 44.minutes + 30.seconds)
assert_equal "about 1 hour", distance_of_time_in_words(from, to + 89.minutes + 29.seconds)
- # 90..1439
+ # 90 minutes up to 24 hours
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..2519
+ # 24 hours up to 42 hours
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)
- # 2520..43199
+ # 42 hours up to 30 days
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)
- # 43200..86399
+ # 30 days up to 60 days
assert_equal "about 1 month", distance_of_time_in_words(from, to + 29.days + 23.hours + 59.minutes + 30.seconds)
- assert_equal "about 1 month", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 29.seconds)
+ assert_equal "about 1 month", distance_of_time_in_words(from, to + 44.days + 23.hours + 59.minutes + 29.seconds)
+ assert_equal "about 2 months", distance_of_time_in_words(from, to + 44.days + 23.hours + 59.minutes + 30.seconds)
+ assert_equal "about 2 months", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 29.seconds)
- # 86400..525599
+ # 60 days up to 365 days
assert_equal "2 months", distance_of_time_in_words(from, to + 59.days + 23.hours + 59.minutes + 30.seconds)
assert_equal "12 months", distance_of_time_in_words(from, to + 1.years - 31.seconds)
- # > 525599
+ # >= 365 days
assert_equal "about 1 year", distance_of_time_in_words(from, to + 1.years - 30.seconds)
assert_equal "about 1 year", distance_of_time_in_words(from, to + 1.years + 3.months - 1.day)
assert_equal "over 1 year", distance_of_time_in_words(from, to + 1.years + 6.months)
@@ -95,7 +119,8 @@ class DateHelperTest < ActionView::TestCase
# test to < from
assert_equal "about 4 hours", distance_of_time_in_words(from + 4.hours, to)
- assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, true)
+ assert_equal "less than 20 seconds", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => true)
+ assert_equal "less than a minute", distance_of_time_in_words(from + 19.seconds, to, :include_seconds => false)
end
def test_distance_in_words
@@ -103,6 +128,11 @@ class DateHelperTest < ActionView::TestCase
assert_distance_of_time_in_words(from)
end
+ def test_time_ago_in_words_passes_include_seconds
+ assert_equal "less than 20 seconds", time_ago_in_words(15.seconds.ago, :include_seconds => true)
+ assert_equal "less than a minute", time_ago_in_words(15.seconds.ago, :include_seconds => false)
+ end
+
def test_distance_in_words_with_time_zones
from = Time.mktime(2004, 6, 6, 21, 45, 0)
assert_distance_of_time_in_words(from.in_time_zone('Alaska'))
@@ -125,13 +155,33 @@ class DateHelperTest < ActionView::TestCase
start_date = Date.new 1982, 12, 3
end_date = Date.new 2010, 11, 30
assert_equal("almost 28 years", distance_of_time_in_words(start_date, end_date))
+ assert_equal("almost 28 years", distance_of_time_in_words(end_date, start_date))
end
def test_distance_in_words_with_integers
- assert_equal "less than a minute", distance_of_time_in_words(59)
+ assert_equal "1 minute", distance_of_time_in_words(59)
assert_equal "about 1 hour", distance_of_time_in_words(60*60)
- assert_equal "less than a minute", distance_of_time_in_words(0, 59)
+ assert_equal "1 minute", distance_of_time_in_words(0, 59)
assert_equal "about 1 hour", distance_of_time_in_words(60*60, 0)
+ assert_equal "about 3 years", distance_of_time_in_words(10**8)
+ assert_equal "about 3 years", distance_of_time_in_words(0, 10**8)
+ end
+
+ def test_distance_in_words_with_times
+ assert_equal "1 minute", distance_of_time_in_words(30.seconds)
+ assert_equal "1 minute", distance_of_time_in_words(59.seconds)
+ assert_equal "2 minutes", distance_of_time_in_words(119.seconds)
+ assert_equal "2 minutes", distance_of_time_in_words(1.minute + 59.seconds)
+ assert_equal "3 minutes", distance_of_time_in_words(2.minute + 30.seconds)
+ assert_equal "44 minutes", distance_of_time_in_words(44.minutes + 29.seconds)
+ assert_equal "about 1 hour", distance_of_time_in_words(44.minutes + 30.seconds)
+ assert_equal "about 1 hour", distance_of_time_in_words(60.minutes)
+
+ # include seconds
+ assert_equal "half a minute", distance_of_time_in_words(39.seconds, 0, :include_seconds => true)
+ assert_equal "less than a minute", distance_of_time_in_words(40.seconds, 0, :include_seconds => true)
+ assert_equal "less than a minute", distance_of_time_in_words(59.seconds, 0, :include_seconds => true)
+ assert_equal "1 minute", distance_of_time_in_words(60.seconds, 0, :include_seconds => true)
end
def test_time_ago_in_words
@@ -164,6 +214,15 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_day(nil, :include_blank => true)
end
+ def test_select_day_with_two_digit_numbers
+ expected = %(<select id="date_day" name="date[day]">\n)
+ expected << %(<option value="1">01</option>\n<option selected="selected" value="2">02</option>\n<option value="3">03</option>\n<option value="4">04</option>\n<option value="5">05</option>\n<option value="6">06</option>\n<option value="7">07</option>\n<option value="8">08</option>\n<option value="9">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_day(Time.mktime(2011, 8, 2), :use_two_digit_numbers => true)
+ assert_dom_equal expected, select_day(2, :use_two_digit_numbers => true)
+ end
+
def test_select_day_with_html_options
expected = %(<select id="date_day" name="date[day]" class="selector">\n)
expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n)
@@ -198,6 +257,15 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_month(8)
end
+ def test_select_month_with_two_digit_numbers
+ expected = %(<select id="date_month" name="date[month]">\n)
+ expected << %(<option value="1">01</option>\n<option value="2">02</option>\n<option value="3">03</option>\n<option value="4">04</option>\n<option value="5">05</option>\n<option value="6">06</option>\n<option value="7">07</option>\n<option value="8" selected="selected">08</option>\n<option value="9">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n)
+ expected << "</select>\n"
+
+ assert_dom_equal expected, select_month(Time.mktime(2011, 8, 16), :use_two_digit_numbers => true)
+ assert_dom_equal expected, select_month(8, :use_two_digit_numbers => true)
+ end
+
def test_select_month_with_disabled
expected = %(<select id="date_month" name="date[month]" disabled="disabled">\n)
expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
@@ -549,7 +617,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_minute_with_html_options
- expected = expected = %(<select id="date_minute" name="date[minute]" class="selector" accesskey="M">\n)
+ expected = %(<select id="date_minute" name="date[minute]" class="selector" accesskey="M">\n)
expected << %(<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04" selected="selected">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n)
expected << "</select>\n"
@@ -693,7 +761,7 @@ class DateHelperTest < ActionView::TestCase
# Since the order is incomplete nothing will be shown
expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
- expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
+ expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="1" />\n)
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :order => [:day])
end
@@ -925,11 +993,20 @@ class DateHelperTest < ActionView::TestCase
expected << "</select>\n"
expected << %(<input type="hidden" id="date_first_month" name="date[first][month]" value="8" />\n)
- expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="16" />\n)
+ expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="1" />\n)
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_month => true, :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"})
end
+ def test_select_date_with_hidden
+ expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003"/>\n)
+ expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
+ expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :prefix => "date[first]", :use_hidden => true })
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :prefix => "date[first]", :use_hidden => true })
+ end
+
def test_select_datetime
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -986,7 +1063,6 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :ampm => true)
end
-
def test_select_datetime_with_separators
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
@@ -1167,6 +1243,18 @@ class DateHelperTest < ActionView::TestCase
:prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year', :hour => 'Choose hour', :minute => 'Choose minute'})
end
+ def test_select_datetime_with_hidden
+ expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
+ expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
+ expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
+ expected << %(<input id="date_first_hour" name="date[first][hour]" type="hidden" value="8" />\n)
+ expected << %(<input id="date_first_minute" name="date[first][minute]" type="hidden" value="4" />\n)
+
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :prefix => "date[first]", :use_hidden => true)
+ assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :datetime_separator => "&mdash;", :date_separator => "/",
+ :time_separator => ":", :prefix => "date[first]", :use_hidden => true)
+ end
+
def test_select_time
expected = %(<input name="date[year]" id="date_year" value="2003" type="hidden" />\n)
expected << %(<input name="date[month]" id="date_month" value="8" type="hidden" />\n)
@@ -1342,6 +1430,17 @@ class DateHelperTest < ActionView::TestCase
:prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'})
end
+ def test_select_time_with_hidden
+ expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n)
+ expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n)
+ expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n)
+ expected << %(<input id="date_first_hour" name="date[first][hour]" type="hidden" value="8" />\n)
+ expected << %(<input id="date_first_minute" name="date[first][minute]" type="hidden" value="4" />\n)
+
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :prefix => "date[first]", :use_hidden => true)
+ assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :time_separator => ":", :prefix => "date[first]", :use_hidden => true)
+ end
+
def test_date_select
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
@@ -1379,6 +1478,39 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", :order => [ :month, :year ])
end
+ def test_date_select_without_day_and_month
+ @post = Post.new
+ @post.written_on = Date.new(2004, 2, 29)
+
+ expected = "<input type=\"hidden\" id=\"post_written_on_2i\" name=\"post[written_on(2i)]\" value=\"2\" />\n"
+ expected << "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
+
+ expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :order => [ :year ])
+ end
+
+ def test_date_select_without_day_with_separator
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ expected = "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << "/"
+
+ expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :date_separator => '/', :order => [ :month, :year ])
+ end
+
def test_date_select_without_day_and_with_disabled_html_option
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
@@ -1551,7 +1683,7 @@ class DateHelperTest < ActionView::TestCase
start_year = Time.now.year-5
end_year = Time.now.year+5
- expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i"/>' + "\n"
+ expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1"/>' + "\n"
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
expected << "<option value=\"\"></option>\n"
start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
@@ -1565,6 +1697,40 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", :order=>[:year, :month], :include_blank=>true)
end
+ def test_date_select_with_nil_and_blank_and_discard_month
+ @post = Post.new
+
+ start_year = Time.now.year-5
+ end_year = Time.now.year+5
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
+ expected << "<option value=\"\"></option>\n"
+ start_year.upto(end_year) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
+ expected << "</select>\n"
+ expected << '<input name="post[written_on(2i)]" type="hidden" id="post_written_on_2i" value="1"/>' + "\n"
+ expected << '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i" value="1"/>' + "\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :discard_month => true, :include_blank=>true)
+ end
+
+ def test_date_select_with_nil_and_blank_and_discard_year
+ @post = Post.new
+
+ expected = '<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="1" />' + "\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n}
+ expected << "<option value=\"\"></option>\n"
+ 1.upto(12) { |i| expected << %(<option value="#{i}">#{Date::MONTHNAMES[i]}</option>\n) }
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n}
+ expected << "<option value=\"\"></option>\n"
+ 1.upto(31) { |i| expected << %(<option value="#{i}">#{i}</option>\n) }
+ expected << "</select>\n"
+
+ assert_dom_equal expected, date_select("post", "written_on", :discard_year => true, :include_blank=>true)
+ end
+
def test_date_select_cant_override_discard_hour
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
@@ -2048,6 +2214,18 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, datetime_select("post", "updated_at", { :date_separator => " / ", :datetime_separator => " , ", :time_separator => " - ", :include_seconds => true })
end
+ def test_datetime_select_with_integer
+ @post = Post.new
+ @post.updated_at = 3
+ datetime_select("post", "updated_at")
+ end
+
+ def test_datetime_select_with_infinity # Float
+ @post = Post.new
+ @post.updated_at = (-1.0/0)
+ datetime_select("post", "updated_at")
+ end
+
def test_datetime_select_with_default_prompt
@post = Post.new
@post.updated_at = nil
@@ -2357,7 +2535,7 @@ class DateHelperTest < ActionView::TestCase
1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) }
expected << "</select>\n"
expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" />\n}
- expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="15" />\n}
+ expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="1" />\n}
expected << " &mdash; "
@@ -2378,7 +2556,7 @@ class DateHelperTest < ActionView::TestCase
expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n}
expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" />\n}
- expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="15" />\n}
+ expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="1" />\n}
expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n}
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
@@ -2397,7 +2575,7 @@ class DateHelperTest < ActionView::TestCase
expected = %{<input type="hidden" id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]" value="2004" />\n}
expected << %{<input type="hidden" id="post_updated_at_2i" disabled="disabled" name="post[updated_at(2i)]" value="6" />\n}
- expected << %{<input type="hidden" id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]" value="15" />\n}
+ expected << %{<input type="hidden" id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]" value="1" />\n}
expected << %{<select id="post_updated_at_4i" disabled="disabled" name="post[updated_at(4i)]">\n}
0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) }
@@ -2795,6 +2973,10 @@ class DateHelperTest < ActionView::TestCase
assert_match(/<time.*>Right now<\/time>/, time_tag(Time.now, 'Right now'))
end
+ def test_time_tag_with_given_block
+ assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now){ '<span>Right now</span>'.html_safe })
+ end
+
def test_time_tag_with_different_format
time = Time.now
expected = "<time datetime=\"#{time.xmlschema}\">#{I18n.l(time, :format => :short)}</time>"
diff --git a/actionpack/test/template/erb/tag_helper_test.rb b/actionpack/test/template/erb/tag_helper_test.rb
index a384e94766..1724d6432d 100644
--- a/actionpack/test/template/erb/tag_helper_test.rb
+++ b/actionpack/test/template/erb/tag_helper_test.rb
@@ -11,12 +11,12 @@ module ERBTest
end
test "percent equals works for javascript_tag" do
- expected_output = "<script type=\"text/javascript\">\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>"
+ expected_output = "<script>\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>"
assert_equal expected_output, render_content("javascript_tag", "alert('Hello')")
end
test "percent equals works for javascript_tag with options" do
- expected_output = "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>"
+ expected_output = "<script id=\"the_js_tag\">\n//<![CDATA[\nalert('Hello')\n//]]>\n</script>"
assert_equal expected_output, render_content("javascript_tag(:id => 'the_js_tag')", "alert('Hello')")
end
@@ -30,4 +30,4 @@ module ERBTest
assert_equal expected_output, render_content("field_set_tag('foo')", "<%= 'hello' %>")
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/template/erb_util_test.rb b/actionpack/test/template/erb_util_test.rb
index 790ab1c74c..ca2710e9b3 100644
--- a/actionpack/test/template/erb_util_test.rb
+++ b/actionpack/test/template/erb_util_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/object/inclusion'
-class ErbUtilTest < Test::Unit::TestCase
+class ErbUtilTest < ActiveSupport::TestCase
include ERB::Util
ERB::Util::HTML_ESCAPE.each do |given, expected|
@@ -44,4 +44,18 @@ class ErbUtilTest < Test::Unit::TestCase
assert_equal chr, html_escape(chr)
end
end
+
+ def test_html_escape_once
+ assert_equal '1 &lt; 2 &amp; 3', html_escape_once('1 < 2 &amp; 3')
+ end
+
+ def test_html_escape_once_returns_unsafe_strings_when_passed_unsafe_strings
+ value = html_escape_once('1 < 2 &amp; 3')
+ assert !value.html_safe?
+ end
+
+ def test_html_escape_once_returns_safe_strings_when_passed_safe_strings
+ value = html_escape_once('1 < 2 &amp; 3'.html_safe)
+ assert value.html_safe?
+ end
end
diff --git a/actionpack/test/template/form_collections_helper_test.rb b/actionpack/test/template/form_collections_helper_test.rb
new file mode 100644
index 0000000000..c73e80ed88
--- /dev/null
+++ b/actionpack/test/template/form_collections_helper_test.rb
@@ -0,0 +1,336 @@
+require 'abstract_unit'
+
+class Category < Struct.new(:id, :name)
+end
+
+class FormCollectionsHelperTest < ActionView::TestCase
+ def assert_no_select(selector, value = nil)
+ assert_select(selector, :text => value, :count => 0)
+ end
+
+ def with_collection_radio_buttons(*args, &block)
+ @output_buffer = collection_radio_buttons(*args, &block)
+ end
+
+ def with_collection_check_boxes(*args, &block)
+ @output_buffer = collection_check_boxes(*args, &block)
+ end
+
+ # COLLECTION RADIO BUTTONS
+ test 'collection radio accepts a collection and generate inputs from value method' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s
+
+ assert_select 'input[type=radio][value=true]#user_active_true'
+ assert_select 'input[type=radio][value=false]#user_active_false'
+ end
+
+ test 'collection radio accepts a collection and generate inputs from label method' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s
+
+ assert_select 'label[for=user_active_true]', 'true'
+ assert_select 'label[for=user_active_false]', 'false'
+ end
+
+ test 'collection radio handles camelized collection values for labels correctly' do
+ with_collection_radio_buttons :user, :active, ['Yes', 'No'], :to_s, :to_s
+
+ assert_select 'label[for=user_active_yes]', 'Yes'
+ assert_select 'label[for=user_active_no]', 'No'
+ end
+
+ test 'colection radio should sanitize collection values for labels correctly' do
+ with_collection_radio_buttons :user, :name, ['$0.99', '$1.99'], :to_s, :to_s
+ assert_select 'label[for=user_name_099]', '$0.99'
+ assert_select 'label[for=user_name_199]', '$1.99'
+ end
+
+ test 'collection radio accepts checked item' do
+ with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, :checked => true
+
+ assert_select 'input[type=radio][value=true][checked=checked]'
+ assert_no_select 'input[type=radio][value=false][checked=checked]'
+ end
+
+ test 'collection radio accepts multiple disabled items' do
+ collection = [[1, true], [0, false], [2, 'other']]
+ with_collection_radio_buttons :user, :active, collection, :last, :first, :disabled => [true, false]
+
+ assert_select 'input[type=radio][value=true][disabled=disabled]'
+ assert_select 'input[type=radio][value=false][disabled=disabled]'
+ assert_no_select 'input[type=radio][value=other][disabled=disabled]'
+ end
+
+ test 'collection radio accepts single disable item' do
+ collection = [[1, true], [0, false]]
+ with_collection_radio_buttons :user, :active, collection, :last, :first, :disabled => true
+
+ assert_select 'input[type=radio][value=true][disabled=disabled]'
+ assert_no_select 'input[type=radio][value=false][disabled=disabled]'
+ end
+
+ test 'collection radio accepts html options as input' do
+ collection = [[1, true], [0, false]]
+ with_collection_radio_buttons :user, :active, collection, :last, :first, {}, :class => 'special-radio'
+
+ assert_select 'input[type=radio][value=true].special-radio#user_active_true'
+ assert_select 'input[type=radio][value=false].special-radio#user_active_false'
+ end
+
+ test 'collection radio does not wrap input inside the label' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s
+
+ assert_select 'input[type=radio] + label'
+ assert_no_select 'label input'
+ end
+
+ test 'collection radio accepts a block to render the label as radio button wrapper' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label { b.radio_button }
+ end
+
+ assert_select 'label[for=user_active_true] > input#user_active_true[type=radio]'
+ assert_select 'label[for=user_active_false] > input#user_active_false[type=radio]'
+ end
+
+ test 'collection radio accepts a block to change the order of label and radio button' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label + b.radio_button
+ end
+
+ assert_select 'label[for=user_active_true] + input#user_active_true[type=radio]'
+ assert_select 'label[for=user_active_false] + input#user_active_false[type=radio]'
+ end
+
+ test 'collection radio with block helpers accept extra html options' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:class => "radio_button") + b.radio_button(:class => "radio_button")
+ end
+
+ assert_select 'label.radio_button[for=user_active_true] + input#user_active_true.radio_button[type=radio]'
+ assert_select 'label.radio_button[for=user_active_false] + input#user_active_false.radio_button[type=radio]'
+ end
+
+ test 'collection radio with block helpers allows access to current text and value' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:"data-value" => b.value) { b.radio_button + b.text }
+ end
+
+ assert_select 'label[for=user_active_true][data-value=true]', 'true' do
+ assert_select 'input#user_active_true[type=radio]'
+ end
+ assert_select 'label[for=user_active_false][data-value=false]', 'false' do
+ assert_select 'input#user_active_false[type=radio]'
+ end
+ end
+
+ test 'collection radio with block helpers allows access to the current object item in the collection to access extra properties' do
+ with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:class => b.object) { b.radio_button + b.text }
+ end
+
+ assert_select 'label.true[for=user_active_true]', 'true' do
+ assert_select 'input#user_active_true[type=radio]'
+ end
+ assert_select 'label.false[for=user_active_false]', 'false' do
+ assert_select 'input#user_active_false[type=radio]'
+ end
+ end
+
+ test 'collection radio buttons with fields for' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ @output_buffer = fields_for(:post) do |p|
+ p.collection_radio_buttons :category_id, collection, :id, :name
+ end
+
+ assert_select 'input#post_category_id_1[type=radio][value=1]'
+ assert_select 'input#post_category_id_2[type=radio][value=2]'
+
+ assert_select 'label[for=post_category_id_1]', 'Category 1'
+ assert_select 'label[for=post_category_id_2]', 'Category 2'
+ end
+
+ # COLLECTION CHECK BOXES
+ test 'collection check boxes accepts a collection and generate a serie of checkboxes for value method' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ with_collection_check_boxes :user, :category_ids, collection, :id, :name
+
+ assert_select 'input#user_category_ids_1[type=checkbox][value=1]'
+ assert_select 'input#user_category_ids_2[type=checkbox][value=2]'
+ end
+
+ test 'collection check boxes generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ with_collection_check_boxes :user, :category_ids, collection, :id, :name
+
+ assert_select "input[type=hidden][name='user[category_ids][]'][value=]", :count => 1
+ end
+
+ test 'collection check boxes accepts a collection and generate a serie of checkboxes with labels for label method' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ with_collection_check_boxes :user, :category_ids, collection, :id, :name
+
+ assert_select 'label[for=user_category_ids_1]', 'Category 1'
+ assert_select 'label[for=user_category_ids_2]', 'Category 2'
+ end
+
+ test 'collection check boxes handles camelized collection values for labels correctly' do
+ with_collection_check_boxes :user, :active, ['Yes', 'No'], :to_s, :to_s
+
+ assert_select 'label[for=user_active_yes]', 'Yes'
+ assert_select 'label[for=user_active_no]', 'No'
+ end
+
+ test 'colection check box should sanitize collection values for labels correctly' do
+ with_collection_check_boxes :user, :name, ['$0.99', '$1.99'], :to_s, :to_s
+ assert_select 'label[for=user_name_099]', '$0.99'
+ assert_select 'label[for=user_name_199]', '$1.99'
+ end
+
+ test 'collection check boxes accepts selected values as :checked option' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => [1, 3]
+
+ assert_select 'input[type=checkbox][value=1][checked=checked]'
+ assert_select 'input[type=checkbox][value=3][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=2][checked=checked]'
+ end
+
+ test 'collection check boxes accepts selected string values as :checked option' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => ['1', '3']
+
+ assert_select 'input[type=checkbox][value=1][checked=checked]'
+ assert_select 'input[type=checkbox][value=3][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=2][checked=checked]'
+ end
+
+ test 'collection check boxes accepts a single checked value' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => 3
+
+ assert_select 'input[type=checkbox][value=3][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=1][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=2][checked=checked]'
+ end
+
+ test 'collection check boxes accepts selected values as :checked option and override the model values' do
+ user = Struct.new(:category_ids).new(2)
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+
+ @output_buffer = fields_for(:user, user) do |p|
+ p.collection_check_boxes :category_ids, collection, :first, :last, :checked => [1, 3]
+ end
+
+ assert_select 'input[type=checkbox][value=1][checked=checked]'
+ assert_select 'input[type=checkbox][value=3][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=2][checked=checked]'
+ end
+
+ test 'collection check boxes accepts multiple disabled items' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => [1, 3]
+
+ assert_select 'input[type=checkbox][value=1][disabled=disabled]'
+ assert_select 'input[type=checkbox][value=3][disabled=disabled]'
+ assert_no_select 'input[type=checkbox][value=2][disabled=disabled]'
+ end
+
+ test 'collection check boxes accepts single disable item' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => 1
+
+ assert_select 'input[type=checkbox][value=1][disabled=disabled]'
+ assert_no_select 'input[type=checkbox][value=3][disabled=disabled]'
+ assert_no_select 'input[type=checkbox][value=2][disabled=disabled]'
+ end
+
+ test 'collection check boxes accepts a proc to disabled items' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => proc { |i| i.first == 1 }
+
+ assert_select 'input[type=checkbox][value=1][disabled=disabled]'
+ assert_no_select 'input[type=checkbox][value=3][disabled=disabled]'
+ assert_no_select 'input[type=checkbox][value=2][disabled=disabled]'
+ end
+
+ test 'collection check boxes accepts html options' do
+ collection = [[1, 'Category 1'], [2, 'Category 2']]
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, {}, :class => 'check'
+
+ assert_select 'input.check[type=checkbox][value=1]'
+ assert_select 'input.check[type=checkbox][value=2]'
+ end
+
+ test 'collection check boxes with fields for' do
+ collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')]
+ @output_buffer = fields_for(:post) do |p|
+ p.collection_check_boxes :category_ids, collection, :id, :name
+ end
+
+ assert_select 'input#post_category_ids_1[type=checkbox][value=1]'
+ assert_select 'input#post_category_ids_2[type=checkbox][value=2]'
+
+ assert_select 'label[for=post_category_ids_1]', 'Category 1'
+ assert_select 'label[for=post_category_ids_2]', 'Category 2'
+ end
+
+ test 'collection check boxes does not wrap input inside the label' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s
+
+ assert_select 'input[type=checkbox] + label'
+ assert_no_select 'label input'
+ end
+
+ test 'collection check boxes accepts a block to render the label as check box wrapper' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label { b.check_box }
+ end
+
+ assert_select 'label[for=user_active_true] > input#user_active_true[type=checkbox]'
+ assert_select 'label[for=user_active_false] > input#user_active_false[type=checkbox]'
+ end
+
+ test 'collection check boxes accepts a block to change the order of label and check box' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label + b.check_box
+ end
+
+ assert_select 'label[for=user_active_true] + input#user_active_true[type=checkbox]'
+ assert_select 'label[for=user_active_false] + input#user_active_false[type=checkbox]'
+ end
+
+ test 'collection check boxes with block helpers accept extra html options' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:class => "check_box") + b.check_box(:class => "check_box")
+ end
+
+ assert_select 'label.check_box[for=user_active_true] + input#user_active_true.check_box[type=checkbox]'
+ assert_select 'label.check_box[for=user_active_false] + input#user_active_false.check_box[type=checkbox]'
+ end
+
+ test 'collection check boxes with block helpers allows access to current text and value' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:"data-value" => b.value) { b.check_box + b.text }
+ end
+
+ assert_select 'label[for=user_active_true][data-value=true]', 'true' do
+ assert_select 'input#user_active_true[type=checkbox]'
+ end
+ assert_select 'label[for=user_active_false][data-value=false]', 'false' do
+ assert_select 'input#user_active_false[type=checkbox]'
+ end
+ end
+
+ test 'collection check boxes with block helpers allows access to the current object item in the collection to access extra properties' do
+ with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b|
+ b.label(:class => b.object) { b.check_box + b.text }
+ end
+
+ assert_select 'label.true[for=user_active_true]', 'true' do
+ assert_select 'input#user_active_true[type=checkbox]'
+ end
+ assert_select 'label.false[for=user_active_false]', 'false' do
+ assert_select 'input#user_active_false[type=checkbox]'
+ end
+ end
+end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index e36d032f6c..27cc3ad48a 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -3,6 +3,8 @@ require 'controller/fake_models'
require 'active_support/core_ext/object/inclusion'
class FormHelperTest < ActionView::TestCase
+ include RenderERBUtils
+
tests ActionView::Helpers::FormHelper
def form_for(*)
@@ -27,7 +29,13 @@ class FormHelperTest < ActionView::TestCase
:body => "Write entire text here",
:color => {
:red => "Rojo"
+ },
+ :comments => {
+ :body => "Write body here"
}
+ },
+ :tag => {
+ :value => "Tag"
}
}
}
@@ -57,7 +65,7 @@ class FormHelperTest < ActionView::TestCase
def full_messages() [ "Author name can't be empty" ] end
}.new
end
- def @post.id; 123; end
+ def @post.to_key; [123]; end
def @post.id_before_type_cast; 123; end
def @post.to_param; '123'; end
@@ -68,6 +76,12 @@ class FormHelperTest < ActionView::TestCase
@post.secret = 1
@post.written_on = Date.new(2004, 6, 15)
+ @post.comments = []
+ @post.comments << @comment
+
+ @post.tags = []
+ @post.tags << Tag.new
+
@blog_post = Blog::Post.new("And his name will be forty and four.", 44)
end
@@ -83,7 +97,7 @@ class FormHelperTest < ActionView::TestCase
end
end
- match "/foo", :to => "controller#action"
+ get "/foo", :to => "controller#action"
root :to => "main#index"
end
@@ -101,6 +115,14 @@ class FormHelperTest < ActionView::TestCase
super
end
+ class FooTag < ActionView::Helpers::Tags::Base
+ def initialize; end
+ end
+
+ def test_tags_base_child_without_render_method
+ assert_raise(NotImplementedError) { FooTag.new.render }
+ end
+
def test_label
assert_dom_equal('<label for="post_title">Title</label>', label("post", "title"))
assert_dom_equal('<label for="post_title">The title goes here</label>', label("post", "title", "The title goes here"))
@@ -151,6 +173,40 @@ class FormHelperTest < ActionView::TestCase
I18n.locale = old_locale
end
+ def test_label_with_locales_and_nested_attributes
+ old_locale, I18n.locale = I18n.locale, :label
+ form_for(@post, :html => { :id => 'create-post' }) do |f|
+ f.fields_for(:comments) do |cf|
+ concat cf.label(:body)
+ end
+ end
+
+ expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do
+ "<label for=\"post_comments_attributes_0_body\">Write body here</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ ensure
+ I18n.locale = old_locale
+ end
+
+ def test_label_with_locales_fallback_and_nested_attributes
+ old_locale, I18n.locale = I18n.locale, :label
+ form_for(@post, :html => { :id => 'create-post' }) do |f|
+ f.fields_for(:tags) do |cf|
+ concat cf.label(:value)
+ end
+ end
+
+ expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do
+ "<label for=\"post_tags_attributes_0_value\">Tag</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ ensure
+ I18n.locale = old_locale
+ end
+
def test_label_with_for_attribute_as_symbol
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
end
@@ -159,6 +215,10 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, "for" => "my_for"))
end
+ def test_label_does_not_generate_for_attribute_when_given_nil
+ assert_dom_equal('<label>Title</label>', label(:post, :title, :for => nil))
+ end
+
def test_label_with_id_attribute_as_symbol
assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, :id => "my_id"))
end
@@ -184,32 +244,40 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal('<label for="post_title">The title, please:</label>', label(:post, :title) { "The title, please:" })
end
+ def test_label_with_block_and_options
+ assert_dom_equal('<label for="my_for">The title, please:</label>', label(:post, :title, "for" => "my_for") { "The title, please:" })
+ end
+
+ def test_label_with_block_in_erb
+ assert_equal "<label for=\"post_message\">\n Message\n <input id=\"post_message\" name=\"post[message]\" type=\"text\" />\n</label>", view.render("test/label_with_block")
+ end
+
def test_text_field
assert_dom_equal(
- '<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title")
+ '<input id="post_title" name="post[title]" type="text" value="Hello World" />', text_field("post", "title")
)
assert_dom_equal(
- '<input id="post_title" name="post[title]" size="30" type="password" />', password_field("post", "title")
+ '<input id="post_title" name="post[title]" type="password" />', password_field("post", "title")
)
assert_dom_equal(
- '<input id="post_title" name="post[title]" size="30" type="password" value="Hello World" />', password_field("post", "title", :value => @post.title)
+ '<input id="post_title" name="post[title]" type="password" value="Hello World" />', password_field("post", "title", :value => @post.title)
)
assert_dom_equal(
- '<input id="person_name" name="person[name]" size="30" type="password" />', password_field("person", "name")
+ '<input id="person_name" name="person[name]" type="password" />', password_field("person", "name")
)
end
def test_text_field_with_escapes
@post.title = "<b>Hello World</b>"
assert_dom_equal(
- '<input id="post_title" name="post[title]" size="30" type="text" value="&lt;b&gt;Hello World&lt;/b&gt;" />', text_field("post", "title")
+ '<input id="post_title" name="post[title]" type="text" value="&lt;b&gt;Hello World&lt;/b&gt;" />', text_field("post", "title")
)
end
def test_text_field_with_html_entities
@post.title = "The HTML Entity for & is &amp;"
assert_dom_equal(
- '<input id="post_title" name="post[title]" size="30" type="text" value="The HTML Entity for &amp; is &amp;amp;" />',
+ '<input id="post_title" name="post[title]" type="text" value="The HTML Entity for &amp; is &amp;amp;" />',
text_field("post", "title")
)
end
@@ -233,13 +301,18 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_field_with_nil_value
- expected = '<input id="post_title" name="post[title]" size="30" type="text" />'
+ expected = '<input id="post_title" name="post[title]" type="text" />'
assert_dom_equal expected, text_field("post", "title", :value => nil)
end
+ def test_text_field_with_nil_name
+ expected = '<input id="post_title" type="text" value="Hello World" />'
+ assert_dom_equal expected, text_field("post", "title", :name => nil)
+ end
+
def test_text_field_doesnt_change_param_values
object_name = 'post[]'
- expected = '<input id="post_123_title" name="post[123][title]" size="30" type="text" value="Hello World" />'
+ expected = '<input id="post_123_title" name="post[123][title]" type="text" value="Hello World" />'
assert_equal expected, text_field(object_name, "title")
assert_equal object_name, "post[]"
end
@@ -273,39 +346,56 @@ class FormHelperTest < ActionView::TestCase
end
def test_text_field_with_custom_type
- assert_dom_equal '<input id="user_email" size="30" name="user[email]" type="email" />',
+ assert_dom_equal '<input id="user_email" name="user[email]" type="email" />',
text_field("user", "email", :type => "email")
end
- def test_check_box
+ def test_check_box_is_html_safe
+ assert check_box("post", "secret").html_safe?
+ end
+
+ def test_check_box_checked_if_object_value_is_same_that_check_value
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret")
)
+ end
+
+ def test_check_box_not_checked_if_object_value_is_same_that_unchecked_value
@post.secret = 0
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret")
)
+ end
+
+ def test_check_box_checked_if_option_checked_is_present
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret" ,{"checked"=>"checked"})
)
+ end
+
+ def test_check_box_checked_if_object_value_is_true
@post.secret = true
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret")
)
+
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret?")
)
+ end
+ def test_check_box_checked_if_object_value_includes_checked_value
@post.secret = ['0']
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input id="post_secret" name="post[secret]" type="checkbox" value="1" />',
check_box("post", "secret")
)
+
@post.secret = ['1']
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
@@ -313,12 +403,109 @@ class FormHelperTest < ActionView::TestCase
)
end
- def test_check_box_with_explicit_checked_and_unchecked_values
+ def test_check_box_with_include_hidden_false
+ @post.secret = false
+ assert_dom_equal('<input id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret", :include_hidden => false))
+ end
+
+ def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_string
@post.secret = "on"
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="off" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="on" />',
check_box("post", "secret", {}, "on", "off")
)
+
+ @post.secret = "off"
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="off" /><input id="post_secret" name="post[secret]" type="checkbox" value="on" />',
+ check_box("post", "secret", {}, "on", "off")
+ )
+ end
+
+ def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_boolean
+ @post.secret = false
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="true" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="false" />',
+ check_box("post", "secret", {}, false, true)
+ )
+
+ @post.secret = true
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="true" /><input id="post_secret" name="post[secret]" type="checkbox" value="false" />',
+ check_box("post", "secret", {}, false, true)
+ )
+ end
+
+ def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_integer
+ @post.secret = 0
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = 1
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = 2
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+ end
+
+ def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_float
+ @post.secret = 0.0
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = 1.1
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = 2.2
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+ end
+
+ def test_check_box_with_explicit_checked_and_unchecked_values_when_object_value_is_big_decimal
+ @post.secret = BigDecimal.new(0)
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = BigDecimal.new(1)
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+
+ @post.secret = BigDecimal.new(2.2, 1)
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="1" /><input id="post_secret" name="post[secret]" type="checkbox" value="0" />',
+ check_box("post", "secret", {}, 0, 1)
+ )
+ end
+
+ def test_check_box_with_nil_unchecked_value
+ @post.secret = "on"
+ assert_dom_equal(
+ '<input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="on" />',
+ check_box("post", "secret", {}, "on", nil)
+ )
+ end
+
+ def test_check_box_with_nil_unchecked_value_is_html_safe
+ assert check_box("post", "secret", {}, "on", nil).html_safe?
end
def test_check_box_with_multiple_behavior
@@ -333,11 +520,17 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_checkbox_disabled_disables_hidden_field
+ assert_dom_equal(
+ '<input name="post[secret]" type="hidden" value="0" disabled="disabled"/><input checked="checked" disabled="disabled" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
+ check_box("post", "secret", { :disabled => true })
+ )
+ end
- def test_checkbox_disabled_still_submits_checked_value
+ def test_checkbox_form_html5_attribute
assert_dom_equal(
- '<input name="post[secret]" type="hidden" value="1" /><input checked="checked" disabled="disabled" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
- check_box("post", "secret", { :disabled => :true })
+ '<input form="new_form" name="post[secret]" type="hidden" value="0" /><input checked="checked" form="new_form" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
+ check_box("post", "secret", :form => "new_form")
)
end
@@ -382,7 +575,7 @@ class FormHelperTest < ActionView::TestCase
def test_text_area
assert_dom_equal(
- '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea id="post_body" name="post[body]">\nBack to the hill and over it again!</textarea>},
text_area("post", "body")
)
end
@@ -390,14 +583,14 @@ class FormHelperTest < ActionView::TestCase
def test_text_area_with_escapes
@post.body = "Back to <i>the</i> hill and over it again!"
assert_dom_equal(
- '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to &lt;i&gt;the&lt;/i&gt; hill and over it again!</textarea>',
+ %{<textarea id="post_body" name="post[body]">\nBack to &lt;i&gt;the&lt;/i&gt; hill and over it again!</textarea>},
text_area("post", "body")
)
end
def test_text_area_with_alternate_value
assert_dom_equal(
- '<textarea cols="40" id="post_body" name="post[body]" rows="20">Testing alternate values.</textarea>',
+ %{<textarea id="post_body" name="post[body]">\nTesting alternate values.</textarea>},
text_area("post", "body", :value => 'Testing alternate values.')
)
end
@@ -405,54 +598,110 @@ class FormHelperTest < ActionView::TestCase
def test_text_area_with_html_entities
@post.body = "The HTML Entity for & is &amp;"
assert_dom_equal(
- '<textarea cols="40" id="post_body" name="post[body]" rows="20">The HTML Entity for &amp; is &amp;amp;</textarea>',
+ %{<textarea id="post_body" name="post[body]">\nThe HTML Entity for &amp; is &amp;amp;</textarea>},
text_area("post", "body")
)
end
def test_text_area_with_size_option
assert_dom_equal(
- '<textarea cols="183" id="post_body" name="post[body]" rows="820">Back to the hill and over it again!</textarea>',
+ %{<textarea cols="183" id="post_body" name="post[body]" rows="820">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", :size => "183x820")
)
end
def test_search_field
- expected = %{<input id="contact_notes_query" size="30" name="contact[notes_query]" type="search" />}
+ expected = %{<input id="contact_notes_query" name="contact[notes_query]" type="search" />}
assert_dom_equal(expected, search_field("contact", "notes_query"))
end
def test_telephone_field
- expected = %{<input id="user_cell" size="30" name="user[cell]" type="tel" />}
+ expected = %{<input id="user_cell" name="user[cell]" type="tel" />}
assert_dom_equal(expected, telephone_field("user", "cell"))
end
+ def test_date_field
+ expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />}
+ assert_dom_equal(expected, date_field("post", "written_on"))
+ end
+
+ def test_date_field_with_datetime_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ assert_dom_equal(expected, date_field("post", "written_on"))
+ end
+
+ def test_date_field_with_timewithzone_value
+ previous_time_zone, Time.zone = Time.zone, 'UTC'
+ expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />}
+ @post.written_on = Time.zone.parse('2004-06-15 15:30:45')
+ assert_dom_equal(expected, date_field("post", "written_on"))
+ ensure
+ Time.zone = previous_time_zone
+ end
+
+ def test_date_field_with_nil_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="date" />}
+ @post.written_on = nil
+ assert_dom_equal(expected, date_field("post", "written_on"))
+ end
+
+ def test_time_field
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="00:00:00.000" />}
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ end
+
+ def test_time_field_with_datetime_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="01:02:03.000" />}
+ @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3)
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ end
+
+ def test_time_field_with_timewithzone_value
+ previous_time_zone, Time.zone = Time.zone, 'UTC'
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" value="01:02:03.000" />}
+ @post.written_on = Time.zone.parse('2004-06-15 01:02:03')
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ ensure
+ Time.zone = previous_time_zone
+ end
+
+ def test_time_field_with_nil_value
+ expected = %{<input id="post_written_on" name="post[written_on]" type="time" />}
+ @post.written_on = nil
+ assert_dom_equal(expected, time_field("post", "written_on"))
+ end
+
def test_url_field
- expected = %{<input id="user_homepage" size="30" name="user[homepage]" type="url" />}
+ expected = %{<input id="user_homepage" name="user[homepage]" type="url" />}
assert_dom_equal(expected, url_field("user", "homepage"))
end
def test_email_field
- expected = %{<input id="user_address" size="30" name="user[address]" type="email" />}
+ expected = %{<input id="user_address" name="user[address]" type="email" />}
assert_dom_equal(expected, email_field("user", "address"))
end
def test_number_field
- expected = %{<input name="order[quantity]" size="30" max="9" id="order_quantity" type="number" min="1" />}
+ expected = %{<input name="order[quantity]" max="9" id="order_quantity" type="number" min="1" />}
assert_dom_equal(expected, number_field("order", "quantity", :in => 1...10))
+ expected = %{<input name="order[quantity]" size="30" max="9" id="order_quantity" type="number" min="1" />}
+ assert_dom_equal(expected, number_field("order", "quantity", :size => 30, :in => 1...10))
end
def test_range_input
- expected = %{<input name="hifi[volume]" step="0.1" size="30" max="11" id="hifi_volume" type="range" min="0" />}
+ expected = %{<input name="hifi[volume]" step="0.1" max="11" id="hifi_volume" type="range" min="0" />}
assert_dom_equal(expected, range_field("hifi", "volume", :in => 0..11, :step => 0.1))
+ expected = %{<input name="hifi[volume]" step="0.1" size="30" max="11" id="hifi_volume" type="range" min="0" />}
+ assert_dom_equal(expected, range_field("hifi", "volume", :size => 30, :in => 0..11, :step => 0.1))
end
def test_explicit_name
assert_dom_equal(
- '<input id="post_title" name="dont guess" size="30" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess")
+ '<input id="post_title" name="dont guess" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess")
)
assert_dom_equal(
- '<textarea cols="40" id="post_body" name="really!" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea id="post_body" name="really!">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", "name" => "really!")
)
assert_dom_equal(
@@ -469,10 +718,10 @@ class FormHelperTest < ActionView::TestCase
def test_explicit_id
assert_dom_equal(
- '<input id="dont guess" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => "dont guess")
+ '<input id="dont guess" name="post[title]" type="text" value="Hello World" />', text_field("post", "title", "id" => "dont guess")
)
assert_dom_equal(
- '<textarea cols="40" id="really!" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea id="really!" name="post[body]">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", "id" => "really!")
)
assert_dom_equal(
@@ -489,10 +738,10 @@ class FormHelperTest < ActionView::TestCase
def test_nil_id
assert_dom_equal(
- '<input name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => nil)
+ '<input name="post[title]" type="text" value="Hello World" />', text_field("post", "title", "id" => nil)
)
assert_dom_equal(
- '<textarea cols="40" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea name="post[body]">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", "id" => nil)
)
assert_dom_equal(
@@ -519,11 +768,11 @@ class FormHelperTest < ActionView::TestCase
def test_index
assert_dom_equal(
- '<input name="post[5][title]" size="30" id="post_5_title" type="text" value="Hello World" />',
+ '<input name="post[5][title]" id="post_5_title" type="text" value="Hello World" />',
text_field("post", "title", "index" => 5)
)
assert_dom_equal(
- '<textarea cols="40" name="post[5][body]" id="post_5_body" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea name="post[5][body]" id="post_5_body">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", "index" => 5)
)
assert_dom_equal(
@@ -546,11 +795,11 @@ class FormHelperTest < ActionView::TestCase
def test_index_with_nil_id
assert_dom_equal(
- '<input name="post[5][title]" size="30" type="text" value="Hello World" />',
+ '<input name="post[5][title]" type="text" value="Hello World" />',
text_field("post", "title", "index" => 5, 'id' => nil)
)
assert_dom_equal(
- '<textarea cols="40" name="post[5][body]" rows="20">Back to the hill and over it again!</textarea>',
+ %{<textarea name="post[5][body]">\nBack to the hill and over it again!</textarea>},
text_area("post", "body", "index" => 5, 'id' => nil)
)
assert_dom_equal(
@@ -572,16 +821,16 @@ class FormHelperTest < ActionView::TestCase
end
def test_auto_index
- pid = @post.id
+ pid = 123
assert_dom_equal(
"<label for=\"post_#{pid}_title\">Title</label>",
label("post[]", "title")
)
assert_dom_equal(
- "<input id=\"post_#{pid}_title\" name=\"post[#{pid}][title]\" size=\"30\" type=\"text\" value=\"Hello World\" />", text_field("post[]","title")
+ "<input id=\"post_#{pid}_title\" name=\"post[#{pid}][title]\" type=\"text\" value=\"Hello World\" />", text_field("post[]","title")
)
assert_dom_equal(
- "<textarea cols=\"40\" id=\"post_#{pid}_body\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>",
+ "<textarea id=\"post_#{pid}_body\" name=\"post[#{pid}][body]\">\nBack to the hill and over it again!</textarea>",
text_area("post[]", "body")
)
assert_dom_equal(
@@ -589,7 +838,7 @@ class FormHelperTest < ActionView::TestCase
check_box("post[]", "secret")
)
assert_dom_equal(
-"<input checked=\"checked\" id=\"post_#{pid}_title_hello_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />",
+ "<input checked=\"checked\" id=\"post_#{pid}_title_hello_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />",
radio_button("post[]", "title", "Hello World")
)
assert_dom_equal("<input id=\"post_#{pid}_title_goodbye_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Goodbye World\" />",
@@ -598,13 +847,13 @@ class FormHelperTest < ActionView::TestCase
end
def test_auto_index_with_nil_id
- pid = @post.id
+ pid = 123
assert_dom_equal(
- "<input name=\"post[#{pid}][title]\" size=\"30\" type=\"text\" value=\"Hello World\" />",
+ "<input name=\"post[#{pid}][title]\" type=\"text\" value=\"Hello World\" />",
text_field("post[]","title", :id => nil)
)
assert_dom_equal(
- "<textarea cols=\"40\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>",
+ "<textarea name=\"post[#{pid}][body]\">\nBack to the hill and over it again!</textarea>",
text_area("post[]", "body", :id => nil)
)
assert_dom_equal(
@@ -633,15 +882,55 @@ class FormHelperTest < ActionView::TestCase
concat f.text_area(:body)
concat f.check_box(:secret)
concat f.submit('Create post')
+ concat f.button('Create post')
end
- expected = whole_form("/posts/123", "create-post" , "edit_post", :method => "put") do
+ expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do
"<label for='post_title'>The Title</label>" +
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
- "<input name='commit' type='submit' value='Create post' />"
+ "<input name='commit' type='submit' value='Create post' />" +
+ "<button name='button' type='submit'>Create post</button>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_for_with_collection_radio_buttons
+ post = Post.new
+ def post.active; false; end
+ form_for(post) do |f|
+ concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s)
+ end
+
+ expected = whole_form("/posts", "new_post" , "new_post") do
+ "<input id='post_active_true' name='post[active]' type='radio' value='true' />" +
+ "<label for='post_active_true'>true</label>" +
+ "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" +
+ "<label for='post_active_false'>false</label>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_for_with_collection_check_boxes
+ post = Post.new
+ def post.tag_ids; [1, 3]; end
+ collection = (1..3).map{|i| [i, "Tag #{i}"] }
+ form_for(post) do |f|
+ concat f.collection_check_boxes(:tag_ids, collection, :first, :last)
+ end
+
+ expected = whole_form("/posts", "new_post" , "new_post") do
+ "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" +
+ "<label for='post_tag_ids_1'>Tag 1</label>" +
+ "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" +
+ "<label for='post_tag_ids_2'>Tag 2</label>" +
+ "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" +
+ "<label for='post_tag_ids_3'>Tag 3</label>" +
+ "<input name='post[tag_ids][]' type='hidden' value='' />"
end
assert_dom_equal expected, output_buffer
@@ -654,7 +943,7 @@ class FormHelperTest < ActionView::TestCase
concat f.file_field(:file)
end
- expected = whole_form("/posts/123", "create-post" , "edit_post", :method => "put", :multipart => true) do
+ expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch', :multipart => true) do
"<input name='post[file]' type='file' id='post_file' />"
end
@@ -670,7 +959,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form("/posts/123", "edit_post_123" , "edit_post", :method => "put", :multipart => true) do
+ expected = whole_form("/posts/123", "edit_post_123" , "edit_post", :method => 'patch', :multipart => true) do
"<input name='post[comment][file]' type='file' id='post_comment_file' />"
end
@@ -683,21 +972,21 @@ class FormHelperTest < ActionView::TestCase
concat f.label(:title)
end
- expected = whole_form("/posts/123.json", "edit_post_123" , "edit_post", :method => "put") do
+ expected = whole_form("/posts/123.json", "edit_post_123" , "edit_post", :method => 'patch') do
"<label for='post_title'>Title</label>"
end
assert_dom_equal expected, output_buffer
end
- def test_form_for_with_isolated_namespaced_model
+ def test_form_for_with_model_using_relative_model_naming
form_for(@blog_post) do |f|
concat f.text_field :title
concat f.submit('Edit post')
end
- expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => "put") do
- "<input name='post[title]' size='30' type='text' id='post_title' value='And his name will be forty and four.' />" +
+ expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => 'patch') do
+ "<input name='post[title]' type='text' id='post_title' value='And his name will be forty and four.' />" +
"<input name='commit' type='submit' value='Edit post' />"
end
@@ -713,10 +1002,10 @@ class FormHelperTest < ActionView::TestCase
concat f.submit('Create post')
end
- expected = whole_form("/posts/123", "create-post", "other_name_edit", :method => "put") do
+ expected = whole_form("/posts/123", "create-post", "edit_other_name", :method => 'patch') do
"<label for='other_name_title' class='post_title'>Title</label>" +
- "<input name='other_name[title]' size='30' id='other_name_title' value='Hello World' type='text' />" +
- "<textarea name='other_name[body]' id='other_name_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='other_name[title]' id='other_name_title' value='Hello World' type='text' />" +
+ "<textarea name='other_name[body]' id='other_name_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='other_name[secret]' value='0' type='hidden' />" +
"<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" +
"<input name='commit' value='Create post' type='submit' />"
@@ -733,8 +1022,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/", "create-post", "edit_post", "delete") do
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -750,8 +1039,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/", "create-post", "edit_post", "delete") do
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -767,22 +1056,22 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/search", "search-post", "new_post", "get") do
- "<input name='post[title]' size='30' type='search' id='post_title' />"
+ "<input name='post[title]' type='search' id='post_title' />"
end
assert_dom_equal expected, output_buffer
end
def test_form_for_with_remote
- form_for(@post, :url => '/', :remote => true, :html => { :id => 'create-post', :method => :put }) do |f|
+ form_for(@post, :url => '/', :remote => true, :html => { :id => 'create-post', :method => :patch}) do |f|
concat f.text_field(:title)
concat f.text_area(:body)
concat f.check_box(:secret)
end
- expected = whole_form("/", "create-post", "edit_post", :method => "put", :remote => true) do
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ expected = whole_form("/", "create-post", "edit_post", :method => 'patch', :remote => true) do
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -791,15 +1080,15 @@ class FormHelperTest < ActionView::TestCase
end
def test_form_for_with_remote_in_html
- form_for(@post, :url => '/', :html => { :remote => true, :id => 'create-post', :method => :put }) do |f|
+ form_for(@post, :url => '/', :html => { :remote => true, :id => 'create-post', :method => :patch }) do |f|
concat f.text_field(:title)
concat f.text_area(:body)
concat f.check_box(:secret)
end
- expected = whole_form("/", "create-post", "edit_post", :method => "put", :remote => true) do
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ expected = whole_form("/", "create-post", "edit_post", :method => 'patch', :remote => true) do
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -809,6 +1098,7 @@ class FormHelperTest < ActionView::TestCase
def test_form_for_with_remote_without_html
@post.persisted = false
+ @post.stubs(:to_key).returns(nil)
form_for(@post, :remote => true) do |f|
concat f.text_field(:title)
concat f.text_area(:body)
@@ -816,8 +1106,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts", 'new_post', 'new_post', :remote => true) do
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -833,8 +1123,8 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/", "create-post") do
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
end
@@ -850,10 +1140,10 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form('/posts/123', 'post[]_edit', 'post[]_edit', 'put') do
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
"<label for='post_123_title'>Title</label>" +
- "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
- "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" +
+ "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[123][secret]' type='hidden' value='0' />" +
"<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />"
end
@@ -868,9 +1158,9 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form('/posts/123', 'post[]_edit', 'post[]_edit', 'put') do
- "<input name='post[][title]' size='30' type='text' id='post__title' value='Hello World' />" +
- "<textarea name='post[][body]' id='post__body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
+ "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" +
+ "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[][secret]' type='hidden' value='0' />" +
"<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />"
end
@@ -878,10 +1168,143 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_label_error_wrapping
+ form_for(@post) do |f|
+ concat f.label(:author_name, :class => 'label')
+ concat f.text_field(:author_name)
+ concat f.submit('Create post')
+ end
+
+ expected = whole_form('/posts/123', 'edit_post_123' , 'edit_post', 'patch') do
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" +
+ "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" +
+ "<input name='commit' type='submit' value='Create post' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_for_label_error_wrapping_without_conventional_instance_variable
+ post = remove_instance_variable :@post
+
+ form_for(post) do |f|
+ concat f.label(:author_name, :class => 'label')
+ concat f.text_field(:author_name)
+ concat f.submit('Create post')
+ end
+
+ expected = whole_form('/posts/123', 'edit_post_123' , 'edit_post', 'patch') do
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" +
+ "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" +
+ "<input name='commit' type='submit' value='Create post' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_for_label_error_wrapping_block_and_non_block_versions
+ form_for(@post) do |f|
+ concat f.label(:author_name, 'Name', :class => 'label')
+ concat f.label(:author_name, :class => 'label') { 'Name' }
+ end
+
+ expected = whole_form('/posts/123', 'edit_post_123' , 'edit_post', 'patch') do
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" +
+ "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_for_with_namespace
+ form_for(@post, :namespace => 'namespace') do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do
+ "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='namespace_post_secret' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_for_with_namespace_with_date_select
+ form_for(@post, :namespace => 'namespace') do |f|
+ concat f.date_select(:written_on)
+ end
+
+ assert_select 'select#namespace_post_written_on_1i'
+ end
+
+ def test_form_for_with_namespace_with_label
+ form_for(@post, :namespace => 'namespace') do |f|
+ concat f.label(:title)
+ concat f.text_field(:title)
+ end
+
+ expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do
+ "<label for='namespace_post_title'>Title</label>" +
+ "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_two_form_for_with_namespace
+ form_for(@post, :namespace => 'namespace_1') do |f|
+ concat f.label(:title)
+ concat f.text_field(:title)
+ end
+
+ expected_1 = whole_form('/posts/123', 'namespace_1_edit_post_123', 'edit_post', 'patch') do
+ "<label for='namespace_1_post_title'>Title</label>" +
+ "<input name='post[title]' type='text' id='namespace_1_post_title' value='Hello World' />"
+ end
+
+ assert_dom_equal expected_1, output_buffer
+
+ form_for(@post, :namespace => 'namespace_2') do |f|
+ concat f.label(:title)
+ concat f.text_field(:title)
+ end
+
+ expected_2 = whole_form('/posts/123', 'namespace_2_edit_post_123', 'edit_post', 'patch') do
+ "<label for='namespace_2_post_title'>Title</label>" +
+ "<input name='post[title]' type='text' id='namespace_2_post_title' value='Hello World' />"
+ end
+
+ assert_dom_equal expected_2, output_buffer
+ end
+
+ def test_fields_for_with_namespace
+ @comment.body = 'Hello World'
+ form_for(@post, :namespace => 'namespace') do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.fields_for(@comment) { |c|
+ concat c.text_field(:body)
+ }
+ end
+
+ expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do
+ "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[comment][body]' type='text' id='namespace_post_comment_body' value='Hello World' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_submit_with_object_as_new_record_and_locale_strings
old_locale, I18n.locale = I18n.locale, :submit
@post.persisted = false
+ @post.stubs(:to_key).returns(nil)
form_for(@post) do |f|
concat f.submit
end
@@ -902,7 +1325,7 @@ class FormHelperTest < ActionView::TestCase
concat f.submit
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
"<input name='commit' type='submit' value='Confirm Post changes' />"
end
@@ -934,7 +1357,7 @@ class FormHelperTest < ActionView::TestCase
concat f.submit
end
- expected = whole_form('/posts/123', 'another_post_edit', 'another_post_edit', :method => 'put') do
+ expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', :method => 'patch') do
"<input name='commit' type='submit' value='Update your Post' />"
end
@@ -951,8 +1374,8 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- "<input name='post[comment][body]' size='30' type='text' id='post_comment_body' value='Hello World' />"
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ "<input name='post[comment][body]' type='text' id='post_comment_body' value='Hello World' />"
end
assert_dom_equal expected, output_buffer
@@ -966,9 +1389,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'post[]_edit', 'post[]_edit', 'put') do
- "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
- "<input name='post[123][comment][][name]' size='30' type='text' id='post_123_comment__name' value='new comment' />"
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
+ "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" +
+ "<input name='post[123][comment][][name]' type='text' id='post_123_comment__name' value='new comment' />"
end
assert_dom_equal expected, output_buffer
@@ -982,9 +1405,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do
- "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" +
- "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' />"
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do
+ "<input name='post[1][title]' type='text' id='post_1_title' value='Hello World' />" +
+ "<input name='post[1][comment][1][name]' type='text' id='post_1_comment_1_name' value='new comment' />"
end
assert_dom_equal expected, output_buffer
@@ -997,8 +1420,8 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do
- "<input name='post[1][comment][title]' size='30' type='text' id='post_1_comment_title' value='Hello World' />"
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do
+ "<input name='post[1][comment][title]' type='text' id='post_1_comment_title' value='Hello World' />"
end
assert_dom_equal expected, output_buffer
@@ -1011,8 +1434,8 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do
- "<input name='post[1][comment][5][title]' size='30' type='text' id='post_1_comment_5_title' value='Hello World' />"
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do
+ "<input name='post[1][comment][5][title]' type='text' id='post_1_comment_5_title' value='Hello World' />"
end
assert_dom_equal expected, output_buffer
@@ -1025,8 +1448,8 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'post[]_edit', 'post[]_edit', 'put') do
- "<input name='post[123][comment][title]' size='30' type='text' id='post_123_comment_title' value='Hello World' />"
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
+ "<input name='post[123][comment][title]' type='text' id='post_123_comment_title' value='Hello World' />"
end
assert_dom_equal expected, output_buffer
@@ -1039,7 +1462,7 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do
"<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />"
end
@@ -1053,8 +1476,8 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'post[]_edit', 'post[]_edit', 'put') do
- "<input name='post[123][comment][123][title]' size='30' type='text' id='post_123_comment_123_title' value='Hello World' />"
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
+ "<input name='post[123][comment][123][title]' type='text' id='post_123_comment_123_title' value='Hello World' />"
end
assert_dom_equal expected, output_buffer
@@ -1073,10 +1496,10 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'post[]_edit', 'post[]_edit', 'put') do
- "<input name='post[123][comment][5][title]' size='30' type='text' id='post_123_comment_5_title' value='Hello World' />"
- end + whole_form('/posts/123', 'post_edit', 'post_edit', 'put') do
- "<input name='post[1][comment][123][title]' size='30' type='text' id='post_1_comment_123_title' value='Hello World' />"
+ expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do
+ "<input name='post[123][comment][5][title]' type='text' id='post_123_comment_5_title' value='Hello World' />"
+ end + whole_form('/posts/123', 'edit_post', 'edit_post', 'patch') do
+ "<input name='post[1][comment][123][title]' type='text' id='post_1_comment_123_title' value='Hello World' />"
end
assert_dom_equal expected, output_buffer
@@ -1092,9 +1515,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="new author" />'
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="new author" />'
end
assert_dom_equal expected, output_buffer
@@ -1119,9 +1542,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
end
@@ -1138,9 +1561,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
end
@@ -1157,9 +1580,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />'
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />'
end
assert_dom_equal expected, output_buffer
@@ -1175,9 +1598,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />'
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />'
end
assert_dom_equal expected, output_buffer
@@ -1193,9 +1616,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
end
@@ -1213,10 +1636,10 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />'
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />'
end
assert_dom_equal expected, output_buffer
@@ -1234,11 +1657,11 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
end
@@ -1261,12 +1684,12 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />'
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
end
assert_dom_equal expected, output_buffer
@@ -1288,11 +1711,11 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />'
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
end
assert_dom_equal expected, output_buffer
@@ -1314,12 +1737,12 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' +
'<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />'
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
end
assert_dom_equal expected, output_buffer
@@ -1337,11 +1760,11 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
end
@@ -1361,12 +1784,12 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />'
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'
end
assert_dom_equal expected, output_buffer
@@ -1384,10 +1807,10 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />'
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="new comment" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />'
end
assert_dom_equal expected, output_buffer
@@ -1405,11 +1828,11 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />'
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />'
end
assert_dom_equal expected, output_buffer
@@ -1423,8 +1846,8 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />'
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />'
end
assert_dom_equal expected, output_buffer
@@ -1440,11 +1863,11 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
end
@@ -1461,11 +1884,11 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
end
@@ -1483,11 +1906,11 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' +
'<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
end
@@ -1506,11 +1929,11 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input name="post[title]" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />'
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />'
end
assert_dom_equal expected, output_buffer
@@ -1526,14 +1949,64 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' +
'<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
end
assert_dom_equal expected, output_buffer
end
+ def test_nested_fields_for_index_method_with_existing_records_on_a_nested_attributes_collection_association
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_for(@post) do |f|
+ expected = 0
+ @post.comments.each do |comment|
+ f.fields_for(:comments, comment) { |cf|
+ assert_equal cf.index, expected
+ expected += 1
+ }
+ end
+ end
+ end
+
+ def test_nested_fields_for_index_method_with_existing_and_new_records_on_a_nested_attributes_collection_association
+ @post.comments = [Comment.new(321), Comment.new]
+
+ form_for(@post) do |f|
+ expected = 0
+ @post.comments.each do |comment|
+ f.fields_for(:comments, comment) { |cf|
+ assert_equal cf.index, expected
+ expected += 1
+ }
+ end
+ end
+ end
+
+ def test_nested_fields_for_index_method_with_existing_records_on_a_supplied_nested_attributes_collection
+ @post.comments = Array.new(2) { |id| Comment.new(id + 1) }
+
+ form_for(@post) do |f|
+ expected = 0
+ f.fields_for(:comments, @post.comments) { |cf|
+ assert_equal cf.index, expected
+ expected += 1
+ }
+ end
+ end
+
+ def test_nested_fields_for_index_method_with_child_index_option_override_on_a_nested_attributes_collection_association
+ @post.comments = []
+
+ form_for(@post) do |f|
+ f.fields_for(:comments, Comment.new(321), :child_index => 'abc') { |cf|
+ assert_equal cf.index, 'abc'
+ }
+ end
+ end
+
def test_nested_fields_uses_unique_indices_for_different_collection_associations
@post.comments = [Comment.new(321)]
@post.tags = [Tag.new(123), Tag.new(456)]
@@ -1562,17 +2035,17 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
- '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' +
'<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' +
'<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
- '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' +
- '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' +
+ '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" type="text" value="tag #123" />' +
+ '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' +
'<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' +
'<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' +
- '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' +
- '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' +
+ '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" type="text" value="tag #456" />' +
+ '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' +
'<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' +
'<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />'
end
@@ -1589,8 +2062,8 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="hash backed author" />'
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="hash backed author" />'
end
assert_dom_equal expected, output_buffer
@@ -1604,8 +2077,8 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
@@ -1620,8 +2093,8 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
- "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" +
+ "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[123][secret]' type='hidden' value='0' />" +
"<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />"
@@ -1636,8 +2109,8 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<input name='post[][title]' size='30' type='text' id='post__title' value='Hello World' />" +
- "<textarea name='post[][body]' id='post__body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" +
+ "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[][secret]' type='hidden' value='0' />" +
"<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />"
@@ -1652,8 +2125,8 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<input name='post[abc][title]' size='30' type='text' id='post_abc_title' value='Hello World' />" +
- "<textarea name='post[abc][body]' id='post_abc_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[abc][title]' type='text' id='post_abc_title' value='Hello World' />" +
+ "<textarea name='post[abc][body]' id='post_abc_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[abc][secret]' type='hidden' value='0' />" +
"<input name='post[abc][secret]' checked='checked' type='checkbox' id='post_abc_secret' value='1' />"
@@ -1668,8 +2141,8 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
@@ -1684,8 +2157,8 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
"<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
@@ -1699,7 +2172,7 @@ class FormHelperTest < ActionView::TestCase
end
assert_dom_equal "<label for=\"author_post_title\">Title</label>" +
- "<input name='author[post][title]' size='30' type='text' id='author_post_title' value='Hello World' />",
+ "<input name='author[post][title]' type='text' id='author_post_title' value='Hello World' />",
output_buffer
end
@@ -1710,7 +2183,7 @@ class FormHelperTest < ActionView::TestCase
end
assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" +
- "<input name='author[post][1][title]' size='30' type='text' id='author_post_1_title' value='Hello World' />",
+ "<input name='author[post][1][title]' type='text' id='author_post_1_title' value='Hello World' />",
output_buffer
end
@@ -1728,9 +2201,9 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'create-post', 'post_edit', :method => 'put') do
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'patch') do
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
"<input name='parent_post[secret]' type='hidden' value='0' />" +
"<input name='parent_post[secret]' checked='checked' type='checkbox' id='parent_post_secret' value='1' />"
end
@@ -1748,10 +2221,10 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'create-post', 'post_edit', :method => 'put') do
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
- "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />"
+ expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'patch') do
+ "<input name='post[title]' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" +
+ "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' />"
end
assert_dom_equal expected, output_buffer
@@ -1764,8 +2237,8 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do
- "<input name='post[category][name]' type='text' size='30' id='post_category_name' />"
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do
+ "<input name='post[category][name]' type='text' id='post_category_name' />"
end
assert_dom_equal expected, output_buffer
@@ -1788,60 +2261,46 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
- "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" +
"<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
end
assert_dom_equal expected, output_buffer
end
- def hidden_fields(method = nil)
- txt = %{<div style="margin:0;padding:0;display:inline">}
- txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
- if method && !method.to_s.in?(['get', 'post'])
- txt << %{<input name="_method" type="hidden" value="#{method}" />}
- end
- txt << %{</div>}
- end
-
- def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil)
- txt = %{<form accept-charset="UTF-8" action="#{action}"}
- txt << %{ enctype="multipart/form-data"} if multipart
- txt << %{ data-remote="true"} if remote
- txt << %{ class="#{html_class}"} if html_class
- txt << %{ id="#{id}"} if id
- method = method.to_s == "get" ? "get" : "post"
- txt << %{ method="#{method}">}
- end
+ def test_default_form_builder
+ old_default_form_builder, ActionView::Base.default_form_builder =
+ ActionView::Base.default_form_builder, LabelledFormBuilder
- def whole_form(action = "/", id = nil, html_class = nil, options = nil)
- contents = block_given? ? yield : ""
+ form_for(@post) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
- if options.is_a?(Hash)
- method, remote, multipart = options.values_at(:method, :remote, :multipart)
- else
- method = options
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" +
+ "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
end
- form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>"
+ assert_dom_equal expected, output_buffer
+ ensure
+ ActionView::Base.default_form_builder = old_default_form_builder
end
- def test_default_form_builder
+ def test_lazy_loading_default_form_builder
old_default_form_builder, ActionView::Base.default_form_builder =
- ActionView::Base.default_form_builder, LabelledFormBuilder
+ ActionView::Base.default_form_builder, "FormHelperTest::LabelledFormBuilder"
form_for(@post) do |f|
concat f.text_field(:title)
- concat f.text_area(:body)
- concat f.check_box(:secret)
end
- expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do
- "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
- "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
- "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
+ expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>"
end
assert_dom_equal expected, output_buffer
@@ -1857,8 +2316,8 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
- "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
+ "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" +
"<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
assert_dom_equal expected, output_buffer
@@ -1918,7 +2377,7 @@ class FormHelperTest < ActionView::TestCase
def test_form_for_with_html_options_adds_options_to_form_tag
form_for(@post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
- expected = whole_form("/posts/123", "some_form", "some_class", 'put')
+ expected = whole_form("/posts/123", "some_form", "some_class", 'patch')
assert_dom_equal expected, output_buffer
end
@@ -1926,7 +2385,7 @@ class FormHelperTest < ActionView::TestCase
def test_form_for_with_string_url_option
form_for(@post, :url => 'http://www.otherdomain.com') do |f| end
- assert_equal whole_form("http://www.otherdomain.com", 'edit_post_123', 'edit_post', 'put'), output_buffer
+ assert_equal whole_form("http://www.otherdomain.com", 'edit_post_123', 'edit_post', 'patch'), output_buffer
end
def test_form_for_with_hash_url_option
@@ -1939,21 +2398,21 @@ class FormHelperTest < ActionView::TestCase
def test_form_for_with_record_url_option
form_for(@post, :url => @post) do |f| end
- expected = whole_form("/posts/123", 'edit_post_123', 'edit_post', 'put')
+ expected = whole_form("/posts/123", 'edit_post_123', 'edit_post', 'patch')
assert_equal expected, output_buffer
end
def test_form_for_with_existing_object
form_for(@post) do |f| end
- expected = whole_form("/posts/123", "edit_post_123", "edit_post", "put")
+ expected = whole_form("/posts/123", "edit_post_123", "edit_post", 'patch')
assert_equal expected, output_buffer
end
def test_form_for_with_new_object
post = Post.new
post.persisted = false
- def post.id() nil end
+ def post.to_key; nil; end
form_for(post) do |f| end
@@ -1965,7 +2424,7 @@ class FormHelperTest < ActionView::TestCase
@comment.save
form_for([@post, @comment]) {}
- expected = whole_form(post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put")
+ expected = whole_form(post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", 'patch')
assert_dom_equal expected, output_buffer
end
@@ -1980,7 +2439,7 @@ class FormHelperTest < ActionView::TestCase
@comment.save
form_for([:admin, @post, @comment]) {}
- expected = whole_form(admin_post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put")
+ expected = whole_form(admin_post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", 'patch')
assert_dom_equal expected, output_buffer
end
@@ -1994,18 +2453,67 @@ class FormHelperTest < ActionView::TestCase
def test_form_for_with_existing_object_and_custom_url
form_for(@post, :url => "/super_posts") do |f| end
- expected = whole_form("/super_posts", "edit_post_123", "edit_post", "put")
+ expected = whole_form("/super_posts", "edit_post_123", "edit_post", 'patch')
assert_equal expected, output_buffer
end
+ def test_form_for_with_default_method_as_patch
+ form_for(@post) {}
+ expected = whole_form("/posts/123", "edit_post_123", "edit_post", "patch")
+ assert_dom_equal expected, output_buffer
+ end
+
def test_fields_for_returns_block_result
output = fields_for(Post.new) { |f| "fields" }
assert_equal "fields", output
end
+ def test_form_builder_block_argument_deprecation
+ builder_class = Class.new(ActionView::Helpers::FormBuilder) do
+ def initialize(object_name, object, template, options, block)
+ super
+ end
+ end
+
+ assert_deprecated(/Giving a block to FormBuilder is deprecated and has no effect anymore/) do
+ builder_class.new(:foo, nil, nil, {}, proc {})
+ end
+ end
+
protected
- def protect_against_forgery?
- false
+
+ def hidden_fields(method = nil)
+ txt = %{<div style="margin:0;padding:0;display:inline">}
+ txt << %{<input name="utf8" type="hidden" value="&#x2713;" />}
+ if method && !method.to_s.in?(['get', 'post'])
+ txt << %{<input name="_method" type="hidden" value="#{method}" />}
+ end
+ txt << %{</div>}
+ end
+
+ def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil)
+ txt = %{<form accept-charset="UTF-8" action="#{action}"}
+ txt << %{ enctype="multipart/form-data"} if multipart
+ txt << %{ data-remote="true"} if remote
+ txt << %{ class="#{html_class}"} if html_class
+ txt << %{ id="#{id}"} if id
+ method = method.to_s == "get" ? "get" : "post"
+ txt << %{ method="#{method}">}
+ end
+
+ def whole_form(action = "/", id = nil, html_class = nil, options = nil)
+ contents = block_given? ? yield : ""
+
+ if options.is_a?(Hash)
+ method, remote, multipart = options.values_at(:method, :remote, :multipart)
+ else
+ method = options
end
+ form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>"
+ end
+
+ def protect_against_forgery?
+ false
+ end
end
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index d3e0cc41a9..2322fb0406 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -87,6 +87,20 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_collection_options_with_proc_for_value_method
+ assert_dom_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
+ options_from_collection_for_select(dummy_posts, lambda { |p| p.author_name }, "title")
+ )
+ end
+
+ def test_collection_options_with_proc_for_text_method
+ assert_dom_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
+ options_from_collection_for_select(dummy_posts, "author_name", lambda { |p| p.title })
+ )
+ end
+
def test_string_options_for_select
options = "<option value=\"Denmark\">Denmark</option><option value=\"USA\">USA</option><option value=\"Sweden\">Sweden</option>"
assert_dom_equal(
@@ -144,6 +158,13 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_range_options_for_select
+ assert_dom_equal(
+ "<option value=\"1\">1</option>\n<option value=\"2\">2</option>\n<option value=\"3\">3</option>",
+ options_for_select(1..3)
+ )
+ end
+
def test_array_options_for_string_include_in_other_string_bug_fix
assert_dom_equal(
"<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>",
@@ -161,16 +182,16 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_hash_options_for_select
assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
- options_for_select("$" => "Dollar", "<DKR>" => "<Kroner>").split("\n").sort.join("\n")
+ "<option value=\"Dollar\">$</option>\n<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>",
+ options_for_select("$" => "Dollar", "<DKR>" => "<Kroner>").split("\n").join("\n")
)
assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
- options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar").split("\n").sort.join("\n")
+ "<option value=\"Dollar\" selected=\"selected\">$</option>\n<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar").split("\n").join("\n")
)
assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
- options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ]).split("\n").sort.join("\n")
+ "<option value=\"Dollar\" selected=\"selected\">$</option>\n<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ]).split("\n").join("\n")
)
end
@@ -275,10 +296,34 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
- def test_grouped_options_for_select_with_selected_and_prompt
+ def test_grouped_options_for_select_with_optional_divider
assert_dom_equal(
+ "<optgroup label=\"----------\"><option value=\"US\">US</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"----------\"><option value=\"GB\">GB</option>\n<option value=\"Germany\">Germany</option></optgroup>",
+
+ grouped_options_for_select([['US',"Canada"] , ["GB", "Germany"]], nil, divider: "----------")
+ )
+ end
+
+ def test_grouped_options_for_select_with_selected_and_prompt_deprecated
+ assert_deprecated 'Passing the prompt to grouped_options_for_select as an argument is deprecated. Please use an options hash like `{ prompt: "Choose a product..." }`.' do
+ assert_dom_equal(
"<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", "Choose a product...")
+ )
+ end
+ end
+
+ def test_grouped_options_for_select_with_selected_and_prompt
+ assert_dom_equal(
+ "<option value=\"\">Choose a product...</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", prompt: "Choose a product...")
+ )
+ end
+
+ def test_grouped_options_for_select_with_selected_and_prompt_true
+ assert_dom_equal(
+ "<option value=\"\">Please select</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option selected=\"selected\" value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], "Cowboy Hat", prompt: true)
)
end
@@ -286,10 +331,18 @@ class FormOptionsHelperTest < ActionView::TestCase
assert grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]]).html_safe?
end
+ def test_grouped_options_for_select_with_prompt_returns_html_escaped_string_deprecated
+ ActiveSupport::Deprecation.silence do
+ assert_dom_equal(
+ "<option value=\"\">&lt;Choose One&gt;</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, '<Choose One>'))
+ end
+ end
+
def test_grouped_options_for_select_with_prompt_returns_html_escaped_string
assert_dom_equal(
"<option value=\"\">&lt;Choose One&gt;</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
- grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, '<Choose One>'))
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, prompt: '<Choose One>'))
end
def test_optgroups_with_with_options_with_hash
@@ -488,7 +541,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_select_under_fields_for_with_string_and_given_prompt
@post = Post.new
- options = "<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>"
+ options = "<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>".html_safe
output_buffer = fields_for :post, @post do |f|
concat f.select(:category, options, :prompt => 'The prompt')
@@ -508,6 +561,14 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_multiple_and_without_hidden_input
+ output_buffer = select(:post, :category, "", {:include_hidden => false}, :multiple => true)
+ assert_dom_equal(
+ "<select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>",
+ output_buffer
+ )
+ end
+
def test_select_with_multiple_and_disabled_to_add_disabled_hidden_input
output_buffer = select(:post, :category, "", {}, :multiple => true, :disabled => true)
assert_dom_equal(
@@ -596,6 +657,75 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_nil
+ @post = Post.new
+ @post.category = "othervalue"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\"></option>\n<option value=\"othervalue\" selected=\"selected\">othervalue</option></select>",
+ select("post", "category", [nil, "othervalue"])
+ )
+ end
+
+ def test_required_select
+ assert_dom_equal(
+ %(<select id="post_category" name="post[category]" required="required"><option value=""></option>\n<option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), {}, required: true)
+ )
+ end
+
+ def test_required_select_with_include_blank_prompt
+ assert_dom_equal(
+ %(<select id="post_category" name="post[category]" required="required"><option value="">Select one</option>\n<option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), { include_blank: "Select one" }, required: true)
+ )
+ end
+
+ def test_required_select_with_prompt
+ assert_dom_equal(
+ %(<select id="post_category" name="post[category]" required="required"><option value="">Select one</option>\n<option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), { prompt: "Select one" }, required: true)
+ )
+ end
+
+ def test_required_select_display_size_equals_to_one
+ assert_dom_equal(
+ %(<select id="post_category" name="post[category]" required="required" size="1"><option value=""></option>\n<option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), {}, required: true, size: 1)
+ )
+ end
+
+ def test_required_select_with_display_size_bigger_than_one
+ assert_dom_equal(
+ %(<select id="post_category" name="post[category]" required="required" size="2"><option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), {}, required: true, size: 2)
+ )
+ end
+
+ def test_required_select_with_multiple_option
+ assert_dom_equal(
+ %(<input name="post[category][]" type="hidden" value=""/><select id="post_category" multiple="multiple" name="post[category][]" required="required"><option value="abe">abe</option>\n<option value="mus">mus</option>\n<option value="hest">hest</option></select>),
+ select("post", "category", %w(abe mus hest), {}, required: true, multiple: true)
+ )
+ end
+
+ def test_select_with_fixnum
+ @post = Post.new
+ @post.category = ""
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n<option value=\"1\">1</option></select>",
+ select("post", "category", [1], :prompt => true, :include_blank => true)
+ )
+ end
+
+ def test_list_of_lists
+ @post = Post.new
+ @post.category = ""
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></option>\n<option value=\"number\">Number</option>\n<option value=\"text\">Text</option>\n<option value=\"boolean\">Yes/No</option></select>",
+ select("post", "category", [["Number", "number"], ["Text", "text"], ["Yes/No", "boolean"]], :prompt => true, :include_blank => true)
+ )
+ end
+
def test_select_with_selected_value
@post = Post.new
@post.category = "<mus>"
@@ -617,6 +747,13 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_escapes_options
+ assert_dom_equal(
+ '<select id="post_title" name="post[title]">&lt;script&gt;alert(1)&lt;/script&gt;</select>',
+ select('post', 'title', '<script>alert(1)</script>')
+ )
+ end
+
def test_select_with_selected_nil
@post = Post.new
@post.category = "<mus>"
@@ -644,6 +781,15 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_range
+ @post = Post.new
+ @post.category = 0
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"1\">1</option>\n<option value=\"2\">2</option>\n<option value=\"3\">3</option></select>",
+ select("post", "category", 1..3)
+ )
+ end
+
def test_collection_select
@post = Post.new
@post.author_name = "Babe"
@@ -747,7 +893,25 @@ class FormOptionsHelperTest < ActionView::TestCase
assert_dom_equal(
"<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\" disabled=\"disabled\">Cabe</option></select>",
collection_select("post", "author_name", dummy_posts, "author_name", "author_name", :disabled => 'Cabe')
- )
+ )
+ end
+
+ def test_collection_select_with_proc_for_value_method
+ @post = Post.new
+
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option></select>",
+ collection_select("post", "author_name", dummy_posts, lambda { |p| p.author_name }, "title")
+ )
+ end
+
+ def test_collection_select_with_proc_for_text_method
+ @post = Post.new
+
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option></select>",
+ collection_select("post", "author_name", dummy_posts, "author_name", lambda { |p| p.title })
+ )
end
def test_time_zone_select
@@ -981,36 +1145,36 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_option_html_attributes_from_without_hash
- assert_dom_equal(
- "",
+ assert_equal(
+ {},
option_html_attributes([ 'foo', 'bar' ])
)
end
def test_option_html_attributes_with_single_element_hash
- assert_dom_equal(
- " class=\"fancy\"",
+ assert_equal(
+ {:class => 'fancy'},
option_html_attributes([ 'foo', 'bar', { :class => 'fancy' } ])
)
end
def test_option_html_attributes_with_multiple_element_hash
- assert_dom_equal(
- " class=\"fancy\" onclick=\"alert('Hello World');\"",
+ assert_equal(
+ {:class => 'fancy', 'onclick' => "alert('Hello World');"},
option_html_attributes([ 'foo', 'bar', { :class => 'fancy', 'onclick' => "alert('Hello World');" } ])
)
end
def test_option_html_attributes_with_multiple_hashes
- assert_dom_equal(
- " class=\"fancy\" onclick=\"alert('Hello World');\"",
+ assert_equal(
+ {:class => 'fancy', 'onclick' => "alert('Hello World');"},
option_html_attributes([ 'foo', 'bar', { :class => 'fancy' }, { 'onclick' => "alert('Hello World');" } ])
)
end
def test_option_html_attributes_with_special_characters
- assert_dom_equal(
- " onclick=\"alert(&quot;&lt;code&gt;&quot;)\"",
+ assert_equal(
+ {:onclick => "alert(&quot;&lt;code&gt;&quot;)"},
option_html_attributes([ 'foo', 'bar', { :onclick => %(alert("<code>")) } ])
)
end
@@ -1025,6 +1189,24 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_grouped_collection_select_with_selected
+ @post = Post.new
+
+ assert_dom_equal(
+ %Q{<select id="post_origin" name="post[origin]"><optgroup label="&lt;Africa&gt;"><option value="&lt;sa&gt;">&lt;South Africa&gt;</option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>},
+ grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name, :selected => 'dk')
+ )
+ end
+
+ def test_grouped_collection_select_with_disabled_value
+ @post = Post.new
+
+ assert_dom_equal(
+ %Q{<select id="post_origin" name="post[origin]"><optgroup label="&lt;Africa&gt;"><option value="&lt;sa&gt;">&lt;South Africa&gt;</option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option disabled="disabled" value="dk">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>},
+ grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name, :disabled => 'dk')
+ )
+ end
+
def test_grouped_collection_select_under_fields_for
@post = Post.new
@post.origin = 'dk'
@@ -1041,14 +1223,14 @@ class FormOptionsHelperTest < ActionView::TestCase
private
- def dummy_posts
- [ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
- Post.new("Babe went home", "Babe", "To a little house", "shh!"),
- Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ]
- end
+ def dummy_posts
+ [ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ]
+ end
- def dummy_continents
- [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
- Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) ]
- end
+ def dummy_continents
+ [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")]),
+ Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")]) ]
+ end
end
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 6eae9bf846..6574e13558 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -2,6 +2,8 @@ require 'abstract_unit'
require 'active_support/core_ext/object/inclusion'
class FormTagHelperTest < ActionView::TestCase
+ include RenderERBUtils
+
tests ActionView::Helpers::FormTagHelper
def setup
@@ -76,6 +78,12 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
+ def test_form_tag_with_method_patch
+ actual = form_tag({}, { :method => :patch })
+ expected = whole_form("http://www.example.com", :method => :patch)
+ assert_dom_equal expected, actual
+ end
+
def test_form_tag_with_method_put
actual = form_tag({}, { :method => :put })
expected = whole_form("http://www.example.com", :method => :put)
@@ -104,14 +112,14 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_form_tag_with_block_in_erb
- output_buffer = form_tag("http://www.example.com") { concat "Hello world!" }
+ output_buffer = render_erb("<%= form_tag('http://www.example.com') do %>Hello world!<% end %>")
expected = whole_form { "Hello world!" }
assert_dom_equal expected, output_buffer
end
def test_form_tag_with_block_and_method_in_erb
- output_buffer = form_tag("http://www.example.com", :method => :put) { concat "Hello world!" }
+ output_buffer = render_erb("<%= form_tag('http://www.example.com', :method => :put) do %>Hello world!<% end %>")
expected = whole_form("http://www.example.com", :method => "put") do
"Hello world!"
@@ -214,19 +222,19 @@ class FormTagHelperTest < ActionView::TestCase
def test_text_area_tag_size_string
actual = text_area_tag "body", "hello world", "size" => "20x40"
- expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>)
+ expected = %(<textarea cols="20" id="body" name="body" rows="40">\nhello world</textarea>)
assert_dom_equal expected, actual
end
def test_text_area_tag_size_symbol
actual = text_area_tag "body", "hello world", :size => "20x40"
- expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>)
+ expected = %(<textarea cols="20" id="body" name="body" rows="40">\nhello world</textarea>)
assert_dom_equal expected, actual
end
def test_text_area_tag_should_disregard_size_if_its_given_as_an_integer
actual = text_area_tag "body", "hello world", :size => 20
- expected = %(<textarea id="body" name="body">hello world</textarea>)
+ expected = %(<textarea id="body" name="body">\nhello world</textarea>)
assert_dom_equal expected, actual
end
@@ -237,19 +245,19 @@ class FormTagHelperTest < ActionView::TestCase
def test_text_area_tag_escape_content
actual = text_area_tag "body", "<b>hello world</b>", :size => "20x40"
- expected = %(<textarea cols="20" id="body" name="body" rows="40">&lt;b&gt;hello world&lt;/b&gt;</textarea>)
+ expected = %(<textarea cols="20" id="body" name="body" rows="40">\n&lt;b&gt;hello world&lt;/b&gt;</textarea>)
assert_dom_equal expected, actual
end
def test_text_area_tag_unescaped_content
actual = text_area_tag "body", "<b>hello world</b>", :size => "20x40", :escape => false
- expected = %(<textarea cols="20" id="body" name="body" rows="40"><b>hello world</b></textarea>)
+ expected = %(<textarea cols="20" id="body" name="body" rows="40">\n<b>hello world</b></textarea>)
assert_dom_equal expected, actual
end
def test_text_area_tag_unescaped_nil_content
actual = text_area_tag "body", nil, :escape => false
- expected = %(<textarea id="body" name="body"></textarea>)
+ expected = %(<textarea id="body" name="body">\n</textarea>)
assert_dom_equal expected, actual
end
@@ -367,14 +375,7 @@ class FormTagHelperTest < ActionView::TestCase
def test_submit_tag
assert_dom_equal(
%(<input name='commit' data-disable-with="Saving..." onclick="alert('hello!')" type="submit" value="Save" />),
- submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')")
- )
- end
-
- def test_submit_tag_with_no_onclick_options
- assert_dom_equal(
- %(<input name='commit' data-disable-with="Saving..." type="submit" value="Save" />),
- submit_tag("Save", :disable_with => "Saving...")
+ submit_tag("Save", 'data-disable-with' => "Saving...", :onclick => "alert('hello!')")
)
end
@@ -385,13 +386,6 @@ class FormTagHelperTest < ActionView::TestCase
)
end
- def test_submit_tag_with_confirmation_and_with_disable_with
- assert_dom_equal(
- %(<input name="commit" data-disable-with="Saving..." data-confirm="Are you sure?" type="submit" value="Save" />),
- submit_tag("Save", :disable_with => "Saving...", :confirm => "Are you sure?")
- )
- end
-
def test_button_tag
assert_dom_equal(
%(<button name="button" type="submit">Button</button>),
@@ -455,11 +449,21 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal(expected, search_field_tag("query"))
end
- def telephone_field_tag
+ def test_telephone_field_tag
expected = %{<input id="cell" name="cell" type="tel" />}
assert_dom_equal(expected, telephone_field_tag("cell"))
end
+ def test_date_field_tag
+ expected = %{<input id="cell" name="cell" type="date" />}
+ assert_dom_equal(expected, date_field_tag("cell"))
+ end
+
+ def test_time_field_tag
+ expected = %{<input id="cell" name="cell" type="time" />}
+ assert_dom_equal(expected, time_field_tag("cell"))
+ end
+
def test_url_field_tag
expected = %{<input id="homepage" name="homepage" type="url" />}
assert_dom_equal(expected, url_field_tag("homepage"))
@@ -480,32 +484,38 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal(expected, range_field_tag("volume", nil, :in => 0..11, :step => 0.1))
end
- def test_pass
- assert_equal 1, 1
- end
-
def test_field_set_tag_in_erb
- output_buffer = field_set_tag("Your details") { concat "Hello world!" }
+ output_buffer = render_erb("<%= field_set_tag('Your details') do %>Hello world!<% end %>")
expected = %(<fieldset><legend>Your details</legend>Hello world!</fieldset>)
assert_dom_equal expected, output_buffer
- output_buffer = field_set_tag { concat "Hello world!" }
+ output_buffer = render_erb("<%= field_set_tag do %>Hello world!<% end %>")
expected = %(<fieldset>Hello world!</fieldset>)
assert_dom_equal expected, output_buffer
- output_buffer = field_set_tag('') { concat "Hello world!" }
+ output_buffer = render_erb("<%= field_set_tag('') do %>Hello world!<% end %>")
expected = %(<fieldset>Hello world!</fieldset>)
assert_dom_equal expected, output_buffer
- output_buffer = field_set_tag('', :class => 'format') { concat "Hello world!" }
+ output_buffer = render_erb("<%= field_set_tag('', :class => 'format') do %>Hello world!<% end %>")
expected = %(<fieldset class="format">Hello world!</fieldset>)
assert_dom_equal expected, output_buffer
+
+ output_buffer = render_erb("<%= field_set_tag %>")
+
+ expected = %(<fieldset></fieldset>)
+ assert_dom_equal expected, output_buffer
+
+ output_buffer = render_erb("<%= field_set_tag('You legend!') %>")
+
+ expected = %(<fieldset><legend>You legend!</legend></fieldset>)
+ assert_dom_equal expected, output_buffer
end
-
+
def test_text_area_tag_options_symbolize_keys_side_effects
options = { :option => "random_option" }
text_area_tag "body", "hello world", options
diff --git a/actionpack/test/template/html-scanner/cdata_node_test.rb b/actionpack/test/template/html-scanner/cdata_node_test.rb
index 1822cc565a..9b58174641 100644
--- a/actionpack/test/template/html-scanner/cdata_node_test.rb
+++ b/actionpack/test/template/html-scanner/cdata_node_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class CDATANodeTest < Test::Unit::TestCase
+class CDATANodeTest < ActiveSupport::TestCase
def setup
@node = HTML::CDATA.new(nil, 0, 0, "<p>howdy</p>")
end
diff --git a/actionpack/test/template/html-scanner/document_test.rb b/actionpack/test/template/html-scanner/document_test.rb
index 3db2fba783..17f045d549 100644
--- a/actionpack/test/template/html-scanner/document_test.rb
+++ b/actionpack/test/template/html-scanner/document_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class DocumentTest < Test::Unit::TestCase
+class DocumentTest < ActiveSupport::TestCase
def test_handle_doctype
doc = nil
assert_nothing_raised do
diff --git a/actionpack/test/template/html-scanner/node_test.rb b/actionpack/test/template/html-scanner/node_test.rb
index f4b9b198e8..5b5d092036 100644
--- a/actionpack/test/template/html-scanner/node_test.rb
+++ b/actionpack/test/template/html-scanner/node_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class NodeTest < Test::Unit::TestCase
+class NodeTest < ActiveSupport::TestCase
class MockNode
def initialize(matched, value)
diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb
index 62ad6be680..324caef224 100644
--- a/actionpack/test/template/html-scanner/sanitizer_test.rb
+++ b/actionpack/test/template/html-scanner/sanitizer_test.rb
@@ -56,7 +56,6 @@ class SanitizerTest < ActionController::TestCase
assert_sanitized "a b c<script language=\"Javascript\">blah blah blah</script>d e f", "a b cd e f"
end
- # TODO: Clean up
def test_sanitize_js_handlers
raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>}
assert_sanitized raw, %{onthis="do that" <a name="foo" href="#">hello</a>}
@@ -126,6 +125,24 @@ class SanitizerTest < ActionController::TestCase
assert_equal(text, sanitizer.sanitize(text, :attributes => ['foo']))
end
+ def test_should_raise_argument_error_if_tags_is_not_enumerable
+ sanitizer = HTML::WhiteListSanitizer.new
+ e = assert_raise(ArgumentError) do
+ sanitizer.sanitize('', :tags => 'foo')
+ end
+
+ assert_equal "You should pass :tags as an Enumerable", e.message
+ end
+
+ def test_should_raise_argument_error_if_attributes_is_not_enumerable
+ sanitizer = HTML::WhiteListSanitizer.new
+ e = assert_raise(ArgumentError) do
+ sanitizer.sanitize('', :attributes => 'foo')
+ end
+
+ assert_equal "You should pass :attributes as an Enumerable", e.message
+ end
+
[%w(img src), %w(a href)].each do |(tag, attr)|
define_method "test_should_strip_#{attr}_attribute_in_#{tag}_with_bad_protocols" do
assert_sanitized %(<#{tag} #{attr}="javascript:bang" title="1">boo</#{tag}>), %(<#{tag} title="1">boo</#{tag}>)
@@ -138,7 +155,7 @@ class SanitizerTest < ActionController::TestCase
assert sanitizer.send(:contains_bad_protocols?, 'src', "#{proto}://bad")
end
end
-
+
def test_should_accept_good_protocols_ignoring_case
sanitizer = HTML::WhiteListSanitizer.new
HTML::WhiteListSanitizer.allowed_protocols.each do |proto|
@@ -146,6 +163,13 @@ class SanitizerTest < ActionController::TestCase
end
end
+ def test_should_accept_good_protocols_ignoring_space
+ sanitizer = HTML::WhiteListSanitizer.new
+ HTML::WhiteListSanitizer.allowed_protocols.each do |proto|
+ assert !sanitizer.send(:contains_bad_protocols?, 'src', " #{proto}://good")
+ end
+ end
+
def test_should_accept_good_protocols
sanitizer = HTML::WhiteListSanitizer.new
HTML::WhiteListSanitizer.allowed_protocols.each do |proto|
@@ -208,7 +232,6 @@ class SanitizerTest < ActionController::TestCase
assert_sanitized img_hack, "<img>"
end
- # TODO: Clean up
def test_should_sanitize_attributes
assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="'&gt;&lt;script&gt;alert()&lt;/script&gt;">blah</span>)
end
diff --git a/actionpack/test/template/html-scanner/tag_node_test.rb b/actionpack/test/template/html-scanner/tag_node_test.rb
index 0d87f1bd42..a29d2d43d7 100644
--- a/actionpack/test/template/html-scanner/tag_node_test.rb
+++ b/actionpack/test/template/html-scanner/tag_node_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class TagNodeTest < Test::Unit::TestCase
+class TagNodeTest < ActiveSupport::TestCase
def test_open_without_attributes
node = tag("<tag>")
assert_equal "tag", node.name
@@ -55,7 +55,12 @@ class TagNodeTest < Test::Unit::TestCase
def test_to_s
node = tag("<a b=c d='f' g=\"h 'i'\" />")
- assert_equal %(<a b="c" d="f" g="h 'i'" />), node.to_s
+ node = node.to_s
+ assert node.include?('a')
+ assert node.include?('b="c"')
+ assert node.include?('d="f"')
+ assert node.include?('g="h')
+ assert node.include?('i')
end
def test_tag
diff --git a/actionpack/test/template/html-scanner/text_node_test.rb b/actionpack/test/template/html-scanner/text_node_test.rb
index 6f61253ffa..cbcb9e78f0 100644
--- a/actionpack/test/template/html-scanner/text_node_test.rb
+++ b/actionpack/test/template/html-scanner/text_node_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class TextNodeTest < Test::Unit::TestCase
+class TextNodeTest < ActiveSupport::TestCase
def setup
@node = HTML::Text.new(nil, 0, 0, "hello, howdy, aloha, annyeong")
end
diff --git a/actionpack/test/template/html-scanner/tokenizer_test.rb b/actionpack/test/template/html-scanner/tokenizer_test.rb
index bf45a7c2e3..1d59de23b6 100644
--- a/actionpack/test/template/html-scanner/tokenizer_test.rb
+++ b/actionpack/test/template/html-scanner/tokenizer_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class TokenizerTest < Test::Unit::TestCase
+class TokenizerTest < ActiveSupport::TestCase
def test_blank
tokenize ""
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index 4b9c3c97b1..fe7607ee26 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'active_support/core_ext/string/encoding'
class JavaScriptHelperTest < ActionView::TestCase
tests ActionView::Helpers::JavaScriptHelper
@@ -28,11 +27,9 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) )
assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags))
- if "ruby".encoding_aware?
- assert_equal %(unicode &#x2028; newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding('UTF-8').encode!)
- else
- assert_equal %(unicode &#x2028; newline), escape_javascript(%(unicode \342\200\250 newline))
- end
+ assert_equal %(unicode &#x2028; newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding('UTF-8').encode!)
+ assert_equal %(unicode &#x2029; newline), escape_javascript(%(unicode \342\200\251 newline).force_encoding('UTF-8').encode!)
+
assert_equal %(dont <\\/close> tags), j(%(dont </close> tags))
end
@@ -45,47 +42,17 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_instance_of ActiveSupport::SafeBuffer, escape_javascript(ActiveSupport::SafeBuffer.new(given))
end
- def test_button_to_function
- assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />),
- button_to_function("Greeting", "alert('Hello world!')")
- 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 :(')")
- end
-
- def test_button_to_function_without_function
- assert_dom_equal "<input onclick=\";\" type=\"button\" value=\"Greeting\" />",
- button_to_function("Greeting")
- end
-
- def test_link_to_function
- assert_dom_equal %(<a href="#" onclick="alert('Hello world!'); return false;">Greeting</a>),
- link_to_function("Greeting", "alert('Hello world!')")
- end
-
- def test_link_to_function_with_existing_onclick
- assert_dom_equal %(<a href="#" onclick="confirm('Sanity!'); alert('Hello world!'); return false;">Greeting</a>),
- link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')")
- end
-
- 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_javascript_tag
self.output_buffer = 'foo'
- assert_dom_equal "<script type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>",
+ assert_dom_equal "<script>\n//<![CDATA[\nalert('hello')\n//]]>\n</script>",
javascript_tag("alert('hello')")
assert_equal 'foo', output_buffer, 'javascript_tag without a block should not concat to output_buffer'
end
def test_javascript_tag_with_options
- assert_dom_equal "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>",
+ assert_dom_equal "<script id=\"the_js_tag\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>",
javascript_tag("alert('hello')", :id => "the_js_tag")
end
diff --git a/actionpack/test/template/log_subscriber_test.rb b/actionpack/test/template/log_subscriber_test.rb
index 752b0f23a8..1713ce935c 100644
--- a/actionpack/test/template/log_subscriber_test.rb
+++ b/actionpack/test/template/log_subscriber_test.rb
@@ -8,7 +8,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def setup
super
- @old_logger = ActionController::Base.logger
@controller = Object.new
@controller.stubs(:_prefixes).returns(%w(test))
@view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
@@ -19,11 +18,10 @@ class AVLogSubscriberTest < ActiveSupport::TestCase
def teardown
super
ActiveSupport::LogSubscriber.log_subscribers.clear
- ActionController::Base.logger = @old_logger
end
def set_logger(logger)
- ActionController::Base.logger = logger
+ ActionView::Base.logger = logger
end
def test_render_file_template
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index bac2530e3d..96b14a0acd 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -1,20 +1,14 @@
require "abstract_unit"
require "abstract_controller/rendering"
-ActionView::LookupContext::DetailsKey.class_eval do
- def self.details_keys
- @details_keys
- end
-end
-
class LookupContextTest < ActiveSupport::TestCase
def setup
@lookup_context = ActionView::LookupContext.new(FIXTURE_LOAD_PATH, {})
+ ActionView::LookupContext::DetailsKey.clear
end
def teardown
I18n.locale = :en
- ActionView::LookupContext::DetailsKey.details_keys.clear
end
test "process view paths on initialization" do
@@ -84,9 +78,9 @@ class LookupContextTest < ActiveSupport::TestCase
end
test "found templates respects given formats if one cannot be found from template or handler" do
- ActionView::Template::Handlers::ERB.expects(:default_format).returns(nil)
+ ActionView::Template::Handlers::Builder.expects(:default_format).returns(nil)
@lookup_context.formats = [:text]
- template = @lookup_context.find("hello_world", %w(test))
+ template = @lookup_context.find("hello", %w(test))
assert_equal [:text], template.formats
end
diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb
index 5df09b4d3b..d6e9de9555 100644
--- a/actionpack/test/template/number_helper_i18n_test.rb
+++ b/actionpack/test/template/number_helper_i18n_test.rb
@@ -53,6 +53,13 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("-$10.00", number_to_currency(-10))
end
end
+
+ def test_number_to_currency_without_currency_negative_format
+ clean_i18n do
+ I18n.backend.store_translations 'ts', :number => { :currency => { :format => { :unit => '@', :format => '%n %u' } } }
+ assert_equal("-10.00 @", number_to_currency(-10, :locale => 'ts'))
+ end
+ end
def test_number_with_i18n_precision
#Delimiter was set to ""
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 8d679aac1d..14ca6d9879 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -57,6 +57,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("1000.000%", number_to_percentage("1000"))
assert_equal("123.4%", number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true))
assert_equal("1.000,000%", number_to_percentage(1000, :delimiter => '.', :separator => ','))
+ assert_equal("1000.000 %", number_to_percentage(1000, :format => "%n %"))
end
def test_number_with_delimiter
@@ -77,6 +78,8 @@ class NumberHelperTest < ActionView::TestCase
assert_equal '12,345,678-05', number_with_delimiter(12345678.05, :separator => '-')
assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :separator => ',', :delimiter => '.')
assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :delimiter => '.', :separator => ',')
+ assert_equal '1&lt;script&gt;&lt;/script&gt;01', number_with_delimiter(1.01, :separator => "<script></script>")
+ assert_equal '1&lt;script&gt;&lt;/script&gt;000', number_with_delimiter(1000, :delimiter => "<script></script>")
end
def test_number_with_precision
@@ -95,6 +98,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("0.001", number_with_precision(0.00111, :precision => 3))
assert_equal("10.00", number_with_precision(9.995, :precision => 2))
assert_equal("11.00", number_with_precision(10.995, :precision => 2))
+ assert_equal("0.00", number_with_precision(-0.001, :precision => 2))
end
def test_number_with_precision_with_custom_delimiter_and_separator
@@ -267,6 +271,31 @@ class NumberHelperTest < ActionView::TestCase
assert_nil number_to_human(nil)
end
+ def test_number_helpers_do_not_mutate_options_hash
+ options = { 'raise' => true }
+
+ number_to_phone(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_to_currency(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_to_percentage(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_with_delimiter(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_with_precision(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_to_human_size(1, options)
+ assert_equal({ 'raise' => true }, options)
+
+ number_to_human(1, options)
+ assert_equal({ 'raise' => true }, options)
+ end
+
def test_number_helpers_should_return_non_numeric_param_unchanged
assert_equal("+1-x x 123", number_to_phone("x", :country_code => 1, :extension => 123))
assert_equal("x", number_to_phone("x"))
diff --git a/actionpack/test/template/output_buffer_test.rb b/actionpack/test/template/output_buffer_test.rb
index bd49a11af1..eb0df3d1ab 100644
--- a/actionpack/test/template/output_buffer_test.rb
+++ b/actionpack/test/template/output_buffer_test.rb
@@ -39,15 +39,13 @@ class OutputBufferTest < ActionController::TestCase
assert_equal ['foo', 'bar'], body_parts
end
- if '1.9'.respond_to?(:force_encoding)
- test 'flushing preserves output buffer encoding' do
- original_buffer = ' '.force_encoding(Encoding::EUC_JP)
- @vc.output_buffer = original_buffer
- @vc.flush_output_buffer
- assert_equal ['foo', original_buffer], body_parts
- assert_not_equal original_buffer, output_buffer
- assert_equal Encoding::EUC_JP, output_buffer.encoding
- end
+ test 'flushing preserves output buffer encoding' do
+ original_buffer = ' '.force_encoding(Encoding::EUC_JP)
+ @vc.output_buffer = original_buffer
+ @vc.flush_output_buffer
+ assert_equal ['foo', original_buffer], body_parts
+ assert_not_equal original_buffer, output_buffer
+ assert_equal Encoding::EUC_JP, output_buffer.encoding
end
protected
diff --git a/actionpack/test/template/output_safety_helper_test.rb b/actionpack/test/template/output_safety_helper_test.rb
index fc127c24e9..76c71c9e6d 100644
--- a/actionpack/test/template/output_safety_helper_test.rb
+++ b/actionpack/test/template/output_safety_helper_test.rb
@@ -1,9 +1,7 @@
require 'abstract_unit'
-require 'testing_sandbox'
class OutputSafetyHelperTest < ActionView::TestCase
tests ActionView::Helpers::OutputSafetyHelper
- include TestingSandbox
def setup
@string = "hello"
diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb
index 7f23629e05..a84034c02e 100644
--- a/actionpack/test/template/record_tag_helper_test.rb
+++ b/actionpack/test/template/record_tag_helper_test.rb
@@ -1,98 +1,103 @@
require 'abstract_unit'
-require 'controller/fake_models'
-class Post
+class RecordTagPost
extend ActiveModel::Naming
include ActiveModel::Conversion
- attr_writer :id, :body
+ attr_accessor :id, :body
def initialize
- @id = nil
- @body = nil
- super
- end
+ @id = 45
+ @body = "What a wonderful world!"
- def id
- @id || 45
- end
-
- def body
- super || @body || "What a wonderful world!"
+ yield self if block_given?
end
end
class RecordTagHelperTest < ActionView::TestCase
+ include RenderERBUtils
+
tests ActionView::Helpers::RecordTagHelper
def setup
super
- @post = Post.new
- @post.persisted = true
+ @post = RecordTagPost.new
end
def test_content_tag_for
- expected = %(<li class="post bar" id="post_45"></li>)
- actual = content_tag_for(:li, @post, :class => 'bar') { }
+ expected = %(<li class="record_tag_post" id="record_tag_post_45"></li>)
+ actual = content_tag_for(:li, @post) { }
assert_dom_equal expected, actual
end
def test_content_tag_for_prefix
- expected = %(<ul class="archived_post" id="archived_post_45"></ul>)
+ expected = %(<ul class="archived_record_tag_post" id="archived_record_tag_post_45"></ul>)
actual = content_tag_for(:ul, @post, :archived) { }
assert_dom_equal expected, actual
end
- def test_content_tag_for_with_extra_html_tags
- expected = %(<tr class="post bar" id="post_45" style='background-color: #f0f0f0'></tr>)
- actual = content_tag_for(:tr, @post, {:class => "bar", :style => "background-color: #f0f0f0"}) { }
+ def test_content_tag_for_with_extra_html_options
+ expected = %(<tr class="record_tag_post special" id="record_tag_post_45" style='background-color: #f0f0f0'></tr>)
+ actual = content_tag_for(:tr, @post, :class => "special", :style => "background-color: #f0f0f0") { }
+ assert_dom_equal expected, actual
+ end
+
+ def test_content_tag_for_with_prefix_and_extra_html_options
+ expected = %(<tr class="archived_record_tag_post special" id="archived_record_tag_post_45" style='background-color: #f0f0f0'></tr>)
+ actual = content_tag_for(:tr, @post, :archived, :class => "special", :style => "background-color: #f0f0f0") { }
assert_dom_equal expected, actual
end
def test_block_not_in_erb_multiple_calls
- expected = %(<div class="post bar" id="post_45">#{@post.body}</div>)
- actual = div_for(@post, :class => "bar") { @post.body }
+ expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>)
+ actual = div_for(@post, :class => "special") { @post.body }
assert_dom_equal expected, actual
- actual = div_for(@post, :class => "bar") { @post.body }
+ actual = div_for(@post, :class => "special") { @post.body }
assert_dom_equal expected, actual
end
def test_block_works_with_content_tag_for_in_erb
- expected = %(<tr class="post" id="post_45">#{@post.body}</tr>)
- actual = content_tag_for(:tr, @post) { concat @post.body }
+ expected = %(<tr class="record_tag_post" id="record_tag_post_45">What a wonderful world!</tr>)
+ actual = render_erb("<%= content_tag_for(:tr, @post) do %><%= @post.body %><% end %>")
assert_dom_equal expected, actual
end
def test_div_for_in_erb
- expected = %(<div class="post bar" id="post_45">#{@post.body}</div>)
- actual = div_for(@post, :class => "bar") { concat @post.body }
+ expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>)
+ actual = render_erb("<%= div_for(@post, :class => 'special') do %><%= @post.body %><% end %>")
assert_dom_equal expected, actual
end
def test_content_tag_for_collection
- post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
- post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
- expected = %(<li class="post" id="post_101">Hello!</li>\n<li class="post" id="post_102">World!</li>)
- actual = content_tag_for(:li, [post_1, post_2]) { |post| concat post.body }
+ post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
+ post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
+ expected = %(<li class="record_tag_post" id="record_tag_post_101">Hello!</li>\n<li class="record_tag_post" id="record_tag_post_102">World!</li>)
+ actual = content_tag_for(:li, [post_1, post_2]) { |post| post.body }
assert_dom_equal expected, actual
end
def test_div_for_collection
- post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
- post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
- expected = %(<div class="post" id="post_101">Hello!</div>\n<div class="post" id="post_102">World!</div>)
- actual = div_for([post_1, post_2]) { |post| concat post.body }
+ post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
+ post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
+ expected = %(<div class="record_tag_post" id="record_tag_post_101">Hello!</div>\n<div class="record_tag_post" id="record_tag_post_102">World!</div>)
+ actual = div_for([post_1, post_2]) { |post| post.body }
assert_dom_equal expected, actual
end
def test_content_tag_for_single_record_is_html_safe
- result = div_for(@post, :class => "bar") { concat @post.body }
+ result = div_for(@post, :class => "special") { @post.body }
assert result.html_safe?
end
def test_content_tag_for_collection_is_html_safe
- post_1 = Post.new.tap { |post| post.id = 101; post.body = "Hello!"; post.persisted = true }
- post_2 = Post.new.tap { |post| post.id = 102; post.body = "World!"; post.persisted = true }
- result = content_tag_for(:li, [post_1, post_2]) { |post| concat post.body }
+ post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" }
+ post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" }
+ result = content_tag_for(:li, [post_1, post_2]) { |post| post.body }
assert result.html_safe?
end
+
+ def test_content_tag_for_does_not_change_options_hash
+ options = { :class => "important" }
+ content_tag_for(:li, @post, options) { }
+ assert_equal({ :class => "important" }, options)
+ end
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 0ef3239f83..88ed8664c2 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -20,6 +20,11 @@ module RenderTestCases
assert_equal ORIGINAL_LOCALES, I18n.available_locales.map {|l| l.to_s }.sort
end
+ def test_render_without_options
+ e = assert_raises(ArgumentError) { @view.render() }
+ assert_match "You invoked render but did not give any of :partial, :template, :inline, :file or :text option.", e.message
+ end
+
def test_render_file
assert_equal "Hello world!", @view.render(:file => "test/hello_world")
end
@@ -43,25 +48,45 @@ module RenderTestCases
assert_match "<h1>No Comment</h1>", @view.render(:template => "comments/empty", :formats => [:html])
assert_match "<error>No Comment</error>", @view.render(:template => "comments/empty", :formats => [:xml])
end
-
+
+ def test_render_partial_implicitly_use_format_of_the_rendered_template
+ @view.lookup_context.formats = [:json]
+ assert_equal "Hello world", @view.render(:template => "test/one", :formats => [:html])
+ end
+
+ def test_render_template_with_a_missing_partial_of_another_format
+ @view.lookup_context.formats = [:html]
+ assert_raise ActionView::Template::Error, "Missing partial /missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do
+ @view.render(:template => "with_format", :formats => [:json])
+ end
+ end
+
def test_render_file_with_locale
assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => [:de])
assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => :de)
end
-
+
def test_render_template_with_locale
assert_equal "<h1>Kein Kommentar</h1>", @view.render(:template => "comments/empty", :locale => [:de])
end
-
+
def test_render_file_with_handlers
assert_equal "<h1>No Comment</h1>\n", @view.render(:file => "comments/empty", :handlers => [:builder])
assert_equal "<h1>No Comment</h1>\n", @view.render(:file => "comments/empty", :handlers => :builder)
end
-
+
def test_render_template_with_handlers
assert_equal "<h1>No Comment</h1>\n", @view.render(:template => "comments/empty", :handlers => [:builder])
end
+ def test_render_raw_template_with_handlers
+ assert_equal "<%= hello_world %>\n", @view.render(:template => "plain_text")
+ end
+
+ def test_render_raw_template_with_quotes
+ assert_equal %q;Here are some characters: !@#$%^&*()-="'}{`; + "\n", @view.render(:template => "plain_text_with_characters")
+ end
+
def test_render_file_with_localization_on_context_level
old_locale, @view.locale = @view.locale, :da
assert_equal "Hey verden", @view.render(:file => "test/hello_world")
@@ -134,18 +159,26 @@ module RenderTestCases
end
def test_render_partial_with_invalid_name
- @view.render(:partial => "test/200")
- flunk "Render did not raise ArgumentError"
- rescue ArgumentError => e
+ e = assert_raises(ArgumentError) { @view.render(:partial => "test/200") }
assert_equal "The partial name (test/200) is not a valid Ruby identifier; " +
- "make sure your partial name starts with a letter or underscore, " +
- "and is followed by any combinations of letters, numbers, or underscores.", e.message
+ "make sure your partial name starts with a lowercase letter or underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
+ end
+
+ def test_render_partial_with_missing_filename
+ e = assert_raises(ArgumentError) { @view.render(:partial => "test/") }
+ assert_equal "The partial name (test/) is not a valid Ruby identifier; " +
+ "make sure your partial name starts with a lowercase letter or underscore, " +
+ "and is followed by any combination of letters, numbers and underscores.", e.message
+ end
+
+ def test_render_partial_with_incompatible_object
+ e = assert_raises(ArgumentError) { @view.render(:partial => nil) }
+ assert_equal "'#{nil.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.", e.message
end
def test_render_partial_with_errors
- @view.render(:partial => "test/raise")
- flunk "Render did not raise Template::Error"
- rescue ActionView::Template::Error => e
+ e = assert_raises(ActionView::Template::Error) { @view.render(:partial => "test/raise") }
assert_match %r!method.*doesnt_exist!, e.message
assert_equal "", e.sub_template_message
assert_equal "1", e.line_number
@@ -154,9 +187,7 @@ module RenderTestCases
end
def test_render_sub_template_with_errors
- @view.render(:template => "test/sub_template_raise")
- flunk "Render did not raise Template::Error"
- rescue ActionView::Template::Error => e
+ e = assert_raises(ActionView::Template::Error) { @view.render(:template => "test/sub_template_raise") }
assert_match %r!method.*doesnt_exist!, e.message
assert_equal "Trace of template inclusion: #{File.expand_path("#{FIXTURE_LOAD_PATH}/test/sub_template_raise.html.erb")}", e.sub_template_message
assert_equal "1", e.line_number
@@ -164,9 +195,7 @@ module RenderTestCases
end
def test_render_file_with_errors
- @view.render(:file => File.expand_path("test/_raise", FIXTURE_LOAD_PATH))
- flunk "Render did not raise Template::Error"
- rescue ActionView::Template::Error => e
+ e = assert_raises(ActionView::Template::Error) { @view.render(:file => File.expand_path("test/_raise", FIXTURE_LOAD_PATH)) }
assert_match %r!method.*doesnt_exist!, e.message
assert_equal "", e.sub_template_message
assert_equal "1", e.line_number
@@ -213,6 +242,25 @@ module RenderTestCases
assert_equal "Hello: davidHello: Anonymous", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), nil ])
end
+ def test_render_partial_with_layout_using_collection_and_template
+ assert_equal "<b>Hello: Amazon</b><b>Hello: Yahoo</b>", @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ])
+ end
+
+ def test_render_partial_with_layout_using_collection_and_template_makes_current_item_available_in_layout
+ assert_equal '<b class="amazon">Hello: Amazon</b><b class="yahoo">Hello: Yahoo</b>',
+ @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ])
+ end
+
+ def test_render_partial_with_layout_using_collection_and_template_makes_current_item_counter_available_in_layout
+ assert_equal '<b data-counter="0">Hello: Amazon</b><b data-counter="1">Hello: Yahoo</b>',
+ @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object_counter', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ])
+ end
+
+ def test_render_partial_with_layout_using_object_and_template_makes_object_available_in_layout
+ assert_equal '<b class="amazon">Hello: Amazon</b>',
+ @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial_with_object', :object => Customer.new("Amazon"))
+ end
+
def test_render_partial_with_empty_array_should_return_nil
assert_nil @view.render(:partial => [])
end
@@ -236,36 +284,6 @@ module RenderTestCases
@controller_view.render(customers, :greeting => "Hello")
end
- class CustomerWithDeprecatedPartialPath
- attr_reader :name
-
- def self.model_name
- Struct.new(:partial_path).new("customers/customer")
- end
-
- def initialize(name)
- @name = name
- end
- end
-
- def test_render_partial_using_object_with_deprecated_partial_path
- assert_deprecated(/#model_name.*#partial_path.*#to_partial_path/) do
- assert_equal "Hello: nertzy",
- @controller_view.render(CustomerWithDeprecatedPartialPath.new("nertzy"), :greeting => "Hello")
- end
- end
-
- def test_render_partial_using_collection_with_deprecated_partial_path
- assert_deprecated(/#model_name.*#partial_path.*#to_partial_path/) do
- customers = [
- CustomerWithDeprecatedPartialPath.new("nertzy"),
- CustomerWithDeprecatedPartialPath.new("peeja")
- ]
- assert_equal "Hello: nertzyHello: peeja",
- @controller_view.render(customers, :greeting => "Hello")
- end
- end
-
# TODO: The reason for this test is unclear, improve documentation
def test_render_partial_and_fallback_to_layout
assert_equal "Before (Josh)\n\nAfter", @view.render(:partial => "test/layout_for_partial", :locals => { :name => "Josh" })
@@ -274,7 +292,7 @@ module RenderTestCases
# TODO: The reason for this test is unclear, improve documentation
def test_render_missing_xml_partial_and_raise_missing_template
@view.formats = [:xml]
- assert_raise(ActionView::MissingTemplate) { @view.render(:partial => "test/layout_for_partial") }
+ assert_raises(ActionView::MissingTemplate) { @view.render(:partial => "test/layout_for_partial") }
ensure
@view.formats = nil
end
@@ -316,10 +334,16 @@ module RenderTestCases
assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
end
+ def test_render_knows_about_types_registered_when_extensions_are_checked_earlier_in_initialization
+ ActionView::Template::Handlers.extensions
+ ActionView::Template.register_template_handler :foo, CustomHandler
+ assert ActionView::Template::Handlers.extensions.include?(:foo)
+ end
+
def test_render_ignores_templates_with_malformed_template_handlers
ActiveSupport::Deprecation.silence do
%w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name|
- assert_raise(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") }
+ assert_raises(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") }
end
end
end
@@ -426,51 +450,41 @@ class LazyViewRenderTest < ActiveSupport::TestCase
GC.start
end
- if '1.9'.respond_to?(:force_encoding)
- def test_render_utf8_template_with_magic_comment
- with_external_encoding Encoding::ASCII_8BIT do
- result = @view.render(:file => "test/utf8_magic.html", :layouts => "layouts/yield")
- assert_equal Encoding::UTF_8, result.encoding
- assert_equal "\nРусский \nтекст\n\nUTF-8\nUTF-8\nUTF-8\n", result
- end
+ def test_render_utf8_template_with_magic_comment
+ with_external_encoding Encoding::ASCII_8BIT do
+ result = @view.render(:file => "test/utf8_magic", :formats => [:html], :layouts => "layouts/yield")
+ assert_equal Encoding::UTF_8, result.encoding
+ assert_equal "\nРусский \nтекст\n\nUTF-8\nUTF-8\nUTF-8\n", result
end
+ end
- def test_render_utf8_template_with_default_external_encoding
- with_external_encoding Encoding::UTF_8 do
- result = @view.render(:file => "test/utf8.html", :layouts => "layouts/yield")
- assert_equal Encoding::UTF_8, result.encoding
- assert_equal "Русский текст\n\nUTF-8\nUTF-8\nUTF-8\n", result
- end
+ def test_render_utf8_template_with_default_external_encoding
+ with_external_encoding Encoding::UTF_8 do
+ result = @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield")
+ assert_equal Encoding::UTF_8, result.encoding
+ assert_equal "Русский текст\n\nUTF-8\nUTF-8\nUTF-8\n", result
end
+ end
- def test_render_utf8_template_with_incompatible_external_encoding
- with_external_encoding Encoding::SHIFT_JIS do
- begin
- @view.render(:file => "test/utf8.html", :layouts => "layouts/yield")
- flunk 'Should have raised incompatible encoding error'
- rescue ActionView::Template::Error => error
- assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message
- end
- end
+ def test_render_utf8_template_with_incompatible_external_encoding
+ with_external_encoding Encoding::SHIFT_JIS do
+ e = assert_raises(ActionView::Template::Error) { @view.render(:file => "test/utf8", :formats => [:html], :layouts => "layouts/yield") }
+ assert_match 'Your template was not saved as valid Shift_JIS', e.original_exception.message
end
+ end
- def test_render_utf8_template_with_partial_with_incompatible_encoding
- with_external_encoding Encoding::SHIFT_JIS do
- begin
- @view.render(:file => "test/utf8_magic_with_bare_partial.html", :layouts => "layouts/yield")
- flunk 'Should have raised incompatible encoding error'
- rescue ActionView::Template::Error => error
- assert_match 'Your template was not saved as valid Shift_JIS', error.original_exception.message
- end
- end
+ def test_render_utf8_template_with_partial_with_incompatible_encoding
+ with_external_encoding Encoding::SHIFT_JIS do
+ e = assert_raises(ActionView::Template::Error) { @view.render(:file => "test/utf8_magic_with_bare_partial", :formats => [:html], :layouts => "layouts/yield") }
+ assert_match 'Your template was not saved as valid Shift_JIS', e.original_exception.message
end
+ end
- def with_external_encoding(encoding)
- old = Encoding.default_external
- silence_warnings { Encoding.default_external = encoding }
- yield
- ensure
- silence_warnings { Encoding.default_external = old }
- end
+ def with_external_encoding(encoding)
+ old = Encoding.default_external
+ silence_warnings { Encoding.default_external = encoding }
+ yield
+ ensure
+ silence_warnings { Encoding.default_external = old }
end
end
diff --git a/actionpack/test/template/sanitize_helper_test.rb b/actionpack/test/template/sanitize_helper_test.rb
index 222d4dbf4c..4182af590e 100644
--- a/actionpack/test/template/sanitize_helper_test.rb
+++ b/actionpack/test/template/sanitize_helper_test.rb
@@ -1,11 +1,9 @@
require 'abstract_unit'
-require 'testing_sandbox'
# The exhaustive tests are in test/controller/html/sanitizer_test.rb.
# This tests the that the helpers hook up correctly to the sanitizer classes.
class SanitizeHelperTest < ActionView::TestCase
tests ActionView::Helpers::SanitizeHelper
- include TestingSandbox
def test_strip_links
assert_equal "Dont touch me", strip_links("Dont touch me")
diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb
deleted file mode 100644
index db69f95130..0000000000
--- a/actionpack/test/template/sprockets_helper_test.rb
+++ /dev/null
@@ -1,311 +0,0 @@
-require 'abstract_unit'
-require 'sprockets'
-require 'sprockets/helpers/rails_helper'
-require 'mocha'
-
-class SprocketsHelperTest < ActionView::TestCase
- include Sprockets::Helpers::RailsHelper
-
- attr_accessor :assets
-
- class MockRequest
- def protocol() 'http://' end
- def ssl?() false end
- def host_with_port() 'localhost' end
- end
-
- def setup
- super
-
- @controller = BasicController.new
- @controller.request = MockRequest.new
-
- @assets = Sprockets::Environment.new
- @assets.append_path(FIXTURES.join("sprockets/app/javascripts"))
- @assets.append_path(FIXTURES.join("sprockets/app/stylesheets"))
- @assets.append_path(FIXTURES.join("sprockets/app/images"))
-
- application = Struct.new(:config, :assets).new(config, @assets)
- Rails.stubs(:application).returns(application)
- @config = config
- @config.perform_caching = true
- @config.assets.digest = true
- @config.assets.compile = true
- end
-
- def url_for(*args)
- "http://www.example.com"
- end
-
- def config
- @controller ? @controller.config : @config
- end
-
- test "asset_path" do
- assert_match %r{/assets/logo-[0-9a-f]+.png},
- asset_path("logo.png")
- assert_match %r{/assets/logo-[0-9a-f]+.png},
- asset_path("logo.png", :digest => true)
- assert_match %r{/assets/logo.png},
- asset_path("logo.png", :digest => false)
- end
-
- test "custom_asset_path" do
- @config.assets.prefix = '/s'
- assert_match %r{/s/logo-[0-9a-f]+.png},
- asset_path("logo.png")
- assert_match %r{/s/logo-[0-9a-f]+.png},
- asset_path("logo.png", :digest => true)
- assert_match %r{/s/logo.png},
- asset_path("logo.png", :digest => false)
- end
-
- test "asset_path with root relative assets" do
- assert_equal "/images/logo",
- asset_path("/images/logo")
- assert_equal "/images/logo.gif",
- asset_path("/images/logo.gif")
-
- assert_equal "/dir/audio",
- asset_path("/dir/audio")
- end
-
- test "asset_path with absolute urls" do
- assert_equal "http://www.example.com/video/play",
- asset_path("http://www.example.com/video/play")
- assert_equal "http://www.example.com/video/play.mp4",
- asset_path("http://www.example.com/video/play.mp4")
- end
-
- test "with a simple asset host the url should default to protocol relative" do
- @controller.config.default_asset_host_protocol = :relative
- @controller.config.asset_host = "assets-%d.example.com"
- assert_match %r{^//assets-\d.example.com/assets/logo-[0-9a-f]+.png},
- asset_path("logo.png")
- end
-
- test "with a simple asset host the url can be changed to use the request protocol" do
- @controller.config.asset_host = "assets-%d.example.com"
- @controller.config.default_asset_host_protocol = :request
- assert_match %r{http://assets-\d.example.com/assets/logo-[0-9a-f]+.png},
- asset_path("logo.png")
- end
-
- test "With a proc asset host that returns no protocol the url should be protocol relative" do
- @controller.config.default_asset_host_protocol = :relative
- @controller.config.asset_host = Proc.new do |asset|
- "assets-999.example.com"
- end
- assert_match %r{^//assets-999.example.com/assets/logo-[0-9a-f]+.png},
- asset_path("logo.png")
- end
-
- test "with a proc asset host that returns a protocol the url use it" do
- @controller.config.asset_host = Proc.new do |asset|
- "http://assets-999.example.com"
- end
- assert_match %r{http://assets-999.example.com/assets/logo-[0-9a-f]+.png},
- asset_path("logo.png")
- end
-
- test "stylesheets served with a controller in scope can access the request" do
- config.asset_host = Proc.new do |asset, request|
- assert_not_nil request
- "http://assets-666.example.com"
- end
- assert_match %r{http://assets-666.example.com/assets/logo-[0-9a-f]+.png},
- asset_path("logo.png")
- end
-
- test "stylesheets served without a controller in scope cannot access the request" do
- @controller = nil
- @config.asset_host = Proc.new do |asset, request|
- fail "This should not have been called."
- end
- assert_raises ActionController::RoutingError do
- asset_path("logo.png")
- end
- end
-
- test "image_tag" do
- assert_dom_equal '<img alt="Xml" src="/assets/xml.png" />', image_tag("xml.png")
- end
-
- test "image_path" do
- assert_match %r{/assets/logo-[0-9a-f]+.png},
- image_path("logo.png")
-
- assert_match %r{/assets/logo-[0-9a-f]+.png},
- path_to_image("logo.png")
- end
-
- test "javascript_path" do
- assert_match %r{/assets/application-[0-9a-f]+.js},
- javascript_path("application.js")
-
- assert_match %r{/assets/application-[0-9a-f]+.js},
- path_to_javascript("application.js")
- end
-
- test "stylesheet_path" do
- assert_match %r{/assets/application-[0-9a-f]+.css},
- stylesheet_path("application.css")
-
- assert_match %r{/assets/application-[0-9a-f]+.css},
- path_to_stylesheet("application.css")
- end
-
- test "stylesheets served without a controller in do not use asset hosts when the default protocol is :request" do
- @controller = nil
- @config.asset_host = "assets-%d.example.com"
- @config.default_asset_host_protocol = :request
- @config.perform_caching = true
-
- assert_match %r{/assets/logo-[0-9a-f]+.png},
- asset_path("logo.png")
- end
-
- test "asset path with relative url root" do
- @controller.config.relative_url_root = "/collaboration/hieraki"
- assert_equal "/collaboration/hieraki/images/logo.gif",
- asset_path("/images/logo.gif")
- end
-
- test "asset path with relative url root when controller isn't present but relative_url_root is" do
- @controller = nil
- @config.relative_url_root = "/collaboration/hieraki"
- assert_equal "/collaboration/hieraki/images/logo.gif",
- asset_path("/images/logo.gif")
- end
-
- test "javascript path through asset_path" do
- assert_match %r{/assets/application-[0-9a-f]+.js},
- asset_path(:application, :ext => "js")
-
- assert_match %r{/assets/xmlhr-[0-9a-f]+.js},
- asset_path("xmlhr", :ext => "js")
- assert_match %r{/assets/dir/xmlhr-[0-9a-f]+.js},
- asset_path("dir/xmlhr.js", :ext => "js")
-
- assert_equal "/dir/xmlhr.js",
- asset_path("/dir/xmlhr", :ext => "js")
-
- assert_equal "http://www.example.com/js/xmlhr",
- asset_path("http://www.example.com/js/xmlhr", :ext => "js")
- assert_equal "http://www.example.com/js/xmlhr.js",
- asset_path("http://www.example.com/js/xmlhr.js", :ext => "js")
- end
-
- test "javascript include tag" do
- assert_match %r{<script src="/assets/application-[0-9a-f]+.js" type="text/javascript"></script>},
- javascript_include_tag(:application)
- assert_match %r{<script src="/assets/application-[0-9a-f]+.js" type="text/javascript"></script>},
- javascript_include_tag(:application, :digest => true)
- assert_match %r{<script src="/assets/application.js" type="text/javascript"></script>},
- javascript_include_tag(:application, :digest => false)
-
- assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js" type="text/javascript"></script>},
- javascript_include_tag("xmlhr")
- assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js" type="text/javascript"></script>},
- javascript_include_tag("xmlhr.js")
- assert_equal '<script src="http://www.example.com/xmlhr" type="text/javascript"></script>',
- javascript_include_tag("http://www.example.com/xmlhr")
-
- assert_match %r{<script src=\"/assets/xmlhr-[0-9a-f]+.js" type=\"text/javascript\"></script>\n<script src=\"/assets/extra-[0-9a-f]+.js" type=\"text/javascript\"></script>},
- javascript_include_tag("xmlhr", "extra")
-
- assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/application-[0-9a-f]+.js\?body=1" type="text/javascript"></script>},
- javascript_include_tag(:application, :debug => true)
-
- @config.assets.compile = true
- @config.assets.debug = true
- assert_match %r{<script src="/javascripts/application.js" type="text/javascript"></script>},
- javascript_include_tag('/javascripts/application')
- assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/application-[0-9a-f]+.js\?body=1" type="text/javascript"></script>},
- javascript_include_tag(:application)
- end
-
- test "stylesheet path through asset_path" do
- assert_match %r{/assets/application-[0-9a-f]+.css}, asset_path(:application, :ext => "css")
-
- assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", :ext => "css")
- assert_match %r{/assets/dir/style-[0-9a-f]+.css}, asset_path("dir/style.css", :ext => "css")
- assert_equal "/dir/style.css", asset_path("/dir/style.css", :ext => "css")
-
- assert_equal "http://www.example.com/css/style",
- asset_path("http://www.example.com/css/style", :ext => "css")
- assert_equal "http://www.example.com/css/style.css",
- asset_path("http://www.example.com/css/style.css", :ext => "css")
- end
-
- test "stylesheet link tag" do
- assert_match %r{<link href="/assets/application-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag(:application)
- assert_match %r{<link href="/assets/application-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag(:application, :digest => true)
- assert_match %r{<link href="/assets/application.css" media="screen" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag(:application, :digest => false)
-
- assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag("style")
- assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag("style.css")
-
- assert_equal '<link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />',
- stylesheet_link_tag("http://www.example.com/style.css")
- assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="all" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag("style", :media => "all")
- assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="print" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag("style", :media => "print")
-
- assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/extra-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag("style", "extra")
-
- assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag(:application, :debug => true)
-
- @config.assets.compile = true
- @config.assets.debug = true
- assert_match %r{<link href="/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag('/stylesheets/application')
-
- assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag(:application)
-
- assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="print" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="print" rel="stylesheet" type="text/css" />},
- stylesheet_link_tag(:application, :media => "print")
- end
-
- test "alternate asset prefix" do
- stubs(:asset_prefix).returns("/themes/test")
- assert_match %r{/themes/test/style-[0-9a-f]+.css}, asset_path("style", :ext => "css")
- end
-
- test "alternate asset environment" do
- assets = Sprockets::Environment.new
- assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets"))
- stubs(:asset_environment).returns(assets)
- assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", :ext => "css")
- end
-
- test "alternate hash based on environment" do
- assets = Sprockets::Environment.new
- assets.version = 'development'
- assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets"))
- stubs(:asset_environment).returns(assets)
- dev_path = asset_path("style", :ext => "css")
-
- assets.version = 'production'
- prod_path = asset_path("style", :ext => "css")
-
- assert_not_equal prod_path, dev_path
- end
-
- test "precedence of `config.digest = false` over manifest.yml asset digests" do
- Rails.application.config.assets.digests = {'logo.png' => 'logo-d1g3st.png'}
- @config.assets.digest = false
-
- assert_equal '/assets/logo.png',
- asset_path("logo.png")
- end
-end
diff --git a/actionpack/test/template/streaming_render_test.rb b/actionpack/test/template/streaming_render_test.rb
index 4d01352b43..520bf3a824 100644
--- a/actionpack/test/template/streaming_render_test.rb
+++ b/actionpack/test/template/streaming_render_test.rb
@@ -106,4 +106,4 @@ class FiberedTest < ActiveSupport::TestCase
buffered_render(:template => "test/nested_streaming", :layout => "layouts/streaming")
end
-end if defined?(Fiber)
+end
diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb
index 60b466a9ff..f0a7ce0bc9 100644
--- a/actionpack/test/template/tag_helper_test.rb
+++ b/actionpack/test/template/tag_helper_test.rb
@@ -1,6 +1,8 @@
require 'abstract_unit'
class TagHelperTest < ActionView::TestCase
+ include RenderERBUtils
+
tests ActionView::Helpers::TagHelper
def test_tag
@@ -28,8 +30,8 @@ class TagHelperTest < ActionView::TestCase
end
def test_tag_options_converts_boolean_option
- assert_equal '<p disabled="disabled" multiple="multiple" readonly="readonly" />',
- tag("p", :disabled => true, :multiple => true, :readonly => true)
+ assert_equal '<p disabled="disabled" itemscope="itemscope" multiple="multiple" readonly="readonly" />',
+ tag("p", :disabled => true, :itemscope => true, :multiple => true, :readonly => true)
end
def test_content_tag
@@ -44,12 +46,12 @@ class TagHelperTest < ActionView::TestCase
end
def test_content_tag_with_block_in_erb
- buffer = content_tag(:div) { concat "Hello world!" }
+ buffer = render_erb("<%= content_tag(:div) do %>Hello world!<% end %>")
assert_dom_equal "<div>Hello world!</div>", buffer
end
def test_content_tag_with_block_and_options_in_erb
- buffer = content_tag(:div, :class => "green") { concat "Hello world!" }
+ buffer = render_erb("<%= content_tag(:div, :class => 'green') do %>Hello world!<% end %>")
assert_dom_equal %(<div class="green">Hello world!</div>), buffer
end
@@ -68,10 +70,8 @@ class TagHelperTest < ActionView::TestCase
output_buffer
end
- # TAG TODO: Move this into a real template
def test_content_tag_nested_in_content_tag_in_erb
- buffer = content_tag("p") { concat content_tag("b", "Hello") }
- assert_equal '<p><b>Hello</b></p>', buffer
+ assert_equal "<p>\n <b>Hello</b>\n</p>", view.render("test/content_tag_nested_in_content_tag")
end
def test_content_tag_with_escaped_array_class
@@ -91,6 +91,11 @@ class TagHelperTest < ActionView::TestCase
assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>")
end
+ def test_cdata_section_splitted
+ assert_equal "<![CDATA[hello]]]]><![CDATA[>world]]>", cdata_section("hello]]>world")
+ assert_equal "<![CDATA[hello]]]]><![CDATA[>world]]]]><![CDATA[>again]]>", cdata_section("hello]]>world]]>again")
+ end
+
def test_escape_once
assert_equal '1 &lt; 2 &amp; 3', escape_once('1 < 2 &amp; 3')
end
@@ -113,8 +118,8 @@ class TagHelperTest < ActionView::TestCase
def test_data_attributes
['data', :data].each { |data|
- assert_dom_equal '<a data-a-number="1" data-array="[1,2,3]" data-hash="{&quot;key&quot;:&quot;value&quot;}" data-string="hello" data-symbol="foo" />',
- tag('a', { data => { :a_number => 1, :string => 'hello', :symbol => :foo, :array => [1, 2, 3], :hash => { :key => 'value'} } })
+ assert_dom_equal '<a data-a-float="3.14" data-a-big-decimal="-123.456" data-a-number="1" data-array="[1,2,3]" data-hash="{&quot;key&quot;:&quot;value&quot;}" data-string="hello" data-symbol="foo" />',
+ tag('a', { data => { :a_float => 3.14, :a_big_decimal => BigDecimal.new("-123.456"), :a_number => 1, :string => 'hello', :symbol => :foo, :array => [1, 2, 3], :hash => { :key => 'value'} } })
}
end
end
diff --git a/actionpack/test/template/template_error_test.rb b/actionpack/test/template/template_error_test.rb
index 3a874082d9..91424daeed 100644
--- a/actionpack/test/template/template_error_test.rb
+++ b/actionpack/test/template/template_error_test.rb
@@ -2,12 +2,12 @@ require "abstract_unit"
class TemplateErrorTest < ActiveSupport::TestCase
def test_provides_original_message
- error = ActionView::Template::Error.new("test", {}, Exception.new("original"))
+ error = ActionView::Template::Error.new("test", Exception.new("original"))
assert_equal "original", error.message
end
def test_provides_useful_inspect
- error = ActionView::Template::Error.new("test", {}, Exception.new("original"))
+ error = ActionView::Template::Error.new("test", Exception.new("original"))
assert_equal "#<ActionView::Template::Error: original>", error.inspect
end
end
diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb
index b0ca7de0b6..322bea3fb0 100644
--- a/actionpack/test/template/template_test.rb
+++ b/actionpack/test/template/template_test.rb
@@ -8,6 +8,11 @@ class TestERBTemplate < ActiveSupport::TestCase
def disable_cache
yield
end
+
+ def find_template(*args)
+ end
+
+ attr_accessor :formats
end
class Context
@@ -34,7 +39,7 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def logger
- Logger.new(STDERR)
+ ActiveSupport::Logger.new(STDERR)
end
def my_buffer
@@ -43,7 +48,7 @@ class TestERBTemplate < ActiveSupport::TestCase
end
def new_template(body = "<%= hello %>", details = {})
- ActionView::Template.new(body, "hello template", ERBHandler, {:virtual_path => "hello"}.merge!(details))
+ ActionView::Template.new(body, "hello template", details.fetch(:handler) { ERBHandler }, {:virtual_path => "hello"}.merge!(details))
end
def render(locals = {})
@@ -59,6 +64,11 @@ class TestERBTemplate < ActiveSupport::TestCase
assert_equal "Hello", render
end
+ def test_raw_template
+ @template = new_template("<%= hello %>", :handler => ActionView::Template::Handlers::Raw.new)
+ assert_equal "<%= hello %>", render
+ end
+
def test_template_loses_its_source_after_rendering
@template = new_template
render
@@ -111,66 +121,65 @@ class TestERBTemplate < ActiveSupport::TestCase
end
end
- if "ruby".encoding_aware?
- def test_resulting_string_is_utf8
- @template = new_template
- assert_equal Encoding::UTF_8, render.encoding
- end
+ def test_resulting_string_is_utf8
+ @template = new_template
+ assert_equal Encoding::UTF_8, render.encoding
+ end
- def test_no_magic_comment_word_with_utf_8
- @template = new_template("hello \u{fc}mlat")
+ def test_no_magic_comment_word_with_utf_8
+ @template = new_template("hello \u{fc}mlat")
+ assert_equal Encoding::UTF_8, render.encoding
+ assert_equal "hello \u{fc}mlat", render
+ end
+
+ # This test ensures that if the default_external
+ # is set to something other than UTF-8, we don't
+ # get any errors and get back a UTF-8 String.
+ def test_default_external_works
+ with_external_encoding "ISO-8859-1" do
+ @template = new_template("hello \xFCmlat")
assert_equal Encoding::UTF_8, render.encoding
assert_equal "hello \u{fc}mlat", render
end
+ end
- # This test ensures that if the default_external
- # is set to something other than UTF-8, we don't
- # get any errors and get back a UTF-8 String.
- def test_default_external_works
- with_external_encoding "ISO-8859-1" do
- @template = new_template("hello \xFCmlat")
- assert_equal Encoding::UTF_8, render.encoding
- assert_equal "hello \u{fc}mlat", render
- end
- end
-
- def test_encoding_can_be_specified_with_magic_comment
- @template = new_template("# encoding: ISO-8859-1\nhello \xFCmlat")
- assert_equal Encoding::UTF_8, render.encoding
- assert_equal "\nhello \u{fc}mlat", render
- end
+ def test_encoding_can_be_specified_with_magic_comment
+ @template = new_template("# encoding: ISO-8859-1\nhello \xFCmlat")
+ assert_equal Encoding::UTF_8, render.encoding
+ assert_equal "\nhello \u{fc}mlat", render
+ end
- # TODO: This is currently handled inside ERB. The case of explicitly
- # lying about encodings via the normal Rails API should be handled
- # inside Rails.
- def test_lying_with_magic_comment
- assert_raises(ActionView::Template::Error) do
- @template = new_template("# encoding: UTF-8\nhello \xFCmlat", :virtual_path => nil)
- render
- end
+ # TODO: This is currently handled inside ERB. The case of explicitly
+ # lying about encodings via the normal Rails API should be handled
+ # inside Rails.
+ def test_lying_with_magic_comment
+ assert_raises(ActionView::Template::Error) do
+ @template = new_template("# encoding: UTF-8\nhello \xFCmlat", :virtual_path => nil)
+ render
end
+ end
- def test_encoding_can_be_specified_with_magic_comment_in_erb
- with_external_encoding Encoding::UTF_8 do
- @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", :virtual_path => nil)
- assert_equal Encoding::UTF_8, render.encoding
- assert_equal "hello \u{fc}mlat", render
- end
+ def test_encoding_can_be_specified_with_magic_comment_in_erb
+ with_external_encoding Encoding::UTF_8 do
+ @template = new_template("<%# encoding: ISO-8859-1 %>hello \xFCmlat", :virtual_path => nil)
+ assert_equal Encoding::UTF_8, render.encoding
+ assert_equal "hello \u{fc}mlat", render
end
+ end
- def test_error_when_template_isnt_valid_utf8
- assert_raises(ActionView::Template::Error, /\xFC/) do
- @template = new_template("hello \xFCmlat", :virtual_path => nil)
- render
- end
+ def test_error_when_template_isnt_valid_utf8
+ assert_raises(ActionView::Template::Error, /\xFC/) do
+ @template = new_template("hello \xFCmlat", :virtual_path => nil)
+ render
end
+ end
- def with_external_encoding(encoding)
- old = Encoding.default_external
- silence_warnings { Encoding.default_external = encoding }
- yield
- ensure
- silence_warnings { Encoding.default_external = old }
- end
+ def with_external_encoding(encoding)
+ old = Encoding.default_external
+ Encoding::Converter.new old, encoding if old != encoding
+ silence_warnings { Encoding.default_external = encoding }
+ yield
+ ensure
+ silence_warnings { Encoding.default_external = old }
end
end
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index a75f1bc981..f2ed2ec609 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -155,7 +155,7 @@ module ActionView
test "view_assigns excludes internal ivars" do
INTERNAL_IVARS.each do |ivar|
assert defined?(ivar), "expected #{ivar} to be defined"
- assert !view_assigns.keys.include?(ivar.sub('@','').to_sym), "expected #{ivar} to be excluded from view_assigns"
+ assert !view_assigns.keys.include?(ivar.to_s.sub('@', '').to_sym), "expected #{ivar} to be excluded from view_assigns"
end
end
end
@@ -277,6 +277,12 @@ module ActionView
end
class RenderTemplateTest < ActionView::TestCase
+ test "supports specifying templates with a Regexp" do
+ controller.controller_path = "fun"
+ render(:template => "fun/games/hello_world")
+ assert_template %r{\Afun/games/hello_world\Z}
+ end
+
test "supports specifying partials" do
controller.controller_path = "test"
render(:template => "test/calling_partial_with_layout")
diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb
index adcbf1447f..108a674d95 100644
--- a/actionpack/test/template/test_test.rb
+++ b/actionpack/test/template/test_test.rb
@@ -48,7 +48,7 @@ class PeopleHelperTest < ActionView::TestCase
def with_test_route_set
with_routing do |set|
set.draw do
- match 'people', :to => 'people#index', :as => :people
+ get 'people', :to => 'people#index', :as => :people
end
yield
end
diff --git a/actionpack/test/template/testing/fixture_resolver_test.rb b/actionpack/test/template/testing/fixture_resolver_test.rb
index de83540468..9649f349cb 100644
--- a/actionpack/test/template/testing/fixture_resolver_test.rb
+++ b/actionpack/test/template/testing/fixture_resolver_test.rb
@@ -8,8 +8,8 @@ class FixtureResolverTest < ActiveSupport::TestCase
end
def test_should_return_template_for_declared_path
- resolver = ActionView::FixtureResolver.new("arbitrary/path" => "this text")
- templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
+ resolver = ActionView::FixtureResolver.new("arbitrary/path.erb" => "this text")
+ templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => [:erb]})
assert_equal 1, templates.size, "expected one template"
assert_equal "this text", templates.first.source
assert_equal "arbitrary/path", templates.first.virtual_path
diff --git a/actionpack/test/template/testing/null_resolver_test.rb b/actionpack/test/template/testing/null_resolver_test.rb
index e142506e6a..535ad3ab14 100644
--- a/actionpack/test/template/testing/null_resolver_test.rb
+++ b/actionpack/test/template/testing/null_resolver_test.rb
@@ -3,10 +3,10 @@ require 'abstract_unit'
class NullResolverTest < ActiveSupport::TestCase
def test_should_return_template_for_any_path
resolver = ActionView::NullResolver.new()
- templates = resolver.find_all("path", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
+ templates = resolver.find_all("path.erb", "arbitrary", false, {:locale => [], :formats => [:html], :handlers => []})
assert_equal 1, templates.size, "expected one template"
assert_equal "Template generated by Null Resolver", templates.first.source
- assert_equal "arbitrary/path", templates.first.virtual_path
+ assert_equal "arbitrary/path.erb", templates.first.virtual_path
assert_equal [:html], templates.first.formats
end
end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index 02f9609483..f58e474759 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -1,10 +1,8 @@
# encoding: utf-8
require 'abstract_unit'
-require 'testing_sandbox'
class TextHelperTest < ActionView::TestCase
tests ActionView::Helpers::TextHelper
- include TestingSandbox
def setup
super
@@ -48,12 +46,34 @@ class TextHelperTest < ActionView::TestCase
assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false)
end
+ def test_simple_format_with_custom_wrapper
+ assert_equal "<div></div>", simple_format(nil, {}, :wrapper_tag => "div")
+ end
+
+ def test_simple_format_with_custom_wrapper_and_multi_line_breaks
+ assert_equal "<div>We want to put a wrapper...</div>\n\n<div>...right there.</div>", simple_format("We want to put a wrapper...\n\n...right there.", {}, :wrapper_tag => "div")
+ end
+
def test_simple_format_should_not_change_the_text_passed
text = "<b>Ok</b><script>code!</script>"
text_clone = text.dup
simple_format(text)
assert_equal text_clone, text
end
+
+ def test_simple_format_does_not_modify_the_html_options_hash
+ options = { :class => "foobar"}
+ passed_options = options.dup
+ simple_format("some text", passed_options)
+ assert_equal options, passed_options
+ end
+
+ def test_simple_format_does_not_modify_the_options_hash
+ options = { :wrapper_tag => :div, :sanitize => false }
+ passed_options = options.dup
+ simple_format("some text", {}, passed_options)
+ assert_equal options, passed_options
+ end
def test_truncate_should_not_be_html_safe
assert !truncate("Hello World!", :length => 12).html_safe?
@@ -82,25 +102,16 @@ class TextHelperTest < ActionView::TestCase
assert_equal "Hello Big[...]", truncate("Hello Big World!", :omission => "[...]", :length => 15, :separator => ' ')
end
- if RUBY_VERSION < '1.9.0'
- def test_truncate_multibyte
- with_kcode 'none' do
- assert_equal "\354\225\210\353\205\225\355...", truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", :length => 10)
- end
- with_kcode 'u' do
- assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...",
- truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244", :length => 10)
- end
- end
- else
- def test_truncate_multibyte
- # .mb_chars always returns a UTF-8 String.
- # assert_equal "\354\225\210\353\205\225\355...",
- # truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", :length => 10)
-
- assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'),
- truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10)
- end
+ def test_truncate_multibyte
+ assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'),
+ truncate("\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8'), :length => 10)
+ end
+
+ def test_truncate_does_not_modify_the_options_hash
+ options = { :length => 10 }
+ passed_options = options.dup
+ truncate("some text", passed_options)
+ assert_equal options, passed_options
end
def test_highlight_should_be_html_safe
@@ -109,18 +120,18 @@ class TextHelperTest < ActionView::TestCase
def test_highlight
assert_equal(
- "This is a <strong class=\"highlight\">beautiful</strong> morning",
+ "This is a <mark>beautiful</mark> morning",
highlight("This is a beautiful morning", "beautiful")
)
assert_equal(
- "This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day",
+ "This is a <mark>beautiful</mark> morning, but also a <mark>beautiful</mark> day",
highlight("This is a beautiful morning, but also a beautiful day", "beautiful")
)
assert_equal(
"This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
- highlight("This is a beautiful morning, but also a beautiful day", "beautiful", '<b>\1</b>')
+ highlight("This is a beautiful morning, but also a beautiful day", "beautiful", :highlighter => '<b>\1</b>')
)
assert_equal(
@@ -133,104 +144,107 @@ class TextHelperTest < ActionView::TestCase
def test_highlight_should_sanitize_input
assert_equal(
- "This is a <strong class=\"highlight\">beautiful</strong> morning",
+ "This is a <mark>beautiful</mark> morning",
highlight("This is a beautiful morning<script>code!</script>", "beautiful")
)
end
def test_highlight_should_not_sanitize_if_sanitize_option_if_false
assert_equal(
- "This is a <strong class=\"highlight\">beautiful</strong> morning<script>code!</script>",
+ "This is a <mark>beautiful</mark> morning<script>code!</script>",
highlight("This is a beautiful morning<script>code!</script>", "beautiful", :sanitize => false)
)
end
def test_highlight_with_regexp
assert_equal(
- "This is a <strong class=\"highlight\">beautiful!</strong> morning",
+ "This is a <mark>beautiful!</mark> morning",
highlight("This is a beautiful! morning", "beautiful!")
)
assert_equal(
- "This is a <strong class=\"highlight\">beautiful! morning</strong>",
+ "This is a <mark>beautiful! morning</mark>",
highlight("This is a beautiful! morning", "beautiful! morning")
)
assert_equal(
- "This is a <strong class=\"highlight\">beautiful? morning</strong>",
+ "This is a <mark>beautiful? morning</mark>",
highlight("This is a beautiful? morning", "beautiful? morning")
)
end
def test_highlight_with_multiple_phrases_in_one_pass
- assert_equal %(<em>wow</em> <em>em</em>), highlight('wow em', %w(wow em), '<em>\1</em>')
- end
-
- def test_highlight_with_options_hash
- assert_equal(
- "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
- highlight("This is a beautiful morning, but also a beautiful day", "beautiful", :highlighter => '<b>\1</b>')
- )
+ assert_equal %(<em>wow</em> <em>em</em>), highlight('wow em', %w(wow em), :highlighter => '<em>\1</em>')
end
def test_highlight_with_html
assert_equal(
- "<p>This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ "<p>This is a <mark>beautiful</mark> morning, but also a <mark>beautiful</mark> day</p>",
highlight("<p>This is a beautiful morning, but also a beautiful day</p>", "beautiful")
)
assert_equal(
- "<p>This is a <em><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ "<p>This is a <em><mark>beautiful</mark></em> morning, but also a <mark>beautiful</mark> day</p>",
highlight("<p>This is a <em>beautiful</em> morning, but also a beautiful day</p>", "beautiful")
)
assert_equal(
- "<p>This is a <em class=\"error\"><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> <span class=\"last\">day</span></p>",
+ "<p>This is a <em class=\"error\"><mark>beautiful</mark></em> morning, but also a <mark>beautiful</mark> <span class=\"last\">day</span></p>",
highlight("<p>This is a <em class=\"error\">beautiful</em> morning, but also a beautiful <span class=\"last\">day</span></p>", "beautiful")
)
assert_equal(
- "<p class=\"beautiful\">This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ "<p class=\"beautiful\">This is a <mark>beautiful</mark> morning, but also a <mark>beautiful</mark> day</p>",
highlight("<p class=\"beautiful\">This is a beautiful morning, but also a beautiful day</p>", "beautiful")
)
assert_equal(
- "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful#top?what=beautiful%20morning&amp;when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>",
+ "<p>This is a <mark>beautiful</mark> <a href=\"http://example.com/beautiful#top?what=beautiful%20morning&amp;when=now+then\">morning</a>, but also a <mark>beautiful</mark> day</p>",
highlight("<p>This is a beautiful <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a beautiful day</p>", "beautiful")
)
+ assert_equal(
+ "<div>abc <b>div</b></div>",
+ highlight("<div>abc div</div>", "div", :highlighter => '<b>\1</b>')
+ )
+ end
+
+ def test_highlight_does_not_modify_the_options_hash
+ options = { :highlighter => '<b>\1</b>', :sanitize => false }
+ passed_options = options.dup
+ highlight("<div>abc div</div>", "div", passed_options)
+ assert_equal options, passed_options
end
def test_excerpt
- assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", 5))
- assert_equal("This is a...", excerpt("This is a beautiful morning", "this", 5))
- assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", 5))
+ assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", :radius => 5))
+ assert_equal("This is a...", excerpt("This is a beautiful morning", "this", :radius => 5))
+ assert_equal("...iful morning", excerpt("This is a beautiful morning", "morning", :radius => 5))
assert_nil excerpt("This is a beautiful morning", "day")
end
def test_excerpt_should_not_be_html_safe
- assert !excerpt('This is a beautiful! morning', 'beautiful', 5).html_safe?
+ assert !excerpt('This is a beautiful! morning', 'beautiful', :radius => 5).html_safe?
end
def test_excerpt_in_borderline_cases
- assert_equal("", excerpt("", "", 0))
- assert_equal("a", excerpt("a", "a", 0))
- assert_equal("...b...", excerpt("abc", "b", 0))
- assert_equal("abc", excerpt("abc", "b", 1))
- assert_equal("abc...", excerpt("abcd", "b", 1))
- assert_equal("...abc", excerpt("zabc", "b", 1))
- assert_equal("...abc...", excerpt("zabcd", "b", 1))
- assert_equal("zabcd", excerpt("zabcd", "b", 2))
+ assert_equal("", excerpt("", "", :radius => 0))
+ assert_equal("a", excerpt("a", "a", :radius => 0))
+ assert_equal("...b...", excerpt("abc", "b", :radius => 0))
+ assert_equal("abc", excerpt("abc", "b", :radius => 1))
+ assert_equal("abc...", excerpt("abcd", "b", :radius => 1))
+ assert_equal("...abc", excerpt("zabc", "b", :radius => 1))
+ assert_equal("...abc...", excerpt("zabcd", "b", :radius => 1))
+ assert_equal("zabcd", excerpt("zabcd", "b", :radius => 2))
# excerpt strips the resulting string before ap-/prepending excerpt_string.
# whether this behavior is meaningful when excerpt_string is not to be
# appended is questionable.
- assert_equal("zabcd", excerpt(" zabcd ", "b", 4))
- assert_equal("...abc...", excerpt("z abc d", "b", 1))
+ assert_equal("zabcd", excerpt(" zabcd ", "b", :radius => 4))
+ assert_equal("...abc...", excerpt("z abc d", "b", :radius => 1))
end
def test_excerpt_with_regex
- assert_equal('...is a beautiful! mor...', excerpt('This is a beautiful! morning', 'beautiful', 5))
- assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', 'beautiful', 5))
+ assert_equal('...is a beautiful! mor...', excerpt('This is a beautiful! morning', 'beautiful', :radius => 5))
+ assert_equal('...is a beautiful? mor...', excerpt('This is a beautiful? morning', 'beautiful', :radius => 5))
end
- def test_excerpt_with_options_hash
- assert_equal("...is a beautiful morn...", excerpt("This is a beautiful morning", "beautiful", :radius => 5))
+ def test_excerpt_with_omission
assert_equal("[...]is a beautiful morn[...]", excerpt("This is a beautiful morning", "beautiful", :omission => "[...]",:radius => 5))
assert_equal(
"This is the ultimate supercalifragilisticexpialidoceous very looooooooooooooooooong looooooooooooong beautiful morning with amazing sunshine and awesome tempera[...]",
@@ -239,33 +253,30 @@ class TextHelperTest < ActionView::TestCase
)
end
- if RUBY_VERSION < '1.9'
- def test_excerpt_with_utf8
- with_kcode('u') do
- assert_equal("...\357\254\203ciency could not be...", excerpt("That's why e\357\254\203ciency could not be helped", 'could', 8))
- end
- with_kcode('none') do
- assert_equal("...\203ciency could not be...", excerpt("That's why e\357\254\203ciency could not be helped", 'could', 8))
- end
- end
- else
- def test_excerpt_with_utf8
- assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', 8))
- # .mb_chars always returns UTF-8, even in 1.9. This is not great, but it's how it works. Let's work this out.
- # assert_equal("...\203ciency could not be...", excerpt("That's why e\357\254\203ciency could not be helped".force_encoding("BINARY"), 'could', 8))
- end
+ def test_excerpt_with_utf8
+ assert_equal("...\357\254\203ciency could not be...".force_encoding('UTF-8'), excerpt("That's why e\357\254\203ciency could not be helped".force_encoding('UTF-8'), 'could', :radius => 8))
+ end
+
+ def test_excerpt_does_not_modify_the_options_hash
+ options = { :omission => "[...]",:radius => 5 }
+ passed_options = options.dup
+ excerpt("This is a beautiful morning", "beautiful", passed_options)
+ assert_equal options, passed_options
end
def test_word_wrap
- assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", 15))
+ assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15))
end
def test_word_wrap_with_extra_newlines
- assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", 15))
- end
-
- def test_word_wrap_with_options_hash
- assert_equal("my very very\nvery long\nstring", word_wrap("my very very very long string", :line_width => 15))
+ assert_equal("my very very\nvery long\nstring\n\nwith another\nline", word_wrap("my very very very long string\n\nwith another line", :line_width => 15))
+ end
+
+ def test_word_wrap_does_not_modify_the_options_hash
+ options = { :line_width => 15 }
+ passed_options = options.dup
+ word_wrap("some text", passed_options)
+ assert_equal options, passed_options
end
def test_pluralization
diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb
index cd9f54e04c..97777ccff0 100644
--- a/actionpack/test/template/translation_helper_test.rb
+++ b/actionpack/test/template/translation_helper_test.rb
@@ -11,14 +11,20 @@ class TranslationHelperTest < ActiveSupport::TestCase
:translations => {
:templates => {
:found => { :foo => 'Foo' },
- :array => { :foo => { :bar => 'Foo Bar' } }
+ :array => { :foo => { :bar => 'Foo Bar' } },
+ :default => { :foo => 'Foo' }
},
:foo => 'Foo',
:hello => '<a>Hello World</a>',
:html => '<a>Hello World</a>',
:hello_html => '<a>Hello World</a>',
+ :interpolated_html => '<a>Hello %{word}</a>',
:array_html => %w(foo bar),
- :array => %w(foo bar)
+ :array => %w(foo bar),
+ :count_html => {
+ :one => '<a>One %{count}</a>',
+ :other => '<a>Other %{count}</a>'
+ }
}
)
@view = ::ActionView::Base.new(ActionController::Base.view_paths, {})
@@ -66,6 +72,10 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert_equal 'Foo Bar', @view.render(:file => 'translations/templates/array').strip
end
+ def test_default_lookup_scoped_by_partial
+ assert_equal 'Foo', view.render(:file => 'translations/templates/default').strip
+ end
+
def test_missing_translation_scoped_by_partial
expected = '<span class="translation_missing" title="translation missing: en.translations.templates.missing.missing">Missing</span>'
assert_equal expected, view.render(:file => 'translations/templates/missing').strip
@@ -83,7 +93,36 @@ class TranslationHelperTest < ActiveSupport::TestCase
assert translate(:'translations.hello_html').html_safe?
end
+ def test_translate_escapes_interpolations_in_translations_with_a_html_suffix
+ assert_equal '<a>Hello &lt;World&gt;</a>', translate(:'translations.interpolated_html', :word => '<World>')
+ assert_equal '<a>Hello &lt;World&gt;</a>', translate(:'translations.interpolated_html', :word => stub(:to_s => "<World>"))
+ end
+
+ def test_translate_with_html_count
+ assert_equal '<a>One 1</a>', translate(:'translations.count_html', :count => 1)
+ assert_equal '<a>Other 2</a>', translate(:'translations.count_html', :count => 2)
+ assert_equal '<a>Other &lt;One&gt;</a>', translate(:'translations.count_html', :count => '<One>')
+ end
+
def test_translation_returning_an_array_ignores_html_suffix
assert_equal ["foo", "bar"], translate(:'translations.array_html')
end
+
+ def test_translate_with_default_named_html
+ translation = translate(:'translations.missing', :default => :'translations.hello_html')
+ assert_equal '<a>Hello World</a>', translation
+ assert translation.html_safe?
+ end
+
+ def test_translate_with_two_defaults_named_html
+ translation = translate(:'translations.missing', :default => [:'translations.missing_html', :'translations.hello_html'])
+ assert_equal '<a>Hello World</a>', translation
+ assert translation.html_safe?
+ end
+
+ def test_translate_with_last_default_named_html
+ translation = translate(:'translations.missing', :default => [:'translations.missing', :'translations.hello_html'])
+ assert_equal '<a>Hello World</a>', translation
+ assert translation.html_safe?
+ end
end
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index bc45fabf34..fb5b35bac6 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -1,6 +1,5 @@
# encoding: utf-8
require 'abstract_unit'
-require 'active_support/ordered_hash'
require 'controller/fake_controllers'
class UrlHelperTest < ActiveSupport::TestCase
@@ -11,11 +10,14 @@ class UrlHelperTest < ActiveSupport::TestCase
# In those cases, we'll set up a simple mock
attr_accessor :controller, :request
+ cattr_accessor :request_forgery
+ self.request_forgery = false
+
routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
- match "/" => "foo#bar"
- match "/other" => "foo#other"
- match "/article/:id" => "foo#article", :as => :article
+ get "/" => "foo#bar"
+ get "/other" => "foo#other"
+ get "/article/:id" => "foo#article", :as => :article
end
include routes.url_helpers
@@ -28,13 +30,13 @@ class UrlHelperTest < ActiveSupport::TestCase
setup :_prepare_context
- def hash_for(opts = [])
- ActiveSupport::OrderedHash[*([:controller, "foo", :action, "bar"].concat(opts))]
+ def hash_for(options = {})
+ { :controller => "foo", :action => "bar" }.merge!(options)
end
alias url_hash hash_for
def test_url_for_does_not_escape_urls
- assert_equal "/?a=b&c=d", url_for(hash_for([:a, :b, :c, :d]))
+ assert_equal "/?a=b&c=d", url_for(hash_for(:a => :b, :c => :d))
end
def test_url_for_with_back
@@ -49,11 +51,22 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal 'javascript:history.back()', url_for(:back)
end
- # todo: missing test cases
+ # TODO: missing test cases
def test_button_to_with_straight_url
assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com")
end
+ def test_button_to_with_straight_url_and_request_forgery
+ self.request_forgery = true
+
+ assert_dom_equal(
+ %{<form method="post" action="http://www.example.com" class="button_to"><div><input type="submit" value="Hello" /><input name="form_token" type="hidden" value="secret" /></div></form>},
+ button_to("Hello", "http://www.example.com")
+ )
+ ensure
+ self.request_forgery = false
+ end
+
def test_button_to_with_form_class
assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"custom-class\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :form_class => 'custom-class')
end
@@ -84,14 +97,14 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_button_to_with_javascript_disable_with
assert_dom_equal(
"<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>",
- button_to("Hello", "http://www.example.com", :disable_with => "Greeting...")
+ button_to("Hello", "http://www.example.com", 'data-disable-with' => "Greeting...")
)
end
def test_button_to_with_remote_and_form_options
assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"custom-class\" data-remote=\"true\" data-type=\"json\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com", :remote => true, :form => { :class => "custom-class", "data-type" => "json" } )
end
-
+
def test_button_to_with_remote_and_javascript_confirm
assert_dom_equal(
"<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>",
@@ -99,20 +112,6 @@ class UrlHelperTest < ActiveSupport::TestCase
)
end
- def test_button_to_with_remote_and_javascript_disable_with
- assert_dom_equal(
- "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-disable-with=\"Greeting...\" type=\"submit\" value=\"Hello\" /></div></form>",
- button_to("Hello", "http://www.example.com", :remote => true, :disable_with => "Greeting...")
- )
- end
-
- def test_button_to_with_remote_and_javascript_confirm_and_javascript_disable_with
- assert_dom_equal(
- "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\" data-remote=\"true\"><div><input data-disable-with=\"Greeting...\" data-confirm=\"Are you sure?\" type=\"submit\" value=\"Hello\" /></div></form>",
- button_to("Hello", "http://www.example.com", :remote => true, :confirm => "Are you sure?", :disable_with => "Greeting...")
- )
- end
-
def test_button_to_with_remote_false
assert_dom_equal(
"<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>",
@@ -154,7 +153,7 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_link_tag_with_host_option
- hash = hash_for([:host, "www.example.com"])
+ hash = hash_for(:host => "www.example.com")
expected = %q{<a href="http://www.example.com/">Test Link</a>}
assert_dom_equal(expected, link_to('Test Link', hash))
end
@@ -329,7 +328,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_current_page_with_params_that_match
@request = request_for_url("/?order=desc&page=1")
- assert current_page?(hash_for([:order, "desc", :page, "1"]))
+ assert current_page?(hash_for(:order => "desc", :page => "1"))
assert current_page?("http://www.example.com/?order=desc&page=1")
end
@@ -357,20 +356,20 @@ class UrlHelperTest < ActiveSupport::TestCase
@request = request_for_url("/?order=desc&page=1")
assert_equal "Showing",
- link_to_unless_current("Showing", hash_for([:order, 'desc', :page, '1']))
+ link_to_unless_current("Showing", hash_for(:order => 'desc', :page => '1'))
assert_equal "Showing",
link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=1")
@request = request_for_url("/?order=desc")
assert_equal %{<a href="/?order=asc">Showing</a>},
- link_to_unless_current("Showing", hash_for([:order, :asc]))
+ link_to_unless_current("Showing", hash_for(:order => :asc))
assert_equal %{<a href="http://www.example.com/?order=asc">Showing</a>},
link_to_unless_current("Showing", "http://www.example.com/?order=asc")
@request = request_for_url("/?order=desc")
assert_equal %{<a href="/?order=desc&amp;page=2\">Showing</a>},
- link_to_unless_current("Showing", hash_for([:order, "desc", :page, 2]))
+ link_to_unless_current("Showing", hash_for(:order => "desc", :page => 2))
assert_equal %{<a href="http://www.example.com/?order=desc&amp;page=2">Showing</a>},
link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=2")
@@ -395,12 +394,12 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_mail_to_with_javascript
snippet = mail_to("me@domain.com", "My email", :encode => "javascript")
- assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
+ assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
end
def test_mail_to_with_javascript_unicode
snippet = mail_to("unicode@example.com", "únicode", :encode => "javascript")
- assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
+ assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%75%6e%69%63%6f%64%65%40%65%78%61%6d%70%6c%65%2e%63%6f%6d%5c%22%3e%c3%ba%6e%69%63%6f%64%65%3c%5c%2f%61%3e%27%29%3b'))</script>", snippet
end
def test_mail_with_options
@@ -425,8 +424,8 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#46;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)")
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#40;&#100;&#111;&#116;&#41;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
- assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
- assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
+ assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%4d%79%20%65%6d%61%69%6c%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
+ assert_dom_equal "<script>eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%5c%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%5c%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%5c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
end
def test_mail_to_returns_html_safe_string
@@ -435,9 +434,16 @@ class UrlHelperTest < ActiveSupport::TestCase
assert mail_to("me@domain.com", "My email", :encode => "hex").html_safe?
end
- # TODO: button_to looks at this ... why?
def protect_against_forgery?
- false
+ self.request_forgery
+ end
+
+ def form_authenticity_token
+ "secret"
+ end
+
+ def request_forgery_protection_token
+ "form_token"
end
private
@@ -451,25 +457,25 @@ end
class UrlHelperControllerTest < ActionController::TestCase
class UrlHelperController < ActionController::Base
test_routes do
- match 'url_helper_controller_test/url_helper/show/:id',
+ get 'url_helper_controller_test/url_helper/show/:id',
:to => 'url_helper_controller_test/url_helper#show',
:as => :show
- match 'url_helper_controller_test/url_helper/profile/:name',
+ get 'url_helper_controller_test/url_helper/profile/:name',
:to => 'url_helper_controller_test/url_helper#show',
:as => :profile
- match 'url_helper_controller_test/url_helper/show_named_route',
+ get 'url_helper_controller_test/url_helper/show_named_route',
:to => 'url_helper_controller_test/url_helper#show_named_route',
:as => :show_named_route
- match "/:controller(/:action(/:id))"
+ get "/:controller(/:action(/:id))"
- match 'url_helper_controller_test/url_helper/normalize_recall_params',
+ get 'url_helper_controller_test/url_helper/normalize_recall_params',
:to => UrlHelperController.action(:normalize_recall),
:as => :normalize_recall_params
- match '/url_helper_controller_test/url_helper/override_url_helper/default',
+ get '/url_helper_controller_test/url_helper/override_url_helper/default',
:to => 'url_helper_controller_test/url_helper#override_url_helper',
:as => :override_url_helper
end
@@ -506,8 +512,6 @@ class UrlHelperControllerTest < ActionController::TestCase
render :inline => '<%= url_for(:action => :show_url_for) %>'
end
- def rescue_action(e) raise e end
-
def override_url_helper
render :inline => '<%= override_url_helper_path %>'
end
@@ -548,7 +552,7 @@ class UrlHelperControllerTest < ActionController::TestCase
def test_named_route_should_show_host_and_path_using_controller_default_url_options
class << @controller
- def default_url_options(options = nil)
+ def default_url_options
{:host => 'testtwo.host'}
end
end
@@ -595,8 +599,6 @@ class TasksController < ActionController::Base
render_default
end
- def rescue_action(e) raise e end
-
protected
def render_default
render :inline =>
@@ -655,8 +657,6 @@ class WorkshopsController < ActionController::Base
@workshop = Workshop.new(params[:id])
render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>"
end
-
- def rescue_action(e) raise e end
end
class SessionsController < ActionController::Base
@@ -677,8 +677,6 @@ class SessionsController < ActionController::Base
@session = Session.new(params[:id])
render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>"
end
-
- def rescue_action(e) raise e end
end
class PolymorphicControllerTest < ActionController::TestCase
diff --git a/actionpack/test/ts_isolated.rb b/actionpack/test/ts_isolated.rb
index 5670d93613..595b4018e9 100644
--- a/actionpack/test/ts_isolated.rb
+++ b/actionpack/test/ts_isolated.rb
@@ -1,10 +1,8 @@
-$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
-
-require 'test/unit'
+require 'minitest/autorun'
require 'rbconfig'
-require 'active_support/core_ext/kernel/reporting'
+require 'abstract_unit'
-class TestIsolated < Test::Unit::TestCase
+class TestIsolated < ActiveSupport::TestCase
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
Dir["#{File.dirname(__FILE__)}/{abstract,controller,dispatch,template}/**/*_test.rb"].each do |file|
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
deleted file mode 100644
index 3d26d646b0..0000000000
--- a/activemodel/CHANGELOG
+++ /dev/null
@@ -1,107 +0,0 @@
-* Add ability to define strict validation(with :strict => true option) that always raises exception when fails [Bogdan Gusiev]
-
-* Deprecate "Model.model_name.partial_path" in favor of "model.to_partial_path" [Grant Hutchins, Peter Jaros]
-
-* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior [Bogdan Gusiev]
-
-*Rails 3.1.0 (August 30, 2011)*
-
-* Alternate I18n namespace lookup is no longer supported.
- Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead [José Valim]
-
-* attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis]
-
-* 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]
-
-* Add support for selectively enabling/disabling observers [Myron Marston]
-
-
-*Rails 3.0.7 (April 18, 2011)*
-
-*No changes.
-
-
-*Rails 3.0.6 (April 5, 2011)
-
-* Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 [Robert Pankowecki, Santiago Pastorino]
-
-* Fix length validation for fixnums #6556 [Andriy Tyurnikov]
-
-* Fix i18n key collision with namespaced models #6448 [yves.senn]
-
-
-*Rails 3.0.5 (February 26, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.4 (February 8, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.3 (November 16, 2010)*
-
-* No changes.
-
-
-*Rails 3.0.2 (November 15, 2010)*
-
-* No changes
-
-
-*Rails 3.0.1 (October 15, 2010)*
-
-* No Changes, just a version bump.
-
-
-*Rails 3.0.0 (August 29, 2010)*
-
-* Added ActiveModel::MassAssignmentSecurity [Eric Chapweske, Josh Kalderimis]
-
-* JSON supports a custom root option: to_json(:root => 'custom') #4515 [Jatinder Singh]
-
-* #new_record? and #destroyed? were removed from ActiveModel::Lint. Use
- persisted? instead. A model is persisted if it's not a new_record? and it was
- not destroyed? [MG]
-
-* Added validations reflection in ActiveModel::Validations [JV]
-
- Model.validators
- Model.validators_on(:field)
-
-* #to_key was added to ActiveModel::Lint so we can generate DOM IDs for
- AMo objects with composite keys [MG]
-
-* ActiveModel::Observer#add_observer!
-
- It has a custom hook to define after_find that should really be in a
- ActiveRecord::Observer subclass:
-
- def add_observer!(klass)
- klass.add_observer(self)
- klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
- end
-
-* Change the ActiveModel::Base.include_root_in_json default to true for Rails 3 [DHH]
-
-* Add validates_format_of :without => /regexp/ option. #430 [Elliot Winkler, Peer Allan]
-
- Example :
-
- validates_format_of :subdomain, :without => /www|admin|mail/
-
-* Introduce validates_with to encapsulate attribute validations in a class. #2630 [Jeff Dean]
-
-* Extracted from Active Record and Active Resource.
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
new file mode 100644
index 0000000000..789cff0673
--- /dev/null
+++ b/activemodel/CHANGELOG.md
@@ -0,0 +1,183 @@
+## Rails 4.0.0 (unreleased) ##
+
+* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella*
+
+* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran*
+
+* `AM::Errors#to_json`: support `:full_messages` parameter *Bogdan Gusiev*
+
+* Trim down Active Model API by removing `valid?` and `errors.full_messages` *José Valim*
+
+
+## Rails 3.2.2 (March 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* Deprecated `define_attr_method` in `ActiveModel::AttributeMethods`, because this only existed to
+ support methods like `set_table_name` in Active Record, which are themselves being deprecated.
+
+ *Jon Leighton*
+
+* Add ActiveModel::Errors#added? to check if a specific error has been added *Martin Svalin*
+
+* Add ability to define strict validation(with :strict => true option) that always raises exception when fails *Bogdan Gusiev*
+
+* Deprecate "Model.model_name.partial_path" in favor of "model.to_partial_path" *Grant Hutchins, Peter Jaros*
+
+* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior *Bogdan Gusiev*
+
+
+## Rails 3.1.3 (November 20, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.2 (November 18, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.1 (October 7, 2011) ##
+
+* Remove hard dependency on bcrypt-ruby to avoid make ActiveModel dependent on a binary library.
+ You must add the gem explicitly to your Gemfile if you want use ActiveModel::SecurePassword:
+
+ gem 'bcrypt-ruby', '~> 3.0.0'
+
+ See GH #2687. *Guillermo Iguaran*
+
+
+## Rails 3.1.0 (August 30, 2011) ##
+
+* Alternate I18n namespace lookup is no longer supported.
+ Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead *José Valim*
+
+* attr_accessible and friends now accepts :as as option to specify a role *Josh Kalderimis*
+
+* 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*
+
+* Add support for selectively enabling/disabling observers *Myron Marston*
+
+
+## Rails 3.0.12 (March 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.7 (April 18, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.6 (April 5, 2011) ##
+
+* Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 *Robert Pankowecki, Santiago Pastorino*
+
+* Fix length validation for fixnums #6556 *Andriy Tyurnikov*
+
+* Fix i18n key collision with namespaced models #6448 *yves.senn*
+
+
+## Rails 3.0.5 (February 26, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.4 (February 8, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.3 (November 16, 2010) ##
+
+* No changes.
+
+
+## Rails 3.0.2 (November 15, 2010) ##
+
+* No changes
+
+
+## Rails 3.0.1 (October 15, 2010) ##
+
+* No Changes, just a version bump.
+
+
+## Rails 3.0.0 (August 29, 2010) ##
+
+* Added ActiveModel::MassAssignmentSecurity *Eric Chapweske, Josh Kalderimis*
+
+* JSON supports a custom root option: to_json(:root => 'custom') #4515 *Jatinder Singh*
+
+* #new_record? and #destroyed? were removed from ActiveModel::Lint. Use
+ persisted? instead. A model is persisted if it's not a new_record? and it was
+ not destroyed? *MG*
+
+* Added validations reflection in ActiveModel::Validations *JV*
+
+ Model.validators
+ Model.validators_on(:field)
+
+* #to_key was added to ActiveModel::Lint so we can generate DOM IDs for
+ AMo objects with composite keys *MG*
+
+* ActiveModel::Observer#add_observer!
+
+ It has a custom hook to define after_find that should really be in a
+ ActiveRecord::Observer subclass:
+
+ def add_observer!(klass)
+ klass.add_observer(self)
+ klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
+ end
+
+* Change the ActiveModel::Base.include_root_in_json default to true for Rails 3 *DHH*
+
+* Add validates_format_of :without => /regexp/ option. #430 *Elliot Winkler, Peer Allan*
+
+ Example :
+
+ validates_format_of :subdomain, :without => /www|admin|mail/
+
+* Introduce validates_with to encapsulate attribute validations in a class. #2630 *Jeff Dean*
+
+* Extracted from Active Record and Active Resource.
diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE
index 7ad1051066..810daf856c 100644
--- a/activemodel/MIT-LICENSE
+++ b/activemodel/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2011 David Heinemeier Hansson
+Copyright (c) 2004-2012 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 67701bc422..b4565b5881 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -9,10 +9,31 @@ Prior to Rails 3.0, if a plugin or gem developer wanted to have an object
interact with Action Pack helpers, it was required to either copy chunks of
code from Rails, or monkey patch entire helpers to make them handle objects
that did not exactly conform to the Active Record interface. This would result
-in code duplication and fragile applications that broke on upgrades.
+in code duplication and fragile applications that broke on upgrades. Active
+Model solves this by defining an explicit API. You can read more about the
+API in ActiveModel::Lint::Tests.
-Active Model solves this. You can include functionality from the following
-modules:
+Active Model provides a default module that implements the basic API required
+to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>.
+
+ class Person
+ include ActiveModel::Model
+
+ attr_accessor :name, :age
+ validates_presence_of :name
+ end
+
+ person = Person.new(:name => 'bob', :age => '18')
+ person.name # => 'bob'
+ person.age # => '18'
+ person.valid? # => true
+
+It includes model name introspections, conversions, translations and
+validations, resulting in a class suitable to be used with Action Pack.
+See <tt>ActiveModel::Model</tt> for more examples.
+
+Active Model also provides the following functionality to have ORM-like
+behavior out of the box:
* Add attribute magic to objects
@@ -20,7 +41,7 @@ modules:
include ActiveModel::AttributeMethods
attribute_method_prefix 'clear_'
- define_attribute_methods [:name, :age]
+ define_attribute_methods :name, :age
attr_accessor :name, :age
@@ -50,7 +71,7 @@ modules:
This generates +before_create+, +around_create+ and +after_create+
class methods that wrap your create method.
- {Learn more}[link:classes/ActiveModel/CallBacks.html]
+ {Learn more}[link:classes/ActiveModel/Callbacks.html]
* Tracking value changes
@@ -87,18 +108,14 @@ modules:
errors.add(:name, "can not be nil") if name.nil?
end
- def ErrorsPerson.human_attribute_name(attr, options = {})
+ def self.human_attribute_name(attr, options = {})
"Name"
end
-
end
person.errors.full_messages
# => ["Name can not be nil"]
- person.errors.full_messages
- # => ["Name can not be nil"]
-
{Learn more}[link:classes/ActiveModel/Errors.html]
* Model name introspection
@@ -118,6 +135,16 @@ modules:
pattern in a Rails App and take advantage of all the standard observer
functions.
+ class PersonObserver < ActiveModel::Observer
+ def after_create(person)
+ person.logger.info("New person added!")
+ end
+
+ def after_destroy(person)
+ person.logger.warn("Person with an id of #{person.id} was destroyed!")
+ end
+ end
+
{Learn more}[link:classes/ActiveModel/Observer.html]
* Making objects serializable
@@ -163,7 +190,7 @@ modules:
* Custom validators
- class Person
+ class ValidatorPerson
include ActiveModel::Validations
validates_with HasNameValidator
attr_accessor :name
@@ -171,7 +198,7 @@ modules:
class HasNameValidator < ActiveModel::Validator
def validate(record)
- record.errors[:name] = "must exist" if record.name.blank?
+ record.errors[:name] = "must exist" if record.name.blank?
end
end
@@ -182,7 +209,7 @@ modules:
p.valid? # => true
{Learn more}[link:classes/ActiveModel/Validator.html]
-
+
== Download and installation
@@ -197,7 +224,9 @@ Source code can be downloaded as part of the Rails project on GitHub
== License
-Active Model is released under the MIT license.
+Active Model is released under the MIT license:
+
+* http://www.opensource.org/licenses/MIT
== Support
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index c4b020196d..fc5aaf9f8f 100755..100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -8,6 +8,7 @@ Rake::TestTask.new do |t|
t.libs << "test"
t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort
t.warning = true
+ t.verbose = true
end
namespace :test do
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 260ad01b65..f2d004fb0a 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -5,18 +5,17 @@ Gem::Specification.new do |s|
s.name = 'activemodel'
s.version = version
s.summary = 'A toolkit for building modeling frameworks (part of Rails).'
- s.description = 'A toolkit for building modeling frameworks like Active Record and Active Resource. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.'
+ s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.'
- s.required_ruby_version = '>= 1.8.7'
+ s.required_ruby_version = '>= 1.9.3'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
+ s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
s.add_dependency('activesupport', version)
s.add_dependency('builder', '~> 3.0.0')
- s.add_dependency('i18n', '~> 0.6')
end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index d0e2a6f39c..ded1b752df 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2011 David Heinemeier Hansson
+# Copyright (c) 2004-2012 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -21,8 +21,6 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require 'active_support'
require 'active_model/version'
@@ -32,12 +30,14 @@ module ActiveModel
autoload :AttributeMethods
autoload :BlockValidator, 'active_model/validator'
autoload :Callbacks
+ autoload :Configuration
autoload :Conversion
autoload :Dirty
autoload :EachValidator, 'active_model/validator'
autoload :Errors
autoload :Lint
autoload :MassAssignmentSecurity
+ autoload :Model
autoload :Name, 'active_model/naming'
autoload :Naming
autoload :Observer, 'active_model/observing'
@@ -57,5 +57,6 @@ module ActiveModel
end
end
-require 'active_support/i18n'
-I18n.load_path << File.dirname(__FILE__) + '/active_model/locale/en.yml'
+ActiveSupport.on_load(:i18n) do
+ I18n.load_path << File.dirname(__FILE__) + '/active_model/locale/en.yml'
+end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index ef0b95424e..99918fdb96 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -28,7 +28,7 @@ module ActiveModel
# attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
# attribute_method_suffix '_contrived?'
# attribute_method_prefix 'clear_'
- # define_attribute_methods ['name']
+ # define_attribute_methods 'name'
#
# attr_accessor :name
#
@@ -57,72 +57,16 @@ module ActiveModel
module AttributeMethods
extend ActiveSupport::Concern
- COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
+ NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
+ CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
included do
- class_attribute :attribute_method_matchers, :instance_writer => false
+ extend ActiveModel::Configuration
+ config_attribute :attribute_method_matchers
self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
end
module ClassMethods
- # Defines an "attribute" method (like +inheritance_column+ or +table_name+).
- # A new (class) method will be created with the given name. If a value is
- # specified, the new method will return that value (as a string).
- # Otherwise, the given block will be used to compute the value of the
- # method.
- #
- # The original method will be aliased, with the new name being prefixed
- # with "original_". This allows the new method to access the original
- # value.
- #
- # Example:
- #
- # class Person
- #
- # include ActiveModel::AttributeMethods
- #
- # cattr_accessor :primary_key
- # cattr_accessor :inheritance_column
- #
- # define_attr_method :primary_key, "sysid"
- # define_attr_method( :inheritance_column ) do
- # original_inheritance_column + "_id"
- # end
- #
- # end
- #
- # Provides you with:
- #
- # Person.primary_key
- # # => "sysid"
- # Person.inheritance_column = 'address'
- # Person.inheritance_column
- # # => 'address_id'
- def define_attr_method(name, value=nil, &block)
- sing = singleton_class
- sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
- if method_defined?('original_#{name}')
- undef :'original_#{name}'
- end
- alias_method :'original_#{name}', :'#{name}'
- eorb
- if block_given?
- sing.send :define_method, name, &block
- else
- # If we can compile the method name, do it. Otherwise use define_method.
- # This is an important *optimization*, please don't change it. define_method
- # has slower dispatch and consumes more memory.
- if name =~ COMPILABLE_REGEXP
- sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end
- RUBY
- else
- value = value.to_s if value
- sing.send(:define_method, name) { value }
- end
- end
- end
-
# Declares a method available for all attributes with the given prefix.
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
#
@@ -142,7 +86,7 @@ module ActiveModel
# include ActiveModel::AttributeMethods
# attr_accessor :name
# attribute_method_prefix 'clear_'
- # define_attribute_methods [:name]
+ # define_attribute_methods :name
#
# private
#
@@ -180,7 +124,7 @@ module ActiveModel
# include ActiveModel::AttributeMethods
# attr_accessor :name
# attribute_method_suffix '_short?'
- # define_attribute_methods [:name]
+ # define_attribute_methods :name
#
# private
#
@@ -218,7 +162,7 @@ module ActiveModel
# include ActiveModel::AttributeMethods
# attr_accessor :name
# attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
- # define_attribute_methods [:name]
+ # define_attribute_methods :name
#
# private
#
@@ -236,22 +180,23 @@ module ActiveModel
undefine_attribute_methods
end
+
+ # Allows you to make aliases for attributes.
+ #
+ # class Person
+ # attr_accessor :name
+ # alias_attribute :nickname, :name
+ # end
+ #
+ # person = Person.new
+ # person.nickname = "Bob"
+ # person.nickname # => "Bob"
+ # person.name # => "Bob"
def alias_attribute(new_name, old_name)
attribute_method_matchers.each do |matcher|
matcher_new = matcher.method_name(new_name).to_s
matcher_old = matcher.method_name(old_name).to_s
-
- if matcher_new =~ COMPILABLE_REGEXP && matcher_old =~ COMPILABLE_REGEXP
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{matcher_new}(*args)
- send(:#{matcher_old}, *args)
- end
- RUBY
- else
- define_method(matcher_new) do |*args|
- send(matcher_old, *args)
- end
- end
+ define_optimized_call self, matcher_new, matcher_old
end
end
@@ -271,7 +216,7 @@ module ActiveModel
# # Call to define_attribute_methods must appear after the
# # attribute_method_prefix, attribute_method_suffix or
# # attribute_method_affix declares.
- # define_attribute_methods [:name, :age, :address]
+ # define_attribute_methods :name, :age, :address
#
# private
#
@@ -279,8 +224,8 @@ module ActiveModel
# ...
# end
# end
- def define_attribute_methods(attr_names)
- attr_names.each { |attr_name| define_attribute_method(attr_name) }
+ def define_attribute_methods(*attr_names)
+ attr_names.flatten.each { |attr_name| define_attribute_method(attr_name) }
end
def define_attribute_method(attr_name)
@@ -290,20 +235,10 @@ module ActiveModel
unless instance_method_already_implemented?(method_name)
generate_method = "define_method_#{matcher.method_missing_target}"
- if respond_to?(generate_method)
+ if respond_to?(generate_method, true)
send(generate_method, attr_name)
else
- if method_name =~ COMPILABLE_REGEXP
- defn = "def #{method_name}(*args)"
- else
- defn = "define_method(:'#{method_name}') do |*args|"
- end
-
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
- #{defn}
- send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
- end
- RUBY
+ define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
end
end
end
@@ -320,11 +255,7 @@ module ActiveModel
# Returns true if the attribute methods defined have been generated.
def generated_attribute_methods #:nodoc:
- @generated_attribute_methods ||= begin
- mod = Module.new
- include mod
- mod
- end
+ @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
end
protected
@@ -342,11 +273,11 @@ module ActiveModel
# used to alleviate the GC, which ultimately also speeds up the app
# significantly (in our case our test suite finishes 10% faster with
# this cache).
- def attribute_method_matchers_cache
+ def attribute_method_matchers_cache #:nodoc:
@attribute_method_matchers_cache ||= {}
end
- def attribute_method_matcher(method_name)
+ def attribute_method_matcher(method_name) #:nodoc:
if attribute_method_matchers_cache.key?(method_name)
attribute_method_matchers_cache[method_name]
else
@@ -359,6 +290,31 @@ module ActiveModel
end
end
+ # Define a method `name` in `mod` that dispatches to `send`
+ # using the given `extra` args. This fallbacks `define_method`
+ # and `send` if the given names cannot be compiled.
+ def define_optimized_call(mod, name, send, *extra) #:nodoc:
+ if name =~ NAME_COMPILABLE_REGEXP
+ defn = "def #{name}(*args)"
+ else
+ defn = "define_method(:'#{name}') do |*args|"
+ end
+
+ extra = (extra.map(&:inspect) << "*args").join(", ")
+
+ if send =~ CALL_COMPILABLE_REGEXP
+ target = "#{send}(#{extra})"
+ else
+ target = "send(:'#{send}', #{extra})"
+ end
+
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ #{defn}
+ #{target}
+ end
+ RUBY
+ end
+
class AttributeMethodMatcher
attr_reader :prefix, :suffix, :method_missing_target
@@ -377,14 +333,14 @@ module ActiveModel
end
@prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
- @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
+ @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@method_name = "#{prefix}%s#{suffix}"
end
def match(method_name)
if @regex =~ method_name
- AttributeMethodMatch.new(method_missing_target, $2, method_name)
+ AttributeMethodMatch.new(method_missing_target, $1, method_name)
else
nil
end
@@ -446,7 +402,7 @@ module ActiveModel
protected
def attribute_method?(attr_name)
- attributes.include?(attr_name)
+ respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)
end
private
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 37d0c9a0b9..ebb4b51aa3 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/callbacks'
module ActiveModel
@@ -41,7 +40,7 @@ module ActiveModel
# You can choose not to have all three callbacks by passing a hash to the
# define_model_callbacks method.
#
- # define_model_callbacks :create, :only => :after, :before
+ # define_model_callbacks :create, :only => [:after, :before]
#
# Would only create the after_create and before_create callback methods in your
# class.
@@ -89,11 +88,12 @@ module ActiveModel
options = callbacks.extract_options!
options = {
:terminator => "result == false",
+ :skip_after_callbacks_if_terminated => true,
:scope => [:kind, :name],
:only => [:before, :around, :after]
}.merge(options)
- types = Array.wrap(options.delete(:only))
+ types = Array(options.delete(:only))
callbacks.each do |callback|
define_callbacks(callback, options)
@@ -125,7 +125,7 @@ module ActiveModel
def self.after_#{callback}(*args, &block)
options = args.extract_options!
options[:prepend] = true
- options[:if] = Array.wrap(options[:if]) << "!halted && value != false"
+ options[:if] = Array(options[:if]) << "value != false"
set_callback(:#{callback}, :after, *(args << options), &block)
end
CALLBACK
diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb
new file mode 100644
index 0000000000..ba5a6a2075
--- /dev/null
+++ b/activemodel/lib/active_model/configuration.rb
@@ -0,0 +1,134 @@
+require 'active_support/concern'
+require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/class/attribute_accessors'
+
+module ActiveModel
+ # This API is for Rails' internal use and is not currently considered 'public', so
+ # it may change in the future without warning.
+ #
+ # It creates configuration attributes that can be inherited from a module down
+ # to a class that includes the module. E.g.
+ #
+ # module MyModel
+ # extend ActiveModel::Configuration
+ # config_attribute :awesome
+ # self.awesome = true
+ # end
+ #
+ # class Post
+ # include MyModel
+ # end
+ #
+ # Post.awesome # => true
+ #
+ # Post.awesome = false
+ # Post.awesome # => false
+ # MyModel.awesome # => true
+ #
+ # We assume that the module will have a ClassMethods submodule containing methods
+ # to be transferred to the including class' singleton class.
+ #
+ # Config options can also be defined directly on a class:
+ #
+ # class Post
+ # extend ActiveModel::Configuration
+ # config_attribute :awesome
+ # end
+ #
+ # So this allows us to define a module that doesn't care about whether it is being
+ # included in a class or a module:
+ #
+ # module Awesomeness
+ # extend ActiveSupport::Concern
+ #
+ # included do
+ # extend ActiveModel::Configuration
+ # config_attribute :awesome
+ # self.awesome = true
+ # end
+ # end
+ #
+ # class Post
+ # include Awesomeness
+ # end
+ #
+ # module AwesomeModel
+ # include Awesomeness
+ # end
+ module Configuration #:nodoc:
+ def config_attribute(name, options = {})
+ klass = self.is_a?(Class) ? ClassAttribute : ModuleAttribute
+ klass.new(self, name, options).define
+ end
+
+ class Attribute
+ attr_reader :host, :name, :options
+
+ def initialize(host, name, options)
+ @host, @name, @options = host, name, options
+ end
+
+ def instance_writer?
+ options.fetch(:instance_writer, false)
+ end
+ end
+
+ class ClassAttribute < Attribute
+ def define
+ if options[:global]
+ host.cattr_accessor name, :instance_writer => instance_writer?
+ else
+ host.class_attribute name, :instance_writer => instance_writer?
+ end
+ end
+ end
+
+ class ModuleAttribute < Attribute
+ def class_methods
+ @class_methods ||= begin
+ if host.const_defined?(:ClassMethods, false)
+ host.const_get(:ClassMethods)
+ else
+ host.const_set(:ClassMethods, Module.new)
+ end
+ end
+ end
+
+ def define
+ host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ attr_accessor :#{name}
+ def #{name}?; !!#{name}; end
+ CODE
+
+ name, host = self.name, self.host
+
+ class_methods.class_eval do
+ define_method(name) { host.send(name) }
+ define_method("#{name}?") { !!send(name) }
+ end
+
+ host.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}; defined?(@#{name}) ? @#{name} : self.class.#{name}; end
+ def #{name}?; !!#{name}; end
+ CODE
+
+ if options[:global]
+ class_methods.class_eval do
+ define_method("#{name}=") { |val| host.send("#{name}=", val) }
+ end
+ else
+ class_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}=(val)
+ singleton_class.class_eval do
+ remove_possible_method(:#{name})
+ define_method(:#{name}) { val }
+ end
+ end
+ CODE
+ end
+
+ host.send(:attr_writer, name) if instance_writer?
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 80a3ba51c3..d7f30f0920 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -21,7 +21,7 @@ module ActiveModel
# cm.to_model == self # => true
# cm.to_key # => nil
# cm.to_param # => nil
- # cm.to_path # => "contact_messages/contact_message"
+ # cm.to_partial_path # => "contact_messages/contact_message"
#
module Conversion
extend ActiveSupport::Concern
@@ -39,11 +39,9 @@ module ActiveModel
# Returns an Enumerable of all key attributes if any is set, regardless
# if the object is persisted or not.
- #
- # Note the default implementation uses persisted? just because all objects
- # in Ruby 1.8.x responds to <tt>:id</tt>.
def to_key
- persisted? ? [id] : nil
+ key = respond_to?(:id) && id
+ key ? [key] : nil
end
# Returns a string representing the object's key suitable for use in URLs,
@@ -59,7 +57,7 @@ module ActiveModel
end
module ClassMethods #:nodoc:
- # Provide a class level cache for the to_path. This is an
+ # Provide a class level cache for #to_partial_path. This is an
# internal method and should not be accessed directly.
def _to_partial_path #:nodoc:
@_to_partial_path ||= begin
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 166cccf161..3f2fd12db7 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -1,6 +1,7 @@
require 'active_model/attribute_methods'
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
+require 'active_support/core_ext/object/blank'
module ActiveModel
# == Active Model Dirty
@@ -29,7 +30,7 @@ module ActiveModel
#
# include ActiveModel::Dirty
#
- # define_attribute_methods [:name]
+ # define_attribute_methods :name
#
# def name
# @name
@@ -82,6 +83,7 @@ module ActiveModel
# in-place attributes.
#
# person.name_will_change!
+ # person.name_change # => ['Bill', 'Bill']
# person.name << 'y'
# person.name_change # => ['Bill', 'Billy']
module Dirty
@@ -98,7 +100,7 @@ module ActiveModel
# person.name = 'bob'
# person.changed? # => true
def changed?
- !changed_attributes.empty?
+ changed_attributes.present?
end
# List of attributes with unsaved changes.
@@ -150,13 +152,15 @@ module ActiveModel
# Handle <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
+ return if attribute_changed?(attr)
+
begin
value = __send__(attr)
value = value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
end
- changed_attributes[attr] = value unless changed_attributes.include?(attr)
+ changed_attributes[attr] = value
end
# Handle <tt>reset_*!</tt> for +method_missing+.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index f90030641d..aba6618b56 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -1,16 +1,13 @@
# -*- coding: utf-8 -*-
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/hash/reverse_merge'
-require 'active_support/ordered_hash'
module ActiveModel
# == Active Model Errors
#
- # Provides a modified +OrderedHash+ that you can include in your object
+ # Provides a modified +Hash+ that you can include in your object
# for handling error messages and interacting with Action Pack helpers.
#
# A minimal implementation could be:
@@ -76,7 +73,12 @@ module ActiveModel
# end
def initialize(base)
@base = base
- @messages = ActiveSupport::OrderedHash.new
+ @messages = {}
+ end
+
+ def initialize_dup(other)
+ @messages = other.messages.dup
+ super
end
# Clear the messages
@@ -100,6 +102,11 @@ module ActiveModel
messages[key] = value
end
+ # Delete messages for +key+
+ def delete(key)
+ messages.delete(key)
+ end
+
# When passed a symbol or a name of a method, returns an array of errors
# for the method.
#
@@ -114,7 +121,7 @@ module ActiveModel
# p.errors[:name] = "must be set"
# p.errors[:name] # => ['must be set']
def []=(attribute, error)
- self[attribute.to_sym] << error
+ self[attribute] << error
end
# Iterates through each error key, value pair in the error messages hash.
@@ -122,12 +129,12 @@ module ActiveModel
# has more than one error message, yields once for each error message.
#
# p.errors.add(:name, "can't be blank")
- # p.errors.each do |attribute, errors_array|
+ # p.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# end
#
# p.errors.add(:name, "must be specified")
- # p.errors.each do |attribute, errors_array|
+ # p.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# # then yield :name and "must be specified"
# end
@@ -176,8 +183,9 @@ module ActiveModel
end
# Returns true if no errors are found, false otherwise.
+ # If the error message is a string it can be empty.
def empty?
- all? { |k, v| v && v.empty? }
+ all? { |k, v| v && v.empty? && !v.is_a?(String) }
end
alias_method :blank?, :empty?
@@ -193,35 +201,39 @@ module ActiveModel
# # <error>name must be specified</error>
# # </errors>
def to_xml(options={})
- to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
+ to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options))
end
- # Returns an ActiveSupport::OrderedHash that can be used as the JSON representation for this object.
+ # Returns an Hash that can be used as the JSON representation for this object.
+ # Options:
+ # * <tt>:full_messages</tt> - determines if json object should contain
+ # full messages or not. Default: <tt>false</tt>.
def as_json(options=nil)
- to_hash
+ to_hash(options && options[:full_messages])
end
- def to_hash
- messages.dup
+ def to_hash(full_messages = false)
+ if full_messages
+ messages = {}
+ self.messages.each do |attribute, array|
+ messages[attribute] = array.map { |message| full_message(attribute, message) }
+ end
+ messages
+ else
+ self.messages.dup
+ end
end
- # Adds +message+ to the error messages on +attribute+, which will be returned on a call to
- # <tt>on(attribute)</tt> for the same attribute. More than one error can be added to the same
- # +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
+ # Adds +message+ to the error messages on +attribute+. More than one error can be added to the same
+ # +attribute+.
# If no +message+ is supplied, <tt>:invalid</tt> is assumed.
#
- # If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+).
+ # If +message+ is a symbol, it will be translated using the appropriate scope (see +generate_message+).
# If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
def add(attribute, message = nil, options = {})
- message ||= :invalid
-
- if message.is_a?(Symbol)
- message = generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
- elsif message.is_a?(Proc)
- message = message.call
- end
+ message = normalize_message(attribute, message, options)
if options[:strict]
- raise ActiveModel::StrictValidationFailed, message
+ raise ActiveModel::StrictValidationFailed, full_message(attribute, message)
end
self[attribute] << message
@@ -244,6 +256,15 @@ module ActiveModel
end
end
+ # Returns true if an error on the attribute with the given message is present, false otherwise.
+ # +message+ is treated the same as for +add+.
+ # p.errors.add :name, :blank
+ # p.errors.added? :name, :blank # => true
+ def added?(attribute, message = nil, options = {})
+ message = normalize_message(attribute, message, options)
+ self[attribute].include? message
+ end
+
# Returns all the full error messages in an array.
#
# class Company
@@ -264,7 +285,7 @@ module ActiveModel
# "Name is invalid"
def full_message(attribute, message)
return message if attribute == :base
- attr_name = attribute.to_s.gsub('.', '_').humanize
+ attr_name = attribute.to_s.tr('.', '_').humanize
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
I18n.t(:"errors.format", {
:default => "%{attribute} %{message}",
@@ -325,10 +346,24 @@ module ActiveModel
:model => @base.class.model_name.human,
:attribute => @base.class.human_attribute_name(attribute),
:value => value
- }.merge(options)
+ }.merge!(options)
I18n.translate(key, options)
end
+
+ private
+ def normalize_message(attribute, message, options)
+ message ||= :invalid
+
+ case message
+ when Symbol
+ generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
+ when Proc
+ message.call
+ else
+ message
+ end
+ end
end
class StrictValidationFailed < StandardError
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index bfe7ea1869..88b730626c 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -3,9 +3,13 @@ module ActiveModel
# == Active Model Lint Tests
#
# You can test whether an object is compliant with the Active Model API by
- # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will include
- # tests that tell you whether your object is fully compliant, or if not,
- # which aspects of the API are not implemented.
+ # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will
+ # include tests that tell you whether your object is fully compliant,
+ # or if not, which aspects of the API are not implemented.
+ #
+ # Note an object is not required to implement all APIs in order to work
+ # with Action Pack. This module only intends to provide guidance in case
+ # you want all features out of the box.
#
# These tests do not attempt to determine the semantic correctness of the
# returned values. For instance, you could implement valid? to always
@@ -19,7 +23,8 @@ module ActiveModel
# == Responds to <tt>to_key</tt>
#
# Returns an Enumerable of all (primary) key attributes
- # or nil if model.persisted? is false
+ # or nil if model.persisted? is false. This is used by
+ # dom_id to generate unique ids for the object.
def test_to_key
assert model.respond_to?(:to_key), "The model should respond to to_key"
def model.persisted?() false end
@@ -53,22 +58,13 @@ module ActiveModel
assert_kind_of String, model.to_partial_path
end
- # == Responds to <tt>valid?</tt>
- #
- # Returns a boolean that specifies whether the object is in a valid or invalid
- # state.
- def test_valid?
- assert model.respond_to?(:valid?), "The model should respond to valid?"
- assert_boolean model.valid?, "valid?"
- end
-
# == Responds to <tt>persisted?</tt>
#
# Returns a boolean that specifies whether the object has been persisted yet.
# This is used when calculating the URL for an object. If the object is
- # not persisted, a form for that object, for instance, will be POSTed to the
- # collection. If it is persisted, a form for the object will be PUT to the
- # URL for the object.
+ # not persisted, a form for that object, for instance, will route to the
+ # create action. If it is persisted, a form for the object will routes to
+ # the update action.
def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
assert_boolean model.persisted?, "persisted?"
@@ -82,33 +78,23 @@ module ActiveModel
def test_model_naming
assert model.class.respond_to?(:model_name), "The model should respond to model_name"
model_name = model.class.model_name
- assert_kind_of String, model_name
- assert_kind_of String, model_name.human
- assert_kind_of String, model_name.singular
- assert_kind_of String, model_name.plural
+ assert model_name.respond_to?(:to_str)
+ assert model_name.human.respond_to?(:to_str)
+ assert model_name.singular.respond_to?(:to_str)
+ assert model_name.plural.respond_to?(:to_str)
end
# == Errors Testing
#
- # Returns an object that has :[] and :full_messages defined on it. See below
- # for more details.
- #
- # Returns an Array of Strings that are the errors for the attribute in
- # question. If localization is used, the Strings should be localized
- # for the current locale. If no error is present, this method should
- # return an empty Array.
+ # Returns an object that implements [](attribute) defined which returns an
+ # Array of Strings that are the errors for the attribute in question.
+ # If localization is used, the Strings should be localized for the current
+ # locale. If no error is present, this method should return an empty Array.
def test_errors_aref
assert model.respond_to?(:errors), "The model should respond to errors"
assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
end
- # Returns an Array of all error messages for the object. Each message
- # should contain information about the field, if applicable.
- def test_errors_full_messages
- assert model.respond_to?(:errors), "The model should respond to errors"
- assert model.errors.full_messages.is_a?(Array), "errors#full_messages should return an Array"
- end
-
private
def model
assert @model.respond_to?(:to_model), "The object should respond_to to_model"
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index 44425b4a28..d17848c861 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -9,7 +9,7 @@ en:
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
- confirmation: "doesn't match confirmation"
+ confirmation: "doesn't match %{attribute}"
accepted: "must be accepted"
empty: "can't be empty"
blank: "can't be blank"
@@ -23,5 +23,6 @@ en:
equal_to: "must be equal to %{count}"
less_than: "must be less than %{count}"
less_than_or_equal_to: "must be less than or equal to %{count}"
+ other_than: "must be other than %{count}"
odd: "must be odd"
even: "must be even"
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index 3f9feb7631..893fbf92c3 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/string/inflections'
-require 'active_support/core_ext/array/wrap'
require 'active_model/mass_assignment_security/permission_set'
require 'active_model/mass_assignment_security/sanitizer'
@@ -10,11 +9,13 @@ module ActiveModel
extend ActiveSupport::Concern
included do
- class_attribute :_accessible_attributes
- class_attribute :_protected_attributes
- class_attribute :_active_authorizer
+ extend ActiveModel::Configuration
- class_attribute :_mass_assignment_sanitizer
+ config_attribute :_accessible_attributes
+ config_attribute :_protected_attributes
+ config_attribute :_active_authorizer
+
+ config_attribute :_mass_assignment_sanitizer
self.mass_assignment_sanitizer = :logger
end
@@ -56,7 +57,7 @@ module ActiveModel
# You can specify your own sanitizer object eg. MySanitizer.new.
# See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for example implementation.
#
- #
+ #
module ClassMethods
# Attributes named in this macro are protected from mass-assignment
# whenever attributes are sanitized before assignment. A role for the
@@ -71,10 +72,11 @@ module ActiveModel
# class Customer
# include ActiveModel::MassAssignmentSecurity
#
- # attr_accessor :name, :credit_rating
+ # attr_accessor :name, :email, :logins_count
#
- # attr_protected :credit_rating, :last_login
- # attr_protected :last_login, :as => :admin
+ # attr_protected :logins_count
+ # # Suppose that admin can not change email for customer
+ # attr_protected :logins_count, :email, :as => :admin
#
# def assign_attributes(values, options = {})
# sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
@@ -83,37 +85,38 @@ module ActiveModel
# end
# end
#
- # When using the :default role :
+ # When using the :default role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
+ # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5 }, :as => :default)
# customer.name # => "David"
- # customer.credit_rating # => nil
- # customer.last_login # => nil
+ # customer.email # => "a@b.com"
+ # customer.logins_count # => nil
#
- # customer.credit_rating = "Average"
- # customer.credit_rating # => "Average"
- #
- # And using the :admin role :
+ # And using the :admin role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
+ # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5}, :as => :admin)
# customer.name # => "David"
- # customer.credit_rating # => "Excellent"
- # customer.last_login # => nil
+ # customer.email # => nil
+ # customer.logins_count # => nil
+ #
+ # customer.email = "c@d.com"
+ # customer.email # => "c@d.com"
#
# To start from an all-closed default and enable attributes as needed,
# have a look at +attr_accessible+.
#
- # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_protected+
- # to sanitize attributes won't provide sufficient protection.
+ # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of
+ # +attr_protected+ to sanitize attributes provides basically the same
+ # functionality, but it makes a bit tricky to deal with nested attributes.
def attr_protected(*args)
options = args.extract_options!
role = options[:as] || :default
self._protected_attributes = protected_attributes_configs.dup
- Array.wrap(role).each do |name|
+ Array(role).each do |name|
self._protected_attributes[name] = self.protected_attributes(name) + args
end
@@ -150,7 +153,7 @@ module ActiveModel
# end
# end
#
- # When using the :default role :
+ # When using the :default role:
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
@@ -160,22 +163,23 @@ module ActiveModel
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
- # And using the :admin role :
+ # And using the :admin role:
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
# customer.name # => "David"
# customer.credit_rating # => "Excellent"
#
- # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_accessible+
- # to sanitize attributes won't provide sufficient protection.
+ # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of
+ # +attr_accessible+ to sanitize attributes provides basically the same
+ # functionality, but it makes a bit tricky to deal with nested attributes.
def attr_accessible(*args)
options = args.extract_options!
role = options[:as] || :default
self._accessible_attributes = accessible_attributes_configs.dup
- Array.wrap(role).each do |name|
+ Array(role).each do |name|
self._accessible_attributes[name] = self.accessible_attributes(name) + args
end
@@ -224,12 +228,12 @@ module ActiveModel
protected
- def sanitize_for_mass_assignment(attributes, role = :default)
- _mass_assignment_sanitizer.sanitize(attributes, mass_assignment_authorizer(role))
+ def sanitize_for_mass_assignment(attributes, role = nil)
+ _mass_assignment_sanitizer.sanitize(self.class, attributes, mass_assignment_authorizer(role))
end
- def mass_assignment_authorizer(role = :default)
- self.class.active_authorizer[role]
+ def mass_assignment_authorizer(role)
+ self.class.active_authorizer[role || :default]
end
end
end
diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
index a1fcdf1a38..9661349503 100644
--- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -13,7 +13,7 @@ module ActiveModel
end
def deny?(key)
- raise NotImplementedError, "#deny?(key) suppose to be overwritten"
+ raise NotImplementedError, "#deny?(key) supposed to be overwritten"
end
protected
diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
index bbdddfb50d..44ce5a489d 100644
--- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
@@ -1,51 +1,63 @@
-require 'active_support/core_ext/module/delegation'
-
module ActiveModel
module MassAssignmentSecurity
class Sanitizer
- def initialize(target=nil)
- end
-
# Returns all attributes not denied by the authorizer.
- def sanitize(attributes, authorizer)
- sanitized_attributes = attributes.reject { |key, value| authorizer.deny?(key) }
- debug_protected_attribute_removal(attributes, sanitized_attributes)
+ def sanitize(klass, attributes, authorizer)
+ rejected = []
+ sanitized_attributes = attributes.reject do |key, value|
+ rejected << key if authorizer.deny?(key)
+ end
+ process_removed_attributes(klass, rejected) unless rejected.empty?
sanitized_attributes
end
protected
- def debug_protected_attribute_removal(attributes, sanitized_attributes)
- removed_keys = attributes.keys - sanitized_attributes.keys
- process_removed_attributes(removed_keys) if removed_keys.any?
- end
-
- def process_removed_attributes(attrs)
+ def process_removed_attributes(klass, attrs)
raise NotImplementedError, "#process_removed_attributes(attrs) suppose to be overwritten"
end
end
class LoggerSanitizer < Sanitizer
- delegate :logger, :to => :@target
-
def initialize(target)
@target = target
- super
+ super()
+ end
+
+ def logger
+ @target.logger
end
def logger?
@target.respond_to?(:logger) && @target.logger
end
- def process_removed_attributes(attrs)
- logger.debug "WARNING: Can't mass-assign protected attributes: #{attrs.join(', ')}" if logger?
+ def backtrace
+ if defined? Rails
+ Rails.backtrace_cleaner.clean(caller)
+ else
+ caller
+ end
+ end
+
+ def process_removed_attributes(klass, attrs)
+ if logger?
+ logger.warn do
+ "WARNING: Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}\n" +
+ backtrace.map { |trace| "\t#{trace}" }.join("\n")
+ end
+ end
end
end
class StrictSanitizer < Sanitizer
- def process_removed_attributes(attrs)
+ def initialize(target = nil)
+ super()
+ end
+
+ def process_removed_attributes(klass, attrs)
return if (attrs - insensitive_attributes).empty?
- raise ActiveModel::MassAssignmentSecurity::Error, "Can't mass-assign protected attributes: #{attrs.join(', ')}"
+ raise ActiveModel::MassAssignmentSecurity::Error.new(klass, attrs)
end
def insensitive_attributes
@@ -54,6 +66,9 @@ module ActiveModel
end
class Error < StandardError
+ def initialize(klass, attrs)
+ super("Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}")
+ end
end
end
end
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
new file mode 100644
index 0000000000..3af95b09b0
--- /dev/null
+++ b/activemodel/lib/active_model/model.rb
@@ -0,0 +1,76 @@
+module ActiveModel
+
+ # == Active Model Basic Model
+ #
+ # Includes the required interface for an object to interact with <tt>ActionPack</tt>,
+ # using different <tt>ActiveModel</tt> modules. It includes model name introspections,
+ # conversions, translations and validations. Besides that, it allows you to
+ # initialize the object with a hash of attributes, pretty much like
+ # <tt>ActiveRecord</tt> does.
+ #
+ # A minimal implementation could be:
+ #
+ # class Person
+ # include ActiveModel::Model
+ # attr_accessor :name, :age
+ # end
+ #
+ # person = Person.new(:name => 'bob', :age => '18')
+ # person.name # => 'bob'
+ # person.age # => 18
+ #
+ # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt> to
+ # return <tt>false</tt>, which is the most common case. You may want to override it
+ # in your class to simulate a different scenario:
+ #
+ # class Person
+ # include ActiveModel::Model
+ # attr_accessor :id, :name
+ #
+ # def persisted?
+ # self.id == 1
+ # end
+ # end
+ #
+ # person = Person.new(:id => 1, :name => 'bob')
+ # person.persisted? # => true
+ #
+ # Also, if for some reason you need to run code on <tt>initialize</tt>, make sure you
+ # call super if you want the attributes hash initialization to happen.
+ #
+ # class Person
+ # include ActiveModel::Model
+ # attr_accessor :id, :name, :omg
+ #
+ # def initialize(attributes={})
+ # super
+ # @omg ||= true
+ # end
+ # end
+ #
+ # person = Person.new(:id => 1, :name => 'bob')
+ # person.omg # => true
+ #
+ # For more detailed information on other functionalities available, please refer
+ # to the specific modules included in <tt>ActiveModel::Model</tt> (see below).
+ module Model
+ def self.included(base)
+ base.class_eval do
+ extend ActiveModel::Naming
+ extend ActiveModel::Translation
+ include ActiveModel::Validations
+ include ActiveModel::Conversion
+ end
+ end
+
+ def initialize(params={})
+ params.each do |attr, value|
+ self.public_send("#{attr}=", value)
+ end if params
+ end
+
+ def persisted?
+ false
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index f16459ede2..2b5fc57a3a 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,30 +1,40 @@
require 'active_support/inflector'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
-require 'active_support/core_ext/module/deprecation'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/object/blank'
module ActiveModel
- class Name < String
- attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key, :i18n_key
+ class Name
+ include Comparable
+
+ attr_reader :singular, :plural, :element, :collection,
+ :singular_route_key, :route_key, :param_key, :i18n_key,
+ :name
+
alias_method :cache_key, :collection
- deprecate :partial_path => "ActiveModel::Name#partial_path is deprecated. Call #to_partial_path on model instances directly instead."
+ delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
+ :to_str, :to => :name
def initialize(klass, namespace = nil, name = nil)
- name ||= klass.name
- super(name)
- @unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace
-
- @klass = klass
- @singular = _singularize(self).freeze
- @plural = ActiveSupport::Inflector.pluralize(@singular).freeze
- @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
- @human = ActiveSupport::Inflector.humanize(@element).freeze
- @collection = ActiveSupport::Inflector.tableize(self).freeze
- @partial_path = "#{@collection}/#{@element}".freeze
- @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
- @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze
- @i18n_key = self.underscore.to_sym
+ @name = name || klass.name
+
+ raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
+
+ @unnamespaced = @name.sub(/^#{namespace.name}::/, '') if namespace
+ @klass = klass
+ @singular = _singularize(@name)
+ @plural = ActiveSupport::Inflector.pluralize(@singular)
+ @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
+ @human = ActiveSupport::Inflector.humanize(@element)
+ @collection = ActiveSupport::Inflector.tableize(@name)
+ @param_key = (namespace ? _singularize(@unnamespaced) : @singular)
+ @i18n_key = @name.underscore.to_sym
+
+ @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
+ @singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
+ @route_key << "_index" if @plural == @singular
end
# Transform the model name into a more humane format, using I18n. By default,
@@ -44,7 +54,7 @@ module ActiveModel
defaults << options[:default] if options[:default]
defaults << @human
- options = {:scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults}.merge(options.except(:default))
+ options = { :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults }.merge!(options.except(:default))
I18n.translate(defaults.shift, options)
end
@@ -68,8 +78,8 @@ module ActiveModel
# BookCover.model_name # => "BookCover"
# BookCover.model_name.human # => "Book cover"
#
- # BookCover.model_name.i18n_key # => "book_cover"
- # BookModule::BookCover.model_name.i18n_key # => "book_module.book_cover"
+ # BookCover.model_name.i18n_key # => :book_cover
+ # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
#
# Providing the functionality that ActiveModel::Naming provides in your object
# is required to pass the Active Model Lint test. So either extending the provided
@@ -79,7 +89,9 @@ module ActiveModel
# used to retrieve all kinds of naming-related information.
def model_name
@_model_name ||= begin
- namespace = self.parents.detect { |n| n.respond_to?(:_railtie) }
+ namespace = self.parents.detect do |n|
+ n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
+ end
ActiveModel::Name.new(self, namespace)
end
end
@@ -112,10 +124,25 @@ module ActiveModel
# namespaced models regarding whether it's inside isolated engine.
#
# For isolated engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> post
+ #
+ # For shared engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> blog_post
+ def self.singular_route_key(record_or_class)
+ model_name_from_record_or_class(record_or_class).singular_route_key
+ end
+
+ # Returns string to use while generating route names. It differs for
+ # namespaced models regarding whether it's inside isolated engine.
+ #
+ # For isolated engine:
# ActiveModel::Naming.route_key(Blog::Post) #=> posts
#
# For shared engine:
# ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
+ #
+ # The route key also considers if the noun is uncountable and, in
+ # such cases, automatically appends _index.
def self.route_key(record_or_class)
model_name_from_record_or_class(record_or_class).route_key
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index 7a910d18e7..f5ea285ccb 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -1,10 +1,11 @@
require 'singleton'
require 'active_model/observer_array'
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/enumerable'
+require 'active_support/deprecation'
+require 'active_support/core_ext/object/try'
require 'active_support/descendants_tracker'
module ActiveModel
@@ -64,33 +65,40 @@ module ActiveModel
# raises an +ArgumentError+ exception.
def add_observer(observer)
unless observer.respond_to? :update
- raise ArgumentError, "observer needs to respond to `update'"
+ raise ArgumentError, "observer needs to respond to 'update'"
end
observer_instances << observer
end
# Notify list of observers of a change.
- def notify_observers(*arg)
- observer_instances.each { |observer| observer.update(*arg) }
+ def notify_observers(*args)
+ observer_instances.each { |observer| observer.update(*args) }
end
# Total number of observers.
- def count_observers
+ def observers_count
observer_instances.size
end
+ def count_observers
+ msg = "count_observers is deprecated in favor of observers_count"
+ ActiveSupport::Deprecation.warn(msg)
+ observers_count
+ end
+
protected
def instantiate_observer(observer) #:nodoc:
# string/symbol
if observer.respond_to?(:to_sym)
- observer.to_s.camelize.constantize.instance
- elsif observer.respond_to?(:instance)
+ observer = observer.to_s.camelize.constantize
+ end
+ if observer.respond_to?(:instance)
observer.instance
else
raise ArgumentError,
- "#{observer} must be a lowercase, underscored class name (or an " +
- "instance of the class itself) responding to the instance " +
- "method. Example: Person.observers = :big_brother # calls " +
+ "#{observer} must be a lowercase, underscored class name (or " +
+ "the class itself) responding to the method :instance. " +
+ "Example: Person.observers = :big_brother # calls " +
"BigBrother.instance"
end
end
@@ -102,17 +110,24 @@ module ActiveModel
end
end
- private
- # Fires notifications to model's observers
- #
- # def save
- # notify_observers(:before_save)
- # ...
- # notify_observers(:after_save)
- # end
- def notify_observers(method)
- self.class.notify_observers(method, self)
- end
+ # Fires notifications to model's observers
+ #
+ # def save
+ # notify_observers(:before_save)
+ # ...
+ # notify_observers(:after_save)
+ # end
+ #
+ # Custom notifications can be sent in a similar fashion:
+ #
+ # notify_observers(:custom_notification, :foo)
+ #
+ # This will call +custom_notification+, passing as arguments
+ # the current object and :foo.
+ #
+ def notify_observers(method, *extra_args)
+ self.class.notify_observers(method, self, *extra_args)
+ end
end
# == Active Model Observers
@@ -187,8 +202,7 @@ module ActiveModel
def observe(*models)
models.flatten!
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
- remove_possible_method(:observed_classes)
- define_method(:observed_classes) { models }
+ singleton_class.redefine_method(:observed_classes) { models }
end
# Returns an array of Classes to observe.
@@ -201,21 +215,18 @@ module ActiveModel
# end
# end
def observed_classes
- Array.wrap(observed_class)
+ Array(observed_class)
end
# The class observed by default is inferred from the observer's class name:
# assert_equal Person, PersonObserver.observed_class
def observed_class
- if observed_class_name = name[/(.*)Observer/, 1]
- observed_class_name.constantize
- else
- nil
- end
+ name[/(.*)Observer/, 1].try :constantize
end
end
# Start observing the declared classes and their subclasses.
+ # Called automatically by the instance method.
def initialize
observed_classes.each { |klass| add_observer!(klass) }
end
@@ -226,10 +237,10 @@ module ActiveModel
# Send observed_method(object) if the method exists and
# the observer is enabled for the given object's class.
- def update(observed_method, object, &block) #:nodoc:
+ def update(observed_method, object, *extra_args, &block) #:nodoc:
return unless respond_to?(observed_method)
return if disabled_for?(object)
- send(observed_method, object, &block)
+ send(observed_method, object, *extra_args, &block)
end
# Special method sent by the observed class when it is inherited.
@@ -244,6 +255,7 @@ module ActiveModel
klass.add_observer(self)
end
+ # Returns true if notifications are disabled for this object.
def disabled_for?(object)
klass = object.class
return false unless klass.respond_to?(:observers)
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index db78864c67..3eab745c89 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -6,8 +6,9 @@ module ActiveModel
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a password_digest attribute.
#
- # Validations for presence of password, confirmation of password (using
+ # Validations for presence of password on create, confirmation of password (using
# a "password_confirmation" attribute) are automatically added.
+ # If you wish to turn off validations, pass 'validations: false' as an argument.
# You can add more validations by hand if need be.
#
# You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use has_secure_password:
@@ -29,18 +30,22 @@ module ActiveModel
# user.save # => true
# user.authenticate("notright") # => false
# user.authenticate("mUc3m00RsqyRe") # => user
- # User.find_by_name("david").try(:authenticate, "notright") # => nil
+ # User.find_by_name("david").try(:authenticate, "notright") # => false
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
- def has_secure_password
+ def has_secure_password(options = {})
# Load bcrypt-ruby only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework) being dependent on a binary library.
gem 'bcrypt-ruby', '~> 3.0.0'
require 'bcrypt'
attr_reader :password
-
- validates_confirmation_of :password
- validates_presence_of :password_digest
+
+ if options.fetch(:validations, true)
+ validates_confirmation_of :password
+ validates_presence_of :password, :on => :create
+ end
+
+ before_create { raise "Password digest missing on new record" if password_digest.blank? }
include InstanceMethodsOnActivation
@@ -55,17 +60,14 @@ module ActiveModel
module InstanceMethodsOnActivation
# Returns self if the password is correct, otherwise false.
def authenticate(unencrypted_password)
- if BCrypt::Password.new(password_digest) == unencrypted_password
- self
- else
- false
- end
+ BCrypt::Password.new(password_digest) == unencrypted_password && self
end
- # Encrypts the password into the password_digest attribute.
+ # Encrypts the password into the password_digest attribute, only if the
+ # new password is not blank.
def password=(unencrypted_password)
- @password = unencrypted_password
unless unencrypted_password.blank?
+ @password = unencrypted_password
self.password_digest = BCrypt::Password.create(unencrypted_password)
end
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index a4b58ab456..6d8fd21814 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -1,7 +1,5 @@
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/array/wrap'
-
module ActiveModel
# == Active Model Serialization
@@ -11,15 +9,13 @@ module ActiveModel
# A minimal implementation could be:
#
# class Person
- #
# include ActiveModel::Serialization
#
# attr_accessor :name
#
# def attributes
- # {'name' => name}
+ # {'name' => nil}
# end
- #
# end
#
# Which would provide you with:
@@ -29,27 +25,29 @@ module ActiveModel
# person.name = "Bob"
# person.serializable_hash # => {"name"=>"Bob"}
#
- # You need to declare some sort of attributes hash which contains the attributes
- # you want to serialize and their current value.
+ # You need to declare an attributes hash which contains the attributes
+ # you want to serialize. Attributes must be strings, not symbols.
+ # When called, serializable hash will use
+ # instance methods that match the name of the attributes hash's keys.
+ # In order to override this behavior, take a look at the private
+ # method +read_attribute_for_serialization+.
#
# Most of the time though, you will want to include the JSON or XML
# serializations. Both of these modules automatically include the
- # ActiveModel::Serialization module, so there is no need to explicitly
+ # <tt>ActiveModel::Serialization</tt> module, so there is no need to explicitly
# include it.
#
- # So a minimal implementation including XML and JSON would be:
+ # A minimal implementation including XML and JSON would be:
#
# class Person
- #
# include ActiveModel::Serializers::JSON
# include ActiveModel::Serializers::Xml
#
# attr_accessor :name
#
# def attributes
- # {'name' => name}
+ # {'name' => nil}
# end
- #
# end
#
# Which would provide you with:
@@ -66,27 +64,31 @@ module ActiveModel
# person.to_json # => "{\"name\":\"Bob\"}"
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
#
- # Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> .
+ # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and <tt>:include</tt>.
+ # The following are all valid examples:
+ #
+ # person.serializable_hash(:only => 'name')
+ # person.serializable_hash(:include => :address)
+ # person.serializable_hash(:include => { :address => { :only => 'city' }})
module Serialization
def serializable_hash(options = nil)
options ||= {}
- attribute_names = attributes.keys.sort
+ attribute_names = attributes.keys
if only = options[:only]
- attribute_names &= Array.wrap(only).map(&:to_s)
+ attribute_names &= Array(only).map(&:to_s)
elsif except = options[:except]
- attribute_names -= Array.wrap(except).map(&:to_s)
+ attribute_names -= Array(except).map(&:to_s)
end
hash = {}
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
- method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) }
- method_names.each { |n| hash[n] = send(n) }
+ Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
serializable_add_includes(options) do |association, records, opts|
- hash[association] = if records.is_a?(Enumerable)
- records.map { |a| a.serializable_hash(opts) }
+ hash[association.to_s] = if records.respond_to?(:to_ary)
+ records.to_ary.map { |a| a.serializable_hash(opts) }
else
records.serializable_hash(opts)
end
@@ -123,13 +125,13 @@ module ActiveModel
# +records+ - the association record(s) to be serialized
# +opts+ - options for the association records
def serializable_add_includes(options = {}) #:nodoc:
- return unless include = options[:include]
+ return unless includes = options[:include]
- unless include.is_a?(Hash)
- include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
+ unless includes.is_a?(Hash)
+ includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
end
- include.each do |association, opts|
+ includes.each do |association, opts|
if records = send(association)
yield association, records, opts
end
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index c845440120..63ab8e7edc 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -10,8 +10,9 @@ module ActiveModel
included do
extend ActiveModel::Naming
+ extend ActiveModel::Configuration
- class_attribute :include_root_in_json
+ config_attribute :include_root_in_json
self.include_root_in_json = true
end
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index d61d9d7119..b78f1ff3f3 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/hash/conversions'
@@ -56,7 +55,7 @@ module ActiveModel
end
def serializable_collection
- methods = Array.wrap(options[:methods]).map(&:to_s)
+ methods = Array(options[:methods]).map(&:to_s)
serializable_hash.map do |name, value|
name = name.to_s
if methods.include?(name)
@@ -116,7 +115,9 @@ module ActiveModel
merged_options = opts.merge(options.slice(:builder, :indent))
merged_options[:skip_instruct] = true
- if records.is_a?(Enumerable)
+ if records.respond_to?(:to_ary)
+ records = records.to_ary
+
tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
type = options[:skip_types] ? { } : {:type => "array"}
association_name = association.to_s.singularize
@@ -146,7 +147,7 @@ module ActiveModel
def add_procs
if procs = options.delete(:procs)
- Array.wrap(procs).each do |proc|
+ Array(procs).each do |proc|
if proc.arity == 1
proc.call(options)
else
diff --git a/activemodel/lib/active_model/test_case.rb b/activemodel/lib/active_model/test_case.rb
index 6328807ad7..5004855d56 100644
--- a/activemodel/lib/active_model/test_case.rb
+++ b/activemodel/lib/active_model/test_case.rb
@@ -1,16 +1,4 @@
module ActiveModel #:nodoc:
class TestCase < ActiveSupport::TestCase #:nodoc:
- def with_kcode(kcode)
- if RUBY_VERSION < '1.9'
- orig_kcode, $KCODE = $KCODE, kcode
- begin
- yield
- ensure
- $KCODE = orig_kcode
- end
- else
- yield
- end
- end
end
end
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 6d64c81b5f..6f0ca92e2a 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/hash/reverse_merge'
-
module ActiveModel
# == Active Model Translation
@@ -43,15 +41,28 @@ module ActiveModel
#
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
- defaults = lookup_ancestors.map do |klass|
- :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
+ options = { :count => 1 }.merge!(options)
+ parts = attribute.to_s.split(".", 2)
+ attribute = parts.pop
+ namespace = parts.pop
+ attributes_scope = "#{self.i18n_scope}.attributes"
+
+ if namespace
+ defaults = lookup_ancestors.map do |klass|
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
+ end
+ defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
+ else
+ defaults = lookup_ancestors.map do |klass|
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
+ end
end
defaults << :"attributes.#{attribute}"
defaults << options.delete(:default) if options[:default]
- defaults << attribute.to_s.humanize
+ defaults << attribute.humanize
- options.reverse_merge! :count => 1, :default => defaults
+ options[:default] = defaults
I18n.translate(defaults.shift, options)
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 8ed392abca..611e9ffd55 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/except'
@@ -34,7 +33,7 @@ module ActiveModel
# person.first_name = 'zoolander'
# person.valid? # => false
# person.invalid? # => true
- # person.errors # => #<OrderedHash {:first_name=>["starts with z."]}>
+ # person.errors # => #<Hash {:first_name=>["starts with z."]}>
#
# Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+ method
# to your instances initialized with a new <tt>ActiveModel::Errors</tt> object, so
@@ -42,9 +41,9 @@ module ActiveModel
#
module Validations
extend ActiveSupport::Concern
- include ActiveSupport::Callbacks
included do
+ extend ActiveModel::Callbacks
extend ActiveModel::Translation
extend HelperMethods
@@ -53,7 +52,8 @@ module ActiveModel
attr_accessor :validation_context
define_callbacks :validate, :scope => :name
- class_attribute :_validators
+ extend ActiveModel::Configuration
+ config_attribute :_validators
self._validators = Hash.new { |h,k| h[k] = [] }
end
@@ -65,7 +65,7 @@ module ActiveModel
#
# attr_accessor :first_name, :last_name
#
- # validates_each :first_name, :last_name do |record, attr, value|
+ # validates_each :first_name, :last_name, :allow_blank => true do |record, attr, value|
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
# end
# end
@@ -128,11 +128,24 @@ module ActiveModel
# end
# end
#
+ # Options:
+ # * <tt>:on</tt> - Specifies the context where this validation is active
+ # (e.g. <tt>:on => :create</tt> or <tt>:on => :custom_validation_context</tt>)
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
+ # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
+ # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method,
+ # proc or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # method, proc or string should return or evaluate to a true or false value.
def validate(*args, &block)
options = args.extract_options!
if options.key?(:on)
options = options.dup
- options[:if] = Array.wrap(options[:if])
+ options[:if] = Array(options[:if])
options[:if].unshift("validation_context == :#{options[:on]}")
end
args << options
@@ -145,7 +158,7 @@ module ActiveModel
_validators.values.flatten.uniq
end
- # List all validators that being used to validate a specific attribute.
+ # List all validators that are being used to validate a specific attribute.
def validators_on(*attributes)
attributes.map do |attribute|
_validators[attribute.to_sym]
@@ -165,6 +178,12 @@ module ActiveModel
end
end
+ # Clean the +Errors+ object if instance is duped
+ def initialize_dup(other) # :nodoc:
+ @errors = nil
+ super
+ end
+
# Returns the +Errors+ object that holds all information about attribute error messages.
def errors
@errors ||= Errors.new(self)
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index e628c6f306..38abd0c1fa 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -23,7 +23,7 @@ module ActiveModel
module HelperMethods
# Encapsulates the pattern of wanting to validate the acceptance of a
- # terms of service check box (or similar agreement). Example:
+ # terms of service check box (or similar agreement).
#
# class Person < ActiveRecord::Base
# validates_acceptance_of :terms_of_service
@@ -59,7 +59,7 @@ module ActiveModel
# The method, proc or string should return or evaluate to a true or
# false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index 22a77320dc..dbafd0bd1a 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -8,7 +8,8 @@ module ActiveModel
# Provides an interface for any class to have <tt>before_validation</tt> and
# <tt>after_validation</tt> callbacks.
#
- # First, extend ActiveModel::Callbacks from the class you are creating:
+ # First, include ActiveModel::Validations::Callbacks from the class you are
+ # creating:
#
# class MyModel
# include ActiveModel::Validations::Callbacks
@@ -23,14 +24,14 @@ module ActiveModel
included do
include ActiveSupport::Callbacks
- define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+ define_callbacks :validation, :terminator => "result == false", :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name]
end
module ClassMethods
def before_validation(*args, &block)
options = args.last
if options.is_a?(Hash) && options[:on]
- options[:if] = Array.wrap(options[:if])
+ options[:if] = Array(options[:if])
options[:if].unshift("self.validation_context == :#{options[:on]}")
end
set_callback(:validation, :before, *args, &block)
@@ -39,8 +40,7 @@ module ActiveModel
def after_validation(*args, &block)
options = args.extract_options!
options[:prepend] = true
- options[:if] = Array.wrap(options[:if])
- options[:if] << "!halted"
+ options[:if] = Array(options[:if])
options[:if].unshift("self.validation_context == :#{options[:on]}") if options[:on]
set_callback(:validation, :after, *(args << options), &block)
end
diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb
new file mode 100644
index 0000000000..b632a2bd6b
--- /dev/null
+++ b/activemodel/lib/active_model/validations/clusivity.rb
@@ -0,0 +1,31 @@
+require 'active_support/core_ext/range.rb'
+
+module ActiveModel
+ module Validations
+ module Clusivity
+ 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!
+ unless [:include?, :call].any?{ |method| options[:in].respond_to?(method) }
+ raise ArgumentError, ERROR_MESSAGE
+ end
+ end
+
+ private
+
+ def include?(record, value)
+ delimiter = options[:in]
+ exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
+ exclusions.send(inclusion_method(exclusions), value)
+ end
+
+ # 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
+ end
+end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index 6573a7d264..ede34d15bc 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -5,7 +5,8 @@ module ActiveModel
class ConfirmationValidator < EachValidator
def validate_each(record, attribute, value)
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
- record.errors.add(attribute, :confirmation, options)
+ human_attribute_name = record.class.human_attribute_name(attribute)
+ record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(:attribute => human_attribute_name))
end
end
@@ -18,7 +19,7 @@ module ActiveModel
module HelperMethods
# Encapsulates the pattern of wanting to validate a password or email
- # address field with a confirmation. For example:
+ # address field with a confirmation.
#
# Model:
# class Person < ActiveRecord::Base
@@ -37,7 +38,7 @@ module ActiveModel
# attribute.
#
# NOTE: This check is performed only if +password_confirmation+ is not
- # +nil+, and by default only on save. To require confirmation, make sure
+ # +nil+. To require confirmation, make sure
# to add a presence check for the confirmation attribute:
#
# validates_presence_of :password_confirmation, :if => :password_changed?
@@ -59,7 +60,7 @@ module ActiveModel
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_confirmation_of(*attr_names)
validates_with ConfirmationValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 644cc814a7..4f09679541 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -1,66 +1,56 @@
-require 'active_support/core_ext/range'
+require "active_model/validations/clusivity"
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!
- unless [:include?, :call].any? { |method| options[:in].respond_to?(method) }
- raise ArgumentError, ERROR_MESSAGE
- end
- end
+ include Clusivity
def validate_each(record, attribute, value)
- delimiter = options[:in]
- exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
- if exclusions.send(inclusion_method(exclusions), value)
+ if include?(record, 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
- # Validates that the value of the specified attribute is not in a particular enumerable object.
+ # Validates that the value of the specified attribute is not in a
+ # particular enumerable object.
#
# class Person < ActiveRecord::Base
# 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"
+ # 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+).
+ # * <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+).
# * <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>.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_exclusion_of(*attr_names)
validates_with ExclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index d3faa8c6a6..dd87e312f9 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -42,50 +42,62 @@ module ActiveModel
end
module HelperMethods
- # Validates whether the value of the specified attribute is of the correct form, going by the regular expression provided.
- # You can require that the attribute matches the regular expression:
+ # Validates whether the value of the specified attribute is of the correct
+ # form, going by the regular expression provided.You can require that the
+ # attribute matches the regular expression:
#
# class Person < ActiveRecord::Base
# validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
# end
#
- # Alternatively, you can require that the specified attribute does _not_ match the regular expression:
+ # Alternatively, you can require that the specified attribute does _not_
+ # match the regular expression:
#
# class Person < ActiveRecord::Base
# 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
+ # 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 }
+ # 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.
+ # 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 a proc or lambda, 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>: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>.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_format_of(*attr_names)
validates_with FormatValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 147e2ecb69..ffdbed0fc1 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -1,39 +1,22 @@
-require 'active_support/core_ext/range'
+require "active_model/validations/clusivity"
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!
- unless [:include?, :call].any?{ |method| options[:in].respond_to?(method) }
- raise ArgumentError, ERROR_MESSAGE
- end
- end
+ include Clusivity
def validate_each(record, attribute, value)
- delimiter = options[:in]
- exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
- unless exclusions.send(inclusion_method(exclusions), value)
+ unless include?(record, 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 inclusion_method(enumerable)
- enumerable.is_a?(Range) ? :cover? : :include?
- end
end
module HelperMethods
- # Validates whether the value of the specified attribute is available in a particular enumerable object.
+ # Validates whether the value of the specified attribute is available in a
+ # particular enumerable object.
#
# class Person < ActiveRecord::Base
# validates_inclusion_of :gender, :in => %w( m f )
@@ -47,20 +30,25 @@ module ActiveModel
# 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+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
+ # * <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+).
+ # * <tt>:allow_blank</tt> - If set to true, skips this validation if the
+ # attribute is blank (default is +false+).
# * <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>.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_inclusion_of(*attr_names)
validates_with InclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index eb7aac709d..64b4fe2d74 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -6,14 +6,12 @@ module ActiveModel
MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
- DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
def initialize(options)
if range = (options.delete(:in) || options.delete(:within))
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
- options[:minimum], options[:maximum] = range.begin, range.end
- options[:maximum] -= 1 if range.exclude_end?
+ options[:minimum], options[:maximum] = range.min, range.max
end
super
@@ -23,27 +21,24 @@ module ActiveModel
keys = CHECKS.keys & options.keys
if keys.empty?
- raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
+ raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.'
end
keys.each do |key|
value = options[key]
- unless value.is_a?(Integer) && value >= 0
- raise ArgumentError, ":#{key} must be a nonnegative Integer"
+ unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY
+ raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity"
end
end
end
def validate_each(record, attribute, value)
- value = (options[:tokenizer] || DEFAULT_TOKENIZER).call(value) if value.kind_of?(String)
+ value = tokenize(value)
+ value_length = value.respond_to?(:length) ? value.length : value.to_s.length
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
-
- value ||= [] if key == :maximum
-
- value_length = value.respond_to?(:length) ? value.length : value.to_s.length
next if value_length.send(validity_check, check_value)
errors_options = options.except(*RESERVED_OPTIONS)
@@ -55,6 +50,14 @@ module ActiveModel
record.errors.add(attribute, MESSAGES[key], errors_options)
end
end
+
+ private
+
+ def tokenize(value)
+ if options[:tokenizer] && value.kind_of?(String)
+ options[:tokenizer].call(value)
+ end || value
+ end
end
module HelperMethods
@@ -69,35 +72,46 @@ module ActiveModel
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
# validates_length_of :zip_code, :minimum => 5, :too_short => "please enter at least 5 characters"
# validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with 4 characters... don't play me."
- # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words.", :tokenizer => lambda { |str| str.scan(/\w+/) }
+ # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words.",
+ # :tokenizer => lambda { |str| str.scan(/\w+/) }
# end
#
# Configuration options:
# * <tt>:minimum</tt> - The minimum size of the attribute.
# * <tt>:maximum</tt> - The maximum size of the attribute.
# * <tt>:is</tt> - The exact size of the attribute.
- # * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute.
+ # * <tt>:within</tt> - A range specifying the minimum and maximum size of the
+ # attribute.
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
- # * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %{count} characters)").
- # * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %{count} characters)").
- # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %{count} characters)").
- # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
+ # * <tt>:too_long</tt> - The error message if the attribute goes over the
+ # maximum (default is: "is too long (maximum is %{count} characters)").
+ # * <tt>:too_short</tt> - The error message if the attribute goes under the
+ # minimum (default is: "is too short (min is %{count} characters)").
+ # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method
+ # and the attribute is the wrong size (default is: "is the wrong length
+ # (should be %{count} characters)").
+ # * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
+ # <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
+ # <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
# * <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>.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
- # count words as in above example.)
- # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
+ # proc or string should return or evaluate to a true or false value.
+ # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
+ # (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to count words
+ # as in above example). Defaults to <tt>lambda{ |value| value.split(//) }</tt>
+ # which counts individual characters.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_length_of(*attr_names)
validates_with LengthValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 34d447a0fa..40b5b92b84 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -5,7 +5,7 @@ module ActiveModel
class NumericalityValidator < EachValidator
CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=,
:equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=,
- :odd => :odd?, :even => :even? }.freeze
+ :odd => :odd?, :even => :even?, :other_than => :!= }.freeze
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
@@ -79,9 +79,10 @@ module ActiveModel
end
module HelperMethods
- # Validates whether the value of the specified attribute is numeric by trying to convert it to
- # a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
- # <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).
+ # Validates whether the value of the specified attribute is numeric by trying
+ # to convert it to a float with Kernel.Float (if <tt>only_integer</tt> is false)
+ # or applying it to the regular expression <tt>/\A[\+\-]?\d+\Z/</tt> (if
+ # <tt>only_integer</tt> is set to true).
#
# class Person < ActiveRecord::Base
# validates_numericality_of :value, :on => :create
@@ -92,36 +93,50 @@ module ActiveModel
# * <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>.
- # * <tt>:only_integer</tt> - Specifies whether the value has to be an integer, e.g. an integral value (default is +false+).
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is +false+). Notice that for fixnum and float columns empty strings are converted to +nil+.
- # * <tt>:greater_than</tt> - Specifies the value must be greater than the supplied value.
- # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be greater than or equal the supplied value.
+ # * <tt>:only_integer</tt> - Specifies whether the value has to be an integer,
+ # e.g. an integral value (default is +false+).
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
+ # +false+). Notice that for fixnum and float columns empty strings are
+ # converted to +nil+.
+ # * <tt>:greater_than</tt> - Specifies the value must be greater than the
+ # supplied value.
+ # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be greater
+ # than or equal the supplied value.
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied value.
- # * <tt>:less_than</tt> - Specifies the value must be less than the supplied value.
- # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less than or equal the supplied value.
+ # * <tt>:less_than</tt> - Specifies the value must be less than the supplied
+ # value.
+ # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less than or
+ # equal the supplied value.
+ # * <tt>:other_than</tt> - Specifies the value must be other than the supplied
+ # value.
# * <tt>:odd</tt> - Specifies the value must be an odd number.
# * <tt>:even</tt> - Specifies the value must be an even number.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
+ # The following checks can also be supplied with a proc or a symbol which
+ # corresponds to a method:
#
- # The following checks can also be supplied with a proc or a symbol which corresponds to a method:
# * <tt>:greater_than</tt>
# * <tt>:greater_than_or_equal_to</tt>
# * <tt>:equal_to</tt>
# * <tt>:less_than</tt>
# * <tt>:less_than_or_equal_to</tt>
#
+ # For example:
+ #
# class Person < ActiveRecord::Base
# validates_numericality_of :width, :less_than => Proc.new { |person| person.height }
# validates_numericality_of :width, :greater_than => :minimum_weight
# end
- #
def validates_numericality_of(*attr_names)
validates_with NumericalityValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 35af7152db..018ef1e733 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -11,7 +11,8 @@ module ActiveModel
end
module HelperMethods
- # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
+ # Validates that the specified attributes are not blank (as defined by
+ # Object#blank?). Happens by default on save.
#
# class Person < ActiveRecord::Base
# validates_presence_of :first_name
@@ -19,25 +20,28 @@ module ActiveModel
#
# The first_name attribute must be in the object and it cannot be blank.
#
- # If you want to validate the presence of a boolean field (where the real values are true and false),
- # you will want to use <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
+ # If you want to validate the presence of a boolean field (where the real values
+ # are true and false), you will want to use
+ # <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
#
- # This is due to the way Object#blank? handles boolean values: <tt>false.blank? # => true</tt>.
+ # This is due to the way Object#blank? handles boolean values:
+ # <tt>false.blank? # => true</tt>.
#
# Configuration options:
- # * <tt>message</tt> - A custom error message (default is: "can't be blank").
+ # * <tt>:message</tt> - A custom error message (default is: "can't be blank").
# * <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>.
- # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
- # The method, proc or string should return or evaluate to a true or false value.
- # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
- # The method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
+ # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
+ # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
+ # or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
+ # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
+ # proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
- #
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index fbceb81e8f..d94c4e3f4f 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -1,7 +1,6 @@
require 'active_support/core_ext/hash/slice'
module ActiveModel
-
# == Active Model validates method
module Validations
module ClassMethods
@@ -57,9 +56,9 @@ module ActiveModel
#
# Additionally validator classes may be in another namespace and still used within any class.
#
- # validates :name, :'file/title' => true
+ # validates :name, :'film/title' => true
#
- # The validators hash can also handle regular expressions, ranges,
+ # The validators hash can also handle regular expressions, ranges,
# arrays and strings in shortcut form, e.g.
#
# validates :email, :format => /@/
@@ -70,7 +69,7 @@ module ActiveModel
# validator's initializer as +options[:in]+ while other types including
# regular expressions and strings are passed as +options[:with]+
#
- # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+
+ # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+
# can be given to one specific validator, as a hash:
#
# validates :password, :presence => { :if => :password_required? }, :confirmation => true
@@ -80,7 +79,7 @@ module ActiveModel
# validates :password, :presence => true, :confirmation => true, :if => :password_required?
#
def validates(*attributes)
- defaults = attributes.extract_options!
+ defaults = attributes.extract_options!.dup
validations = defaults.slice!(*_validates_default_keys)
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
@@ -101,12 +100,12 @@ module ActiveModel
end
end
- # This method is used to define validation that can not be corrected by end user
- # and is considered exceptional.
- # So each validator defined with bang or <tt>:strict</tt> option set to <tt>true</tt>
- # will always raise <tt>ActiveModel::InternalValidationFailed</tt> instead of adding error
- # when validation fails
- # See <tt>validates</tt> for more information about validation itself.
+ # This method is used to define validations that cannot be corrected by end
+ # users and are considered exceptional. So each validator defined with bang
+ # or <tt>:strict</tt> option set to <tt>true</tt> will always raise
+ # <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
+ # when validation fails.
+ # See <tt>validates</tt> for more information about the validation itself.
def validates!(*attributes)
options = attributes.extract_options!
options[:strict] = true
@@ -118,7 +117,7 @@ module ActiveModel
# When creating custom validators, it might be useful to be able to specify
# additional default keys. This can be done by overwriting this method.
def _validates_default_keys
- [ :if, :unless, :on, :allow_blank, :allow_nil , :strict]
+ [:if, :unless, :on, :allow_blank, :allow_nil , :strict]
end
def _parse_validates_options(options) #:nodoc:
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 93a340eb39..66cc9daa2c 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -56,14 +56,14 @@ module ActiveModel
# if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
# or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
- # * <tt>unless</tt> - Specifies a method, proc or string to call to
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
# determine if the validation should not occur
# (e.g. <tt>:unless => :skip_validation</tt>, or
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information
-
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
# If you pass any additional configuration options, they will be passed
# to the class and available as <tt>options</tt>:
#
@@ -77,7 +77,6 @@ module ActiveModel
# options[:my_custom_key] # => "my custom value"
# end
# end
- #
def validates_with(*args, &block)
options = args.extract_options!
args.each do |klass|
@@ -126,14 +125,13 @@ module ActiveModel
# end
#
# Standard configuration options (:on, :if and :unless), which are
- # available on the class version of validates_with, should instead be
- # placed on the <tt>validates</tt> method as these are applied and tested
- # in the callback
+ # available on the class version of +validates_with+, should instead be
+ # placed on the +validates+ method as these are applied and tested
+ # in the callback.
#
# If you pass any additional configuration options, they will be passed
- # to the class and available as <tt>options</tt>, please refer to the
- # class version of this method for more information
- #
+ # to the class and available as +options+, please refer to the
+ # class version of this method for more information.
def validates_with(*args, &block)
options = args.extract_options!
args.each do |klass|
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 0e444738ba..2953126c3c 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -1,4 +1,3 @@
-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'
@@ -57,7 +56,7 @@ module ActiveModel #:nodoc:
# To add behavior to the initialize method, use the following signature:
#
# class MyValidator < ActiveModel::Validator
- # def initialize(record, options)
+ # def initialize(options)
# super
# @my_custom_field = options[:field_name] || :first_name
# end
@@ -137,7 +136,7 @@ module ActiveModel #:nodoc:
# +options+ reader, however the <tt>:attributes</tt> option will be removed
# and instead be made available through the +attributes+ reader.
def initialize(options)
- @attributes = Array.wrap(options.delete(:attributes))
+ @attributes = Array(options.delete(:attributes))
raise ":attributes cannot be blank" if @attributes.empty?
super
check_validity!
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index dbda55ca7c..e195c12a4d 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,7 +1,7 @@
module ActiveModel
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index 67471ed497..a9db29ee21 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -10,7 +10,7 @@ class ModelWithAttributes
end
def attributes
- { :foo => 'value of foo' }
+ { :foo => 'value of foo', :baz => 'value of baz' }
end
private
@@ -76,7 +76,15 @@ private
end
end
+class ModelWithoutAttributesMethod
+ include ActiveModel::AttributeMethods
+end
+
class AttributeMethodsTest < ActiveModel::TestCase
+ test 'method missing works correctly even if attributes method is not defined' do
+ assert_raises(NoMethodError) { ModelWithoutAttributesMethod.new.foo }
+ end
+
test 'unrelated classes should not share attribute method matchers' do
assert_not_equal ModelWithAttributes.send(:attribute_method_matchers),
ModelWithAttributes2.send(:attribute_method_matchers)
@@ -119,47 +127,36 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b')
end
+ test '#define_attribute_methods works passing multiple arguments' do
+ ModelWithAttributes.define_attribute_methods(:foo, :baz)
+
+ assert_equal "value of foo", ModelWithAttributes.new.foo
+ assert_equal "value of baz", ModelWithAttributes.new.baz
+ end
+
test '#define_attribute_methods generates attribute methods' do
- ModelWithAttributes.define_attribute_methods([:foo])
+ ModelWithAttributes.define_attribute_methods(:foo)
assert_respond_to ModelWithAttributes.new, :foo
assert_equal "value of foo", ModelWithAttributes.new.foo
end
test '#define_attribute_methods generates attribute methods with spaces in their names' do
- ModelWithAttributesWithSpaces.define_attribute_methods([:'foo bar'])
+ ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar')
assert_respond_to ModelWithAttributesWithSpaces.new, :'foo bar'
assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar')
end
- test '#define_attr_method generates attribute method' do
- ModelWithAttributes.define_attr_method(:bar, 'bar')
-
- assert_respond_to ModelWithAttributes, :bar
- assert_equal "original bar", ModelWithAttributes.original_bar
- assert_equal "bar", ModelWithAttributes.bar
- ModelWithAttributes.define_attr_method(:bar)
- assert !ModelWithAttributes.bar
- end
-
- test '#define_attr_method generates attribute method with invalid identifier characters' do
- ModelWithWeirdNamesAttributes.define_attr_method(:'c?d', 'c?d')
-
- assert_respond_to ModelWithWeirdNamesAttributes, :'c?d'
- assert_equal "original c?d", ModelWithWeirdNamesAttributes.send('original_c?d')
- assert_equal "c?d", ModelWithWeirdNamesAttributes.send('c?d')
- end
-
test '#alias_attribute works with attributes with spaces in their names' do
- ModelWithAttributesWithSpaces.define_attribute_methods([:'foo bar'])
+ ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar')
ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar')
assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar
end
test '#undefine_attribute_methods removes attribute methods' do
- ModelWithAttributes.define_attribute_methods([:foo])
+ ModelWithAttributes.define_attribute_methods(:foo)
ModelWithAttributes.undefine_attribute_methods
assert !ModelWithAttributes.new.respond_to?(:foo)
@@ -180,7 +177,7 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_deprecated { klass.attribute_method_suffix '' }
assert_deprecated { klass.attribute_method_prefix '' }
- klass.define_attribute_methods([:foo])
+ klass.define_attribute_methods(:foo)
assert_equal 'value of foo', klass.new.foo
end
@@ -198,6 +195,12 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_raises(NoMethodError) { m.protected_method }
end
+ class ClassWithProtected
+ protected
+ def protected_method
+ end
+ end
+
test 'should not interfere with respond_to? if the attribute has a private/protected method' do
m = ModelWithAttributes2.new
m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' }
@@ -205,9 +208,11 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert !m.respond_to?(:private_method)
assert m.respond_to?(:private_method, true)
+ c = ClassWithProtected.new
+
# This is messed up, but it's how Ruby works at the moment. Apparently it will be changed
# in the future.
- assert m.respond_to?(:protected_method)
+ assert_equal c.respond_to?(:protected_method), m.respond_to?(:protected_method)
assert m.respond_to?(:protected_method, true)
end
diff --git a/activemodel/test/cases/configuration_test.rb b/activemodel/test/cases/configuration_test.rb
new file mode 100644
index 0000000000..a172fa26a3
--- /dev/null
+++ b/activemodel/test/cases/configuration_test.rb
@@ -0,0 +1,154 @@
+require 'cases/helper'
+
+class ConfigurationOnModuleTest < ActiveModel::TestCase
+ def setup
+ @mod = mod = Module.new do
+ extend ActiveSupport::Concern
+ extend ActiveModel::Configuration
+
+ config_attribute :omg
+ self.omg = "default"
+
+ config_attribute :wtf, global: true
+ self.wtf = "default"
+
+ config_attribute :boolean
+
+ config_attribute :lol, instance_writer: true
+ end
+
+ @klass = Class.new do
+ include mod
+ end
+
+ @subklass = Class.new(@klass)
+ end
+
+ test "default" do
+ assert_equal "default", @mod.omg
+ assert_equal "default", @klass.omg
+ assert_equal "default", @klass.new.omg
+ end
+
+ test "setting" do
+ @mod.omg = "lol"
+ assert_equal "lol", @mod.omg
+ end
+
+ test "setting on class including the module" do
+ @klass.omg = "lol"
+ assert_equal "lol", @klass.omg
+ assert_equal "lol", @klass.new.omg
+ assert_equal "default", @mod.omg
+ end
+
+ test "setting on subclass of class including the module" do
+ @subklass.omg = "lol"
+ assert_equal "lol", @subklass.omg
+ assert_equal "default", @klass.omg
+ assert_equal "default", @mod.omg
+ end
+
+ test "setting on instance" do
+ assert !@klass.new.respond_to?(:omg=)
+
+ @klass.lol = "lol"
+ obj = @klass.new
+ assert_equal "lol", obj.lol
+ obj.lol = "omg"
+ assert_equal "omg", obj.lol
+ assert_equal "lol", @klass.lol
+ assert_equal "lol", @klass.new.lol
+ obj.lol = false
+ assert !obj.lol?
+ end
+
+ test "global attribute" do
+ assert_equal "default", @mod.wtf
+ assert_equal "default", @klass.wtf
+
+ @mod.wtf = "wtf"
+
+ assert_equal "wtf", @mod.wtf
+ assert_equal "wtf", @klass.wtf
+
+ @klass.wtf = "lol"
+
+ assert_equal "lol", @mod.wtf
+ assert_equal "lol", @klass.wtf
+ end
+
+ test "boolean" do
+ assert_equal false, @mod.boolean?
+ assert_equal false, @klass.new.boolean?
+ @mod.boolean = true
+ assert_equal true, @mod.boolean?
+ assert_equal true, @klass.new.boolean?
+ end
+end
+
+class ConfigurationOnClassTest < ActiveModel::TestCase
+ def setup
+ @klass = Class.new do
+ extend ActiveModel::Configuration
+
+ config_attribute :omg
+ self.omg = "default"
+
+ config_attribute :wtf, global: true
+ self.wtf = "default"
+
+ config_attribute :omg2, instance_writer: true
+ config_attribute :wtf2, instance_writer: true, global: true
+ end
+
+ @subklass = Class.new(@klass)
+ end
+
+ test "defaults" do
+ assert_equal "default", @klass.omg
+ assert_equal "default", @klass.wtf
+ assert_equal "default", @subklass.omg
+ assert_equal "default", @subklass.wtf
+ end
+
+ test "changing" do
+ @klass.omg = "lol"
+ assert_equal "lol", @klass.omg
+ assert_equal "lol", @subklass.omg
+ end
+
+ test "changing in subclass" do
+ @subklass.omg = "lol"
+ assert_equal "lol", @subklass.omg
+ assert_equal "default", @klass.omg
+ end
+
+ test "changing global" do
+ @klass.wtf = "wtf"
+ assert_equal "wtf", @klass.wtf
+ assert_equal "wtf", @subklass.wtf
+
+ @subklass.wtf = "lol"
+ assert_equal "lol", @klass.wtf
+ assert_equal "lol", @subklass.wtf
+ end
+
+ test "instance_writer" do
+ obj = @klass.new
+
+ @klass.omg2 = "omg"
+ @klass.wtf2 = "wtf"
+
+ assert_equal "omg", obj.omg2
+ assert_equal "wtf", obj.wtf2
+
+ obj.omg2 = "lol"
+ obj.wtf2 = "lol"
+
+ assert_equal "lol", obj.omg2
+ assert_equal "lol", obj.wtf2
+ assert_equal "omg", @klass.omg2
+ assert_equal "lol", @klass.wtf2
+ end
+end
diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb
index 24552bcaf2..d679ad41aa 100644
--- a/activemodel/test/cases/conversion_test.rb
+++ b/activemodel/test/cases/conversion_test.rb
@@ -24,7 +24,7 @@ class ConversionTest < ActiveModel::TestCase
assert_equal "1", Contact.new(:id => 1).to_param
end
- test "to_path default implementation returns a string giving a relative path" do
+ test "to_partial_path default implementation returns a string giving a relative path" do
assert_equal "contacts/contact", Contact.new.to_partial_path
assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path,
"ActiveModel::Conversion#to_partial_path caching should be class-specific"
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index 98244a6290..eaaf910bac 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -3,7 +3,7 @@ require "cases/helper"
class DirtyTest < ActiveModel::TestCase
class DirtyModel
include ActiveModel::Dirty
- define_attribute_methods [:name, :color]
+ define_attribute_methods :name, :color
def initialize
@name = nil
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 784a2b2709..3bc0d58351 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -27,12 +27,27 @@ class ErrorsTest < ActiveModel::TestCase
end
end
+ def test_delete
+ errors = ActiveModel::Errors.new(self)
+ errors[:foo] = 'omg'
+ errors.delete(:foo)
+ assert_empty errors[:foo]
+ end
+
def test_include?
errors = ActiveModel::Errors.new(self)
errors[:foo] = 'omg'
assert errors.include?(:foo), 'errors should include :foo'
end
+ def test_dup
+ errors = ActiveModel::Errors.new(self)
+ errors[:foo] = 'bar'
+ errors_dup = errors.dup
+ errors_dup[:bar] = 'omg'
+ assert_not_same errors_dup.messages, errors.messages
+ end
+
def test_has_key?
errors = ActiveModel::Errors.new(self)
errors[:foo] = 'omg'
@@ -80,6 +95,49 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["can not be blank"], person.errors[:name]
end
+ test "added? should be true if that error was added" do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ assert person.errors.added?(:name, "can not be blank")
+ end
+
+ test "added? should handle when message is a symbol" do
+ person = Person.new
+ person.errors.add(:name, :blank)
+ assert person.errors.added?(:name, :blank)
+ end
+
+ test "added? should handle when message is a proc" do
+ person = Person.new
+ message = Proc.new { "can not be blank" }
+ person.errors.add(:name, message)
+ assert person.errors.added?(:name, message)
+ end
+
+ test "added? should default message to :invalid" do
+ person = Person.new
+ person.errors.add(:name, :invalid)
+ assert person.errors.added?(:name)
+ end
+
+ test "added? should be true when several errors are present, and we ask for one of them" do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "is invalid")
+ assert person.errors.added?(:name, "can not be blank")
+ end
+
+ test "added? should be false if no errors are present" do
+ person = Person.new
+ assert !person.errors.added?(:name)
+ end
+
+ test "added? should be false when an error is present, but we check for another error" do
+ person = Person.new
+ person.errors.add(:name, "is invalid")
+ assert !person.errors.added?(:name, "can not be blank")
+ end
+
test 'should respond to size' do
person = Person.new
person.errors.add(:name, "can not be blank")
@@ -93,10 +151,10 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
end
- test 'to_hash should return an ordered hash' do
+ test 'to_hash should return a hash' do
person = Person.new
person.errors.add(:name, "can not be blank")
- assert_instance_of ActiveSupport::OrderedHash, person.errors.to_hash
+ assert_instance_of ::Hash, person.errors.to_hash
end
test 'full_messages should return an array of error messages, with the attribute name included' do
@@ -126,6 +184,16 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["is invalid"], hash[:email]
end
+ test 'should return a JSON hash representation of the errors with full messages' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "can not be nil")
+ person.errors.add(:email, "is invalid")
+ hash = person.errors.as_json(:full_messages => true)
+ assert_equal ["name can not be blank", "name can not be nil"], hash[:name]
+ assert_equal ["email is invalid"], hash[:email]
+ end
+
test "generate_message should work without i18n_scope" do
person = Person.new
assert !Person.respond_to?(:i18n_scope)
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index 2e860272a4..7d6f11b5a5 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -1,8 +1,5 @@
require File.expand_path('../../../../load_paths', __FILE__)
-lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib")
-$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
-
require 'config'
require 'active_model'
require 'active_support/core_ext/string/access'
@@ -10,4 +7,4 @@ require 'active_support/core_ext/string/access'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
-require 'test/unit'
+require 'minitest/autorun'
diff --git a/activemodel/test/cases/lint_test.rb b/activemodel/test/cases/lint_test.rb
index 68372160cd..8faf93c056 100644
--- a/activemodel/test/cases/lint_test.rb
+++ b/activemodel/test/cases/lint_test.rb
@@ -7,14 +7,10 @@ class LintTest < ActiveModel::TestCase
extend ActiveModel::Naming
include ActiveModel::Conversion
- def valid?() true end
def persisted?() false end
def errors
- obj = Object.new
- def obj.[](key) [] end
- def obj.full_messages() [] end
- obj
+ Hash.new([])
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 676937b5e1..418a24294a 100644
--- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
@@ -1,5 +1,5 @@
require "cases/helper"
-require 'logger'
+require 'active_support/logger'
require 'active_support/core_ext/object/inclusion'
class SanitizerTest < ActiveModel::TestCase
@@ -19,7 +19,7 @@ class SanitizerTest < ActiveModel::TestCase
test "sanitize attributes" do
original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
- attributes = @logger_sanitizer.sanitize(original_attributes, @authorizer)
+ attributes = @logger_sanitizer.sanitize(self.class, original_attributes, @authorizer)
assert attributes.key?('first_name'), "Allowed key shouldn't be rejected"
assert !attributes.key?('admin'), "Denied key should be rejected"
@@ -28,15 +28,15 @@ class SanitizerTest < ActiveModel::TestCase
test "debug mass assignment removal with LoggerSanitizer" do
original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
log = StringIO.new
- self.logger = Logger.new(log)
- @logger_sanitizer.sanitize(original_attributes, @authorizer)
+ self.logger = ActiveSupport::Logger.new(log)
+ @logger_sanitizer.sanitize(self.class, original_attributes, @authorizer)
assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}")
end
test "debug mass assignment removal with StrictSanitizer" do
original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' }
assert_raise ActiveModel::MassAssignmentSecurity::Error do
- @strict_sanitizer.sanitize(original_attributes, @authorizer)
+ @strict_sanitizer.sanitize(self.class, original_attributes, @authorizer)
end
end
@@ -44,7 +44,7 @@ class SanitizerTest < ActiveModel::TestCase
original_attributes = {'id' => 1, 'first_name' => 'allowed'}
assert_nothing_raised do
- @strict_sanitizer.sanitize(original_attributes, @authorizer)
+ @strict_sanitizer.sanitize(self.class, original_attributes, @authorizer)
end
end
diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb
index be07e59a2f..0c6352cd71 100644
--- a/activemodel/test/cases/mass_assignment_security_test.rb
+++ b/activemodel/test/cases/mass_assignment_security_test.rb
@@ -4,7 +4,7 @@ require 'models/mass_assignment_specific'
class CustomSanitizer < ActiveModel::MassAssignmentSecurity::Sanitizer
- def process_removed_attributes(attrs)
+ def process_removed_attributes(klass, attrs)
raise StandardError
end
@@ -19,6 +19,13 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal expected, sanitized
end
+ def test_attribute_protection_when_role_is_nil
+ user = User.new
+ expected = { "name" => "John Smith", "email" => "john@smith.com" }
+ sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), nil)
+ assert_equal expected, sanitized
+ end
+
def test_only_moderator_role_attribute_accessible
user = SpecialUser.new
expected = { "name" => "John Smith", "email" => "john@smith.com" }
diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb
new file mode 100644
index 0000000000..d93fd96b88
--- /dev/null
+++ b/activemodel/test/cases/model_test.rb
@@ -0,0 +1,26 @@
+require 'cases/helper'
+
+class ModelTest < ActiveModel::TestCase
+ include ActiveModel::Lint::Tests
+
+ class BasicModel
+ include ActiveModel::Model
+ attr_accessor :attr
+ end
+
+ def setup
+ @model = BasicModel.new
+ end
+
+ def test_initialize_with_params
+ object = BasicModel.new(:attr => "value")
+ assert_equal object.attr, "value"
+ end
+
+ def test_initialize_with_nil_or_empty_hash_params_does_not_explode
+ assert_nothing_raised do
+ BasicModel.new()
+ BasicModel.new({})
+ end
+ end
+end
diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb
index 5f943729dd..49d8706ac2 100644
--- a/activemodel/test/cases/naming_test.rb
+++ b/activemodel/test/cases/naming_test.rb
@@ -25,15 +25,13 @@ class NamingTest < ActiveModel::TestCase
assert_equal 'post/track_backs', @model_name.collection
end
- def test_partial_path
- assert_deprecated(/#partial_path.*#to_partial_path/) do
- assert_equal 'post/track_backs/track_back', @model_name.partial_path
- end
- end
-
def test_human
assert_equal 'Track back', @model_name.human
end
+
+ def test_i18n_key
+ assert_equal :"post/track_back", @model_name.i18n_key
+ end
end
class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase
@@ -57,12 +55,6 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase
assert_equal 'blog/posts', @model_name.collection
end
- def test_partial_path
- assert_deprecated(/#partial_path.*#to_partial_path/) do
- assert_equal 'blog/posts/post', @model_name.partial_path
- end
- end
-
def test_human
assert_equal 'Post', @model_name.human
end
@@ -75,8 +67,8 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase
assert_equal 'post', @model_name.param_key
end
- def test_recognizing_namespace
- assert_equal 'Post', Blog::Post.model_name.instance_variable_get("@unnamespaced")
+ def test_i18n_key
+ assert_equal :"blog/post", @model_name.i18n_key
end
end
@@ -101,12 +93,6 @@ class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase
assert_equal 'blog/posts', @model_name.collection
end
- def test_partial_path
- assert_deprecated(/#partial_path.*#to_partial_path/) do
- assert_equal 'blog/posts/post', @model_name.partial_path
- end
- end
-
def test_human
assert_equal 'Post', @model_name.human
end
@@ -118,6 +104,10 @@ class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase
def test_param_key
assert_equal 'blog_post', @model_name.param_key
end
+
+ def test_i18n_key
+ assert_equal :"blog/post", @model_name.i18n_key
+ end
end
class NamingWithSuppliedModelNameTest < ActiveModel::TestCase
@@ -141,12 +131,6 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase
assert_equal 'articles', @model_name.collection
end
- def test_partial_path
- assert_deprecated(/#partial_path.*#to_partial_path/) do
- assert_equal 'articles/article', @model_name.partial_path
- end
- end
-
def test_human
assert_equal 'Article', @model_name.human
end
@@ -158,15 +142,58 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase
def test_param_key
assert_equal 'article', @model_name.param_key
end
+
+ def test_i18n_key
+ assert_equal :"article", @model_name.i18n_key
+ end
+end
+
+class NamingUsingRelativeModelNameTest < ActiveModel::TestCase
+ def setup
+ @model_name = Blog::Post.model_name
+ end
+
+ def test_singular
+ assert_equal 'blog_post', @model_name.singular
+ end
+
+ def test_plural
+ assert_equal 'blog_posts', @model_name.plural
+ end
+
+ def test_element
+ assert_equal 'post', @model_name.element
+ end
+
+ def test_collection
+ assert_equal 'blog/posts', @model_name.collection
+ end
+
+ def test_human
+ assert_equal 'Post', @model_name.human
+ end
+
+ def test_route_key
+ assert_equal 'posts', @model_name.route_key
+ end
+
+ def test_param_key
+ assert_equal 'post', @model_name.param_key
+ end
+
+ def test_i18n_key
+ assert_equal :"blog/post", @model_name.i18n_key
+ end
end
-class NamingHelpersTest < Test::Unit::TestCase
+class NamingHelpersTest < ActiveModel::TestCase
def setup
@klass = Contact
@record = @klass.new
@singular = 'contact'
@plural = 'contacts'
@uncountable = Sheep
+ @singular_route_key = 'contact'
@route_key = 'contacts'
@param_key = 'contact'
end
@@ -193,10 +220,12 @@ class NamingHelpersTest < Test::Unit::TestCase
def test_route_key
assert_equal @route_key, route_key(@record)
+ assert_equal @singular_route_key, singular_route_key(@record)
end
def test_route_key_for_class
assert_equal @route_key, route_key(@klass)
+ assert_equal @singular_route_key, singular_route_key(@klass)
end
def test_param_key
@@ -212,8 +241,26 @@ class NamingHelpersTest < Test::Unit::TestCase
assert !uncountable?(@klass), "Expected 'contact' to be countable"
end
+ def test_uncountable_route_key
+ assert_equal "sheep", singular_route_key(@uncountable)
+ assert_equal "sheep_index", route_key(@uncountable)
+ end
+
private
def method_missing(method, *args)
ActiveModel::Naming.send(method, *args)
end
end
+
+class NameWithAnonymousClassTest < ActiveModel::TestCase
+ def test_anonymous_class_without_name_argument
+ assert_raises(ArgumentError) do
+ ActiveModel::Name.new(Class.new)
+ end
+ end
+
+ def test_anonymous_class_with_name_argument
+ model_name = ActiveModel::Name.new(Class.new, nil, "Anonymous")
+ assert_equal "Anonymous", model_name
+ end
+end
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index f6ec24ae57..ade6026602 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -14,8 +14,8 @@ class FooObserver < ActiveModel::Observer
attr_accessor :stub
- def on_spec(record)
- stub.event_with(record) if stub
+ def on_spec(record, *args)
+ stub.event_with(record, *args) if stub
end
def around_save(record)
@@ -70,23 +70,38 @@ class ObservingTest < ActiveModel::TestCase
ObservedModel.instantiate_observers
end
+ test "raises an appropriate error when a developer accidentally adds the wrong class (i.e. Widget instead of WidgetObserver)" do
+ assert_raise ArgumentError do
+ ObservedModel.observers = ['string']
+ ObservedModel.instantiate_observers
+ end
+ assert_raise ArgumentError do
+ ObservedModel.observers = [:string]
+ ObservedModel.instantiate_observers
+ end
+ assert_raise ArgumentError do
+ ObservedModel.observers = [String]
+ ObservedModel.instantiate_observers
+ end
+ end
+
test "passes observers to subclasses" do
FooObserver.instance
bar = Class.new(Foo)
- assert_equal Foo.count_observers, bar.count_observers
+ assert_equal Foo.observers_count, bar.observers_count
end
end
class ObserverTest < ActiveModel::TestCase
def setup
ObservedModel.observers = :foo_observer
- FooObserver.instance_eval do
+ FooObserver.singleton_class.instance_eval do
alias_method :original_observed_classes, :observed_classes
end
end
def teardown
- FooObserver.instance_eval do
+ FooObserver.singleton_class.instance_eval do
undef_method :observed_classes
alias_method :observed_classes, :original_observed_classes
end
@@ -98,44 +113,51 @@ class ObserverTest < ActiveModel::TestCase
test "tracks implicit observable models" do
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(Foo), "Foo not in #{instance.send(:observed_classes).inspect}"
- assert !instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{instance.send(:observed_classes).inspect}"
+ assert_equal [Foo], instance.observed_classes
end
test "tracks explicit observed model class" do
- old_instance = FooObserver.new
- assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
FooObserver.observe ObservedModel
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ assert_equal [ObservedModel], instance.observed_classes
end
test "tracks explicit observed model as string" do
- old_instance = FooObserver.new
- assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
FooObserver.observe 'observed_model'
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ assert_equal [ObservedModel], instance.observed_classes
end
test "tracks explicit observed model as symbol" do
- old_instance = FooObserver.new
- assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
FooObserver.observe :observed_model
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ assert_equal [ObservedModel], instance.observed_classes
end
test "calls existing observer event" do
foo = Foo.new
FooObserver.instance.stub = stub
FooObserver.instance.stub.expects(:event_with).with(foo)
- Foo.send(:notify_observers, :on_spec, foo)
+ Foo.notify_observers(:on_spec, foo)
+ end
+
+ test "calls existing observer event from the instance" do
+ foo = Foo.new
+ FooObserver.instance.stub = stub
+ FooObserver.instance.stub.expects(:event_with).with(foo)
+ foo.notify_observers(:on_spec)
+ end
+
+ test "passes extra arguments" do
+ foo = Foo.new
+ FooObserver.instance.stub = stub
+ FooObserver.instance.stub.expects(:event_with).with(foo, :bar)
+ Foo.send(:notify_observers, :on_spec, foo, :bar)
end
test "skips nonexistent observer event" do
foo = Foo.new
- Foo.send(:notify_observers, :whatever, foo)
+ Foo.notify_observers(:whatever, foo)
end
test "update passes a block on to the observer" do
@@ -145,4 +167,15 @@ class ObserverTest < ActiveModel::TestCase
end
assert_equal :in_around_save, yielded_value
end
+
+ test "observe redefines observed_classes class method" do
+ class BarObserver < ActiveModel::Observer
+ observe :foo
+ end
+
+ assert_equal [Foo], BarObserver.observed_classes
+
+ BarObserver.observe(ObservedModel)
+ assert_equal [ObservedModel], BarObserver.observed_classes
+ end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 4338a3fc53..5f18909301 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -7,28 +7,38 @@ class SecurePasswordTest < ActiveModel::TestCase
setup do
@user = User.new
+ @visitor = Visitor.new
end
test "blank password" do
- @user.password = ''
- assert !@user.valid?, 'user should be invalid'
+ @user.password = @visitor.password = ''
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert @visitor.valid?(:create), 'visitor should be valid'
end
test "nil password" do
- @user.password = nil
- assert !@user.valid?, 'user should be invalid'
+ @user.password = @visitor.password = nil
+ assert !@user.valid?(:create), 'user should be invalid'
+ assert @visitor.valid?(:create), 'visitor should be valid'
+ end
+
+ test "blank password doesn't override previous password" do
+ @user.password = 'test'
+ @user.password = ''
+ assert_equal @user.password, 'test'
end
test "password must be present" do
- assert !@user.valid?
+ assert !@user.valid?(:create)
assert_equal 1, @user.errors.size
end
- test "password must match confirmation" do
- @user.password = "thiswillberight"
- @user.password_confirmation = "wrong"
+ test "match confirmation" do
+ @user.password = @visitor.password = "thiswillberight"
+ @user.password_confirmation = @visitor.password_confirmation = "wrong"
assert !@user.valid?
+ assert @visitor.valid?
@user.password_confirmation = "thiswillberight"
@@ -53,4 +63,14 @@ class SecurePasswordTest < ActiveModel::TestCase
assert !active_authorizer.include?(:password_digest)
assert active_authorizer.include?(:name)
end
+
+ test "User should not be created with blank digest" do
+ assert_raise RuntimeError do
+ @user.run_callbacks :create
+ end
+ @user.password = "supersecretpassword"
+ assert_nothing_raised do
+ @user.run_callbacks :create
+ end
+ end
end
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb
index b8dad9d51f..d2ba9fd95d 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serialization_test.rb
@@ -43,38 +43,38 @@ class SerializationTest < ActiveModel::TestCase
end
def test_method_serializable_hash_should_work
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected , @user.serializable_hash
+ expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
+ assert_equal expected, @user.serializable_hash
end
def test_method_serializable_hash_should_work_with_only_option
- expected = {"name"=>"David"}
- assert_equal expected , @user.serializable_hash(:only => [:name])
+ expected = {"name"=>"David"}
+ assert_equal expected, @user.serializable_hash(:only => [:name])
end
def test_method_serializable_hash_should_work_with_except_option
- expected = {"gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected , @user.serializable_hash(:except => [:name])
+ expected = {"gender"=>"male", "email"=>"david@example.com"}
+ assert_equal expected, @user.serializable_hash(:except => [:name])
end
def test_method_serializable_hash_should_work_with_methods_option
- expected = {"name"=>"David", "gender"=>"male", :foo=>"i_am_foo", "email"=>"david@example.com"}
- assert_equal expected , @user.serializable_hash(:methods => [:foo])
+ expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "email"=>"david@example.com"}
+ assert_equal expected, @user.serializable_hash(:methods => [:foo])
end
def test_method_serializable_hash_should_work_with_only_and_methods
- expected = {:foo=>"i_am_foo"}
- assert_equal expected , @user.serializable_hash(:only => [], :methods => [:foo])
+ expected = {"foo"=>"i_am_foo"}
+ assert_equal expected, @user.serializable_hash(:only => [], :methods => [:foo])
end
def test_method_serializable_hash_should_work_with_except_and_methods
- expected = {"gender"=>"male", :foo=>"i_am_foo"}
- assert_equal expected , @user.serializable_hash(:except => [:name, :email], :methods => [:foo])
+ expected = {"gender"=>"male", "foo"=>"i_am_foo"}
+ assert_equal expected, @user.serializable_hash(:except => [:name, :email], :methods => [:foo])
end
def test_should_not_call_methods_that_dont_respond
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
- assert_equal expected , @user.serializable_hash(:methods => [:bar])
+ expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"}
+ assert_equal expected, @user.serializable_hash(:methods => [:bar])
end
def test_should_use_read_attribute_for_serialization
@@ -87,65 +87,82 @@ class SerializationTest < ActiveModel::TestCase
end
def test_include_option_with_singular_association
- expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com",
- :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}}
- assert_equal expected , @user.serializable_hash(:include => :address)
+ expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com",
+ "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}}
+ assert_equal expected, @user.serializable_hash(:include => :address)
end
def test_include_option_with_plural_association
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
- {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
- assert_equal expected , @user.serializable_hash(:include => :friends)
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected, @user.serializable_hash(:include => :friends)
end
def test_include_option_with_empty_association
@user.friends = []
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]}
- assert_equal expected , @user.serializable_hash(:include => :friends)
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", "friends"=>[]}
+ assert_equal expected, @user.serializable_hash(:include => :friends)
+ end
+
+ class FriendList
+ def initialize(friends)
+ @friends = friends
+ end
+
+ def to_ary
+ @friends
+ end
+ end
+
+ def test_include_option_with_ary
+ @user.friends = FriendList.new(@user.friends)
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected, @user.serializable_hash(:include => :friends)
end
def test_multiple_includes
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111},
- :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
- {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
- assert_equal expected , @user.serializable_hash(:include => [:address, :friends])
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111},
+ "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected, @user.serializable_hash(:include => [:address, :friends])
end
def test_include_with_options
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- :address=>{"street"=>"123 Lane"}}
- assert_equal expected , @user.serializable_hash(:include => {:address => {:only => "street"}})
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ "address"=>{"street"=>"123 Lane"}}
+ assert_equal expected, @user.serializable_hash(:include => {:address => {:only => "street"}})
end
def test_nested_include
@user.friends.first.friends = [@user]
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male',
- :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]},
- {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]}
- assert_equal expected , @user.serializable_hash(:include => {:friends => {:include => :friends}})
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male',
+ "friends"=> [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', "friends"=> []}]}
+ assert_equal expected, @user.serializable_hash(:include => {:friends => {:include => :friends}})
end
def test_only_include
- expected = {"name"=>"David", :friends => [{"name" => "Joe"}, {"name" => "Sue"}]}
- assert_equal expected , @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}})
+ expected = {"name"=>"David", "friends" => [{"name" => "Joe"}, {"name" => "Sue"}]}
+ assert_equal expected, @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}})
end
def test_except_include
expected = {"name"=>"David", "email"=>"david@example.com",
- :friends => [{"name" => 'Joe', "email" => 'joe@example.com'},
- {"name" => "Sue", "email" => 'sue@example.com'}]}
- assert_equal expected , @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}})
+ "friends"=> [{"name" => 'Joe', "email" => 'joe@example.com'},
+ {"name" => "Sue", "email" => 'sue@example.com'}]}
+ assert_equal expected, @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}})
end
def test_multiple_includes_with_options
- expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
- :address=>{"street"=>"123 Lane"},
- :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
- {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
- assert_equal expected , @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends])
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ "address"=>{"street"=>"123 Lane"},
+ "friends"=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected, @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends])
end
-
end
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index a754d610b9..7160635eb4 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -14,9 +14,11 @@ class Contact
end
end
+ remove_method :attributes if method_defined?(:attributes)
+
def attributes
instance_values
- end unless method_defined?(:attributes)
+ end
end
class JsonSerializationTest < ActiveModel::TestCase
@@ -128,13 +130,13 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end
- test "should return OrderedHash for errors" do
+ test "should return Hash for errors" do
contact = Contact.new
contact.errors.add :name, "can't be blank"
contact.errors.add :name, "is too short (minimum is 2 characters)"
contact.errors.add :age, "must be 16 or over"
- hash = ActiveSupport::OrderedHash.new
+ hash = {}
hash[:name] = ["can't be blank", "is too short (minimum is 2 characters)"]
hash[:age] = ["must be 16 or over"]
assert_equal hash.to_json, contact.errors.to_json
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index fc73d9dcd8..5fa227e0e0 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -9,6 +9,8 @@ class Contact
attr_accessor :address, :friends
+ remove_method :attributes if method_defined?(:attributes)
+
def attributes
instance_values.except("address", "friends")
end
@@ -186,6 +188,23 @@ class XmlSerializationTest < ActiveModel::TestCase
assert_match %r{<friend type="Contact">}, xml
end
+ class FriendList
+ def initialize(friends)
+ @friends = friends
+ end
+
+ def to_ary
+ @friends
+ end
+ end
+
+ test "include option with ary" do
+ @contact.friends = FriendList.new(@contact.friends)
+ xml = @contact.to_xml :include => :friends, :indent => 0
+ assert_match %r{<friends type="array">}, xml
+ assert_match %r{<friend type="Contact">}, xml
+ end
+
test "multiple includes" do
xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => [ :address, :friends ]
assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true))
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index 1b1d972d5c..4999583802 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -56,6 +56,16 @@ class ActiveModelI18nTests < ActiveModel::TestCase
assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute')
end
+ def test_translated_nested_model_attributes
+ I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/addresses" => {:street => 'Person Address Street'}}}
+ assert_equal 'Person Address Street', Person.human_attribute_name('addresses.street')
+ end
+
+ def test_translated_nested_model_attributes_with_namespace_fallback
+ I18n.backend.store_translations 'en', :activemodel => {:attributes => {:addresses => {:street => 'Cool Address Street'}}}
+ assert_equal 'Cool Address Street', Person.human_attribute_name('addresses.street')
+ end
+
def test_translated_model_names
I18n.backend.store_translations 'en', :activemodel => {:models => {:person => 'person model'} }
assert_equal 'person model', Person.model_name.human
@@ -72,9 +82,15 @@ class ActiveModelI18nTests < ActiveModel::TestCase
end
def test_human_does_not_modify_options
- options = {:default => 'person model'}
+ options = { :default => 'person model' }
Person.model_name.human(options)
- assert_equal({:default => 'person model'}, options)
+ assert_equal({ :default => 'person model' }, options)
+ end
+
+ def test_human_attribute_name_does_not_modify_options
+ options = { :default => 'Cool gender' }
+ Person.human_attribute_name('gender', options)
+ assert_equal({ :default => 'Cool gender' }, options)
end
end
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
index 1cf09758f9..e4f602bd80 100644
--- a/activemodel/test/cases/validations/callbacks_test.rb
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -5,11 +5,10 @@ class Dog
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
- attr_accessor :name
- attr_writer :history
+ attr_accessor :name, :history
- def history
- @history ||= []
+ def initialize
+ @history = []
end
end
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index d0418170fa..f7556a249f 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -44,7 +44,7 @@ class ConfirmationValidationTest < ActiveModel::TestCase
p.karma_confirmation = "None"
assert p.invalid?
- assert_equal ["doesn't match confirmation"], p.errors[:karma]
+ assert_equal ["doesn't match Karma"], p.errors[:karma_confirmation]
p.karma = "None"
assert p.valid?
@@ -52,4 +52,23 @@ class ConfirmationValidationTest < ActiveModel::TestCase
Person.reset_callbacks(:validate)
end
+ def test_title_confirmation_with_i18n_attribute
+ @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
+ I18n.load_path.clear
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations('en', {
+ :errors => {:messages => {:confirmation => "doesn't match %{attribute}"}},
+ :activemodel => {:attributes => {:topic => {:title => 'Test Title'}}}
+ })
+
+ Topic.validates_confirmation_of(:title)
+
+ t = Topic.new("title" => "We should be confirmed","title_confirmation" => "")
+ assert t.invalid?
+ assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation]
+
+ I18n.load_path.replace @old_load_path
+ I18n.backend = @old_backend
+ end
+
end
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index 72a383f128..adab8ccb2b 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -46,12 +46,12 @@ class ExclusionValidationTest < ActiveModel::TestCase
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?
+ t = Topic.new
+ t.title = "elephant"
+ t.author_name = "sikachu"
+ assert t.invalid?
- p.title = "wasabi"
- assert p.valid?
+ t.title = "wasabi"
+ assert t.valid?
end
end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 2ce714fef0..41a1131bcb 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -101,25 +101,25 @@ class PresenceValidationTest < ActiveModel::TestCase
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?
+ t = Topic.new
+ t.title = "digit"
+ t.content = "Pixies"
+ assert t.invalid?
- p.content = "1234"
- assert p.valid?
+ t.content = "1234"
+ assert t.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?
+ t = Topic.new
+ t.title = "characters"
+ t.content = "1234"
+ assert t.invalid?
- p.content = "Pixies"
- assert p.valid?
+ t.content = "Pixies"
+ assert t.valid?
end
def test_validates_format_of_for_ruby_class
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 0679e67f84..df0fcd243a 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -37,7 +37,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
# validates_confirmation_of: generate_message(attr_name, :confirmation, :message => custom_message)
def test_generate_message_confirmation_with_default_message
- assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation)
+ assert_equal "doesn't match Title", @person.errors.generate_message(:title, :confirmation)
end
def test_generate_message_confirmation_with_custom_message
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index e9f0e430fe..6b6aad3bd1 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -81,7 +81,7 @@ class I18nValidationTest < ActiveModel::TestCase
test "validates_confirmation_of on generated message #{name}" do
Person.validates_confirmation_of :title, validation_options
@person.title_confirmation = 'foo'
- @person.errors.expects(:generate_message).with(:title, :confirmation, generate_message_options)
+ @person.errors.expects(:generate_message).with(:title_confirmation, :confirmation, generate_message_options.merge(:attribute => 'Title'))
@person.valid?
end
end
@@ -217,24 +217,29 @@ class I18nValidationTest < ActiveModel::TestCase
# To make things DRY this macro is defined to define 3 tests for every validation case.
def self.set_expectations_for_validation(validation, error_type, &block_that_sets_validation)
+ if error_type == :confirmation
+ attribute = :title_confirmation
+ else
+ attribute = :title
+ end
# test "validates_confirmation_of finds custom model key translation when blank"
test "#{validation} finds custom model key translation when #{error_type}" do
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
yield(@person, {})
@person.valid?
- assert_equal ['custom message'], @person.errors[:title]
+ assert_equal ['custom message'], @person.errors[attribute]
end
# test "validates_confirmation_of finds custom model key translation with interpolation when blank"
test "#{validation} finds custom model key translation with interpolation when #{error_type}" do
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message with %{extra}'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message with %{extra}'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
yield(@person, {:extra => "extra information"})
@person.valid?
- assert_equal ['custom message with extra information'], @person.errors[:title]
+ assert_equal ['custom message with extra information'], @person.errors[attribute]
end
# test "validates_confirmation_of finds global default key translation when blank"
@@ -243,7 +248,7 @@ class I18nValidationTest < ActiveModel::TestCase
yield(@person, {})
@person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ assert_equal ['global message'], @person.errors[attribute]
end
end
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 413da92de4..851d345eab 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -78,12 +78,12 @@ class InclusionValidationTest < ActiveModel::TestCase
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?
+ t = Topic.new
+ t.title = "wasabi"
+ t.author_name = "sikachu"
+ assert t.invalid?
- p.title = "elephant"
- assert p.valid?
+ t.title = "elephant"
+ assert t.valid?
end
end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 44048a9c1d..113bfd6337 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -260,74 +260,64 @@ class LengthValidationTest < ActiveModel::TestCase
end
def test_validates_length_of_using_minimum_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of :title, :minimum => 5
+ Topic.validates_length_of :title, :minimum => 5
- t = Topic.new("title" => "一二三四五", "content" => "whatever")
- assert t.valid?
+ t = Topic.new("title" => "一二三四五", "content" => "whatever")
+ assert t.valid?
- t.title = "一二三四"
- assert t.invalid?
- assert t.errors[:title].any?
- assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
- end
+ t.title = "一二三四"
+ assert t.invalid?
+ assert t.errors[:title].any?
+ assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
end
def test_validates_length_of_using_maximum_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of :title, :maximum => 5
+ Topic.validates_length_of :title, :maximum => 5
- t = Topic.new("title" => "一二三四五", "content" => "whatever")
- assert t.valid?
+ t = Topic.new("title" => "一二三四五", "content" => "whatever")
+ assert t.valid?
- t.title = "一二34五六"
- assert t.invalid?
- assert t.errors[:title].any?
- assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"]
- end
+ t.title = "一二34五六"
+ assert t.invalid?
+ assert t.errors[:title].any?
+ assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"]
end
def test_validates_length_of_using_within_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of(:title, :content, :within => 3..5)
-
- t = Topic.new("title" => "一二", "content" => "12三四五六七")
- assert t.invalid?
- assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
- assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content]
- t.title = "一二三"
- t.content = "12三"
- assert t.valid?
- end
+ Topic.validates_length_of(:title, :content, :within => 3..5)
+
+ t = Topic.new("title" => "一二", "content" => "12三四五六七")
+ assert t.invalid?
+ assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title]
+ assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content]
+ t.title = "一二三"
+ t.content = "12三"
+ assert t.valid?
end
def test_optionally_validates_length_of_using_within_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of :title, :within => 3..5, :allow_nil => true
+ Topic.validates_length_of :title, :within => 3..5, :allow_nil => true
- t = Topic.new(:title => "一二三四五")
- assert t.valid?, t.errors.inspect
+ t = Topic.new(:title => "一二三四五")
+ assert t.valid?, t.errors.inspect
- t = Topic.new(:title => "一二三")
- assert t.valid?, t.errors.inspect
+ t = Topic.new(:title => "一二三")
+ assert t.valid?, t.errors.inspect
- t.title = nil
- assert t.valid?, t.errors.inspect
- end
+ t.title = nil
+ assert t.valid?, t.errors.inspect
end
def test_validates_length_of_using_is_utf8
- with_kcode('UTF8') do
- Topic.validates_length_of :title, :is => 5
+ Topic.validates_length_of :title, :is => 5
- t = Topic.new("title" => "一二345", "content" => "whatever")
- assert t.valid?
+ t = Topic.new("title" => "一二345", "content" => "whatever")
+ assert t.valid?
- t.title = "一二345六"
- assert t.invalid?
- assert t.errors[:title].any?
- assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"]
- end
+ t.title = "一二345六"
+ assert t.invalid?
+ assert t.errors[:title].any?
+ assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"]
end
def test_validates_length_of_with_block
@@ -367,4 +357,22 @@ class LengthValidationTest < ActiveModel::TestCase
ensure
Person.reset_callbacks(:validate)
end
+
+ def test_validates_length_of_for_infinite_maxima
+ Topic.validates_length_of(:title, :within => 5..Float::INFINITY)
+
+ t = Topic.new("title" => "1234")
+ assert t.invalid?
+ assert t.errors[:title].any?
+
+ t.title = "12345"
+ assert t.valid?
+
+ Topic.validates_length_of(:author_name, :maximum => Float::INFINITY)
+
+ assert t.valid?
+
+ t.author_name = "A very long author name that should still be valid." * 100
+ assert t.valid?
+ end
end
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 08f6169ca5..6742a4bab0 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -106,6 +106,13 @@ class NumericalityValidationTest < ActiveModel::TestCase
valid!([2])
end
+ def test_validates_numericality_with_other_than
+ Topic.validates_numericality_of :approved, :other_than => 0
+
+ invalid!([0, 0.0])
+ valid!([-1, 42])
+ end
+
def test_validates_numericality_with_proc
Topic.send(:define_method, :min_approved, lambda { 5 })
Topic.validates_numericality_of :approved, :greater_than_or_equal_to => Proc.new {|topic| topic.min_approved }
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
index 779f6c8448..90bc018ae1 100644
--- a/activemodel/test/cases/validations/validates_test.rb
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -16,6 +16,12 @@ class ValidatesTest < ActiveModel::TestCase
PersonWithValidator.reset_callbacks(:validate)
end
+ def test_validates_with_messages_empty
+ Person.validates :title, :presence => {:message => "" }
+ person = Person.new
+ assert !person.valid?, 'person should not be valid.'
+ end
+
def test_validates_with_built_in_validation
Person.validates :title, :numericality => true
person = Person.new
@@ -148,6 +154,6 @@ class ValidatesTest < ActiveModel::TestCase
topic.title = "What's happening"
topic.title_confirmation = "Not this"
assert !topic.valid?
- assert_equal ['Y U NO CONFIRM'], topic.errors[:title]
+ assert_equal ['Y U NO CONFIRM'], topic.errors[:title_confirmation]
end
end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 2f4376bd41..1f5023bf76 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -58,8 +58,7 @@ class ValidationsTest < ActiveModel::TestCase
r = Reply.new
r.valid?
- errors = []
- r.errors.each {|attr, messages| errors << [attr.to_s, messages] }
+ errors = r.errors.collect {|attr, messages| [attr.to_s, messages]}
assert errors.include?(["title", "is Empty"])
assert errors.include?(["content", "is Empty"])
@@ -181,7 +180,7 @@ class ValidationsTest < ActiveModel::TestCase
assert_match %r{<error>Title can't be blank</error>}, xml
assert_match %r{<error>Content can't be blank</error>}, xml
- hash = ActiveSupport::OrderedHash.new
+ hash = {}
hash[:title] = ["can't be blank"]
hash[:content] = ["can't be blank"]
assert_equal t.errors.to_json, hash.to_json
@@ -311,7 +310,7 @@ class ValidationsTest < ActiveModel::TestCase
end
def test_strict_validation_particular_validator
- Topic.validates :title, :presence => {:strict => true}
+ Topic.validates :title, :presence => { :strict => true }
assert_raises ActiveModel::StrictValidationFailed do
Topic.new.valid?
end
@@ -330,4 +329,34 @@ class ValidationsTest < ActiveModel::TestCase
Topic.new.valid?
end
end
+
+ def test_strict_validation_error_message
+ Topic.validates :title, :strict => true, :presence => true
+
+ exception = assert_raises(ActiveModel::StrictValidationFailed) do
+ Topic.new.valid?
+ end
+ assert_equal "Title can't be blank", exception.message
+ end
+
+ def test_does_not_modify_options_argument
+ options = { :presence => true }
+ Topic.validates :title, options
+ assert_equal({ :presence => true }, options)
+ end
+
+ def test_dup_validity_is_independent
+ Topic.validates_presence_of :title
+ topic = Topic.new("title" => "Litterature")
+ topic.valid?
+
+ duped = topic.dup
+ duped.title = nil
+ assert duped.invalid?
+
+ topic.title = nil
+ duped.title = 'Mathematics'
+ assert topic.invalid?
+ assert duped.valid?
+ end
end
diff --git a/activemodel/test/models/administrator.rb b/activemodel/test/models/administrator.rb
index a48f8b064f..2d6d34b3e2 100644
--- a/activemodel/test/models/administrator.rb
+++ b/activemodel/test/models/administrator.rb
@@ -1,7 +1,10 @@
class Administrator
+ extend ActiveModel::Callbacks
include ActiveModel::Validations
include ActiveModel::SecurePassword
include ActiveModel::MassAssignmentSecurity
+
+ define_model_callbacks :create
attr_accessor :name, :password_digest
attr_accessible :name
diff --git a/activemodel/test/models/blog_post.rb b/activemodel/test/models/blog_post.rb
index d289177259..46eba857df 100644
--- a/activemodel/test/models/blog_post.rb
+++ b/activemodel/test/models/blog_post.rb
@@ -1,10 +1,6 @@
module Blog
- def self._railtie
- Object.new
- end
-
- def self.table_name_prefix
- "blog_"
+ def self.use_relative_model_naming?
+ true
end
class Post
diff --git a/activemodel/test/models/user.rb b/activemodel/test/models/user.rb
index e221bb8091..4b11df12bf 100644
--- a/activemodel/test/models/user.rb
+++ b/activemodel/test/models/user.rb
@@ -1,6 +1,9 @@
class User
+ extend ActiveModel::Callbacks
include ActiveModel::Validations
include ActiveModel::SecurePassword
+
+ define_model_callbacks :create
has_secure_password
diff --git a/activemodel/test/models/visitor.rb b/activemodel/test/models/visitor.rb
index 36c0a16688..d15f448516 100644
--- a/activemodel/test/models/visitor.rb
+++ b/activemodel/test/models/visitor.rb
@@ -1,9 +1,12 @@
class Visitor
+ extend ActiveModel::Callbacks
include ActiveModel::Validations
include ActiveModel::SecurePassword
include ActiveModel::MassAssignmentSecurity
+
+ define_model_callbacks :create
- has_secure_password
+ has_secure_password(validations: false)
- attr_accessor :password_digest
+ attr_accessor :password_digest, :password_confirmation
end
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
deleted file mode 100644
index 46076dac61..0000000000
--- a/activerecord/CHANGELOG
+++ /dev/null
@@ -1,6569 +0,0 @@
-*Rails 3.2.0 (unreleased)*
-
-* Added ActiveRecord::Base.store for declaring simple single-column key/value stores [DHH]
-
- class User < ActiveRecord::Base
- store :settings, accessors: [ :color, :homepage ]
- end
-
- u = User.new(color: 'black', homepage: '37signals.com')
- u.color # Accessor stored attribute
- u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
-
-
-* MySQL: case-insensitive uniqueness validation avoids calling LOWER when
- the column already uses a case-insensitive collation. Fixes #561.
-
- [Joseph Palermo]
-
-* Transactional fixtures enlist all active database connections. You can test
- models on different connections without disabling transactional fixtures.
-
- [Jeremy Kemper]
-
-* Add first_or_create, first_or_create!, first_or_initialize methods to Active Record. This is a
- better approach over the old find_or_create_by dynamic methods because it's clearer which
- arguments are used to find the record and which are used to create it:
-
- User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson")
-
- [Andrés Mejía]
-
-* Fix nested attributes bug where _destroy parameter is taken into account
- during :reject_if => :all_blank (fixes #2937)
-
- [Aaron Christy]
-
-*Rails 3.1.1 (October 7, 2011)*
-
-* Add deprecation for the preload_associations method. Fixes #3022.
-
- [Jon Leighton]
-
-* Don't require a DB connection when loading a model that uses set_primary_key. GH #2807.
-
- [Jon Leighton]
-
-* Fix using select() with a habtm association, e.g. Person.friends.select(:name). GH #3030 and
- #2923.
-
- [Hendy Tanata]
-
-* Fix belongs_to polymorphic with custom primary key on target. GH #3104.
-
- [Jon Leighton]
-
-* CollectionProxy#replace should change the DB records rather than just mutating the array.
- Fixes #3020.
-
- [Jon Leighton]
-
-* LRU cache in mysql and sqlite are now per-process caches.
-
- * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache
- keys are per process id.
- * lib/active_record/connection_adapters/sqlite_adapter.rb: ditto
-
- [Aaron Patterson]
-
-* Support bulk change_table in mysql2 adapter, as well as the mysql one. [Jon Leighton]
-
-* If multiple parameters are sent representing a date, and some are blank, the
-resulting object is nil. In previous releases those values defaulted to 1. This
-only affects existing but blank parameters, missing ones still raise an error.
-[Akira Matsuda]
-
-* ActiveRecord::Base.establish_connection now takes a string that contains
-a URI that specifies the connection configuration. For example:
-
- ActiveRecord::Base.establish_connection 'postgres://localhost/foo'
-
-* Active Record's dynamic finder will now raise the error if you passing in less number of arguments than what you call in method signature.
-
- So if you were doing this and expecting the second argument to be nil:
-
- User.find_by_username_and_group("sikachu")
-
- You'll now get `ArgumentError: wrong number of arguments (1 for 2).` You'll then have to do this:
-
- User.find_by_username_and_group("sikachu", nil)
-
- [Prem Sichanugrist]
-
-
-*Rails 3.1.0 (August 30, 2011)*
-
-* Add a proxy_association method to association proxies, which can be called by association
- extensions to access information about the association. This replaces proxy_owner etc with
- proxy_association.owner.
-
- [Jon Leighton]
-
-* ActiveRecord::MacroReflection::AssociationReflection#build_record has a new method signature.
-
- Before: def build_association(*options)
- After: def build_association(*options, &block)
-
- Users who are redefining this method to extend functionality should ensure that the block is
- passed through to ActiveRecord::Base#new.
-
- This change is necessary to fix https://github.com/rails/rails/issues/1842.
-
- [Jon Leighton]
-
-* AR#pluralize_table_names can be used to singularize/pluralize table name of an individual model:
-
- class User < ActiveRecord::Base
- self.pluralize_table_names = false
- end
-
- Previously this could only be set globally for all models through ActiveRecord::Base.pluralize_table_names. [Guillermo Iguaran]
-
-* Add block setting of attributes to singular associations:
-
- class User < ActiveRecord::Base
- has_one :account
- end
-
- user.build_account{ |a| a.credit_limit => 100.0 }
-
- The block is called after the instance has been initialized. [Andrew White]
-
-* Add ActiveRecord::Base.attribute_names to return a list of attribute names. This will return an empty array if the model is abstract or table does not exists. [Prem Sichanugrist]
-
-* CSV Fixtures are deprecated and support will be removed in Rails 3.2.0
-
-* AR#new, AR#create, AR#create!, AR#update_attributes and AR#update_attributes! all accept a second hash as option that allows you
- to specify which role to consider when assigning attributes. This is built on top of ActiveModel's
- new mass assignment capabilities:
-
- class Post < ActiveRecord::Base
- attr_accessible :title
- attr_accessible :title, :published_at, :as => :admin
- end
-
- Post.new(params[:post], :as => :admin)
-
- assign_attributes() with similar API was also added and attributes=(params, guard) was deprecated.
-
- Please note that this changes the method signatures for AR#new, AR#create, AR#create!, AR#update_attributes and AR#update_attributes!. If you have overwritten these methods you should update them accordingly.
-
- [Josh Kalderimis]
-
-* default_scope can take a block, lambda, or any other object which responds to `call` for lazy
- evaluation:
-
- default_scope { ... }
- default_scope lambda { ... }
- default_scope method(:foo)
-
- This feature was originally implemented by Tim Morgan, but was then removed in favour of
- defining a 'default_scope' class method, but has now been added back in by Jon Leighton.
- The relevant lighthouse ticket is #1812.
-
-* 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]
-
-* If you wish to merge default scopes in special ways, it is recommended to define your default
- scope as a class method and use the standard techniques for sharing code (inheritance, mixins,
- etc.):
-
- class Post < ActiveRecord::Base
- def self.default_scope
- where(:published => true).where(:hidden => false)
- end
- end
-
- [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.
-
-* Added an update_column method on ActiveRecord. This new method updates a given attribute on an object, skipping validations and callbacks.
- It is recommended to use #update_attribute unless you are sure you do not want to execute any callback, including the modification of
- the updated_at column. It should not be called on new records.
- Example:
-
- User.first.update_column(:name, "sebastian") # => true
-
- [Sebastian Martinez]
-
-* Associations with a :through option can now use *any* association as the
- through or source association, including other associations which have a
- :through option and has_and_belongs_to_many associations
-
- [Jon Leighton]
-
-* The configuration for the current database connection is now accessible via
- ActiveRecord::Base.connection_config. [fxn]
-
-* limits and offsets are removed from COUNT queries unless both are supplied.
- For example:
-
- People.limit(1).count # => 'SELECT COUNT(*) FROM people'
- People.offset(1).count # => 'SELECT COUNT(*) FROM people'
- People.limit(1).offset(1).count # => 'SELECT COUNT(*) FROM people LIMIT 1 OFFSET 1'
-
- [lighthouse #6262]
-
-* ActiveRecord::Associations::AssociationProxy has been split. There is now an Association class
- (and subclasses) which are responsible for operating on associations, and then a separate,
- thin wrapper called CollectionProxy, which proxies collection associations.
-
- This prevents namespace pollution, separates concerns, and will allow further refactorings.
-
- Singular associations (has_one, belongs_to) no longer have a proxy at all. They simply return
- the associated record or nil. This means that you should not use undocumented methods such
- as bob.mother.create - use bob.create_mother instead.
-
- [Jon Leighton]
-
-* Make has_many :through associations work correctly when you build a record and then save it. This
- requires you to set the :inverse_of option on the source reflection on the join model, like so:
-
- class Post < ActiveRecord::Base
- has_many :taggings
- has_many :tags, :through => :taggings
- end
-
- class Tagging < ActiveRecord::Base
- belongs_to :post
- belongs_to :tag, :inverse_of => :tagging # :inverse_of must be set!
- end
-
- class Tag < ActiveRecord::Base
- has_many :taggings
- has_many :posts, :through => :taggings
- end
-
- post = Post.first
- tag = post.tags.build :name => "ruby"
- tag.save # will save a Taggable linking to the post
-
- [Jon Leighton]
-
-* Support the :dependent option on has_many :through associations. For historical and practical
- reasons, :delete_all is the default deletion strategy employed by association.delete(*records),
- despite the fact that the default strategy is :nullify for regular has_many. Also, this only
- works at all if the source reflection is a belongs_to. For other situations, you should directly
- modify the through association.
-
- [Jon Leighton]
-
-* Changed the behaviour of association.destroy for has_and_belongs_to_many and has_many :through.
- From now on, 'destroy' or 'delete' on an association will be taken to mean 'get rid of the link',
- not (necessarily) 'get rid of the associated records'.
-
- Previously, has_and_belongs_to_many.destroy(*records) would destroy the records themselves. It
- would not delete any records in the join table. Now, it deletes the records in the join table.
-
- Previously, has_many_through.destroy(*records) would destroy the records themselves, and the
- records in the join table. [Note: This has not always been the case; previous version of Rails
- only deleted the records themselves.] Now, it destroys only the records in the join table.
-
- Note that this change is backwards-incompatible to an extent, but there is unfortunately no
- way to 'deprecate' it before changing it. The change is being made in order to have
- consistency as to the meaning of 'destroy' or 'delete' across the different types of associations.
-
- If you wish to destroy the records themselves, you can do records.association.each(&:destroy)
-
- [Jon Leighton]
-
-* Add :bulk => true option to change_table to make all the schema changes defined in change_table block using a single ALTER statement. [Pratik Naik]
-
- Example:
-
- change_table(:users, :bulk => true) do |t|
- t.string :company_name
- t.change :birthdate, :datetime
- end
-
- This will now result in:
-
- ALTER TABLE `users` ADD COLUMN `company_name` varchar(255), CHANGE `updated_at` `updated_at` datetime DEFAULT NULL
-
-* Removed support for accessing attributes on a has_and_belongs_to_many join table. This has been
- documented as deprecated behaviour since April 2006. Please use has_many :through instead.
- [Jon Leighton]
-
-* Added a create_association! method for has_one and belongs_to associations. [Jon Leighton]
-
-* Migration files generated from model and constructive migration generators
- (for example, add_name_to_users) use the reversible migration's `change`
- method instead of the ordinary `up` and `down` methods. [Prem Sichanugrist]
-
-* Removed support for interpolating string SQL conditions on associations. Instead, you should
- use a proc, like so:
-
- Before:
-
- has_many :things, :conditions => 'foo = #{bar}'
-
- After:
-
- has_many :things, :conditions => proc { "foo = #{bar}" }
-
- Inside the proc, 'self' is the object which is the owner of the association, unless you are
- eager loading the association, in which case 'self' is the class which the association is within.
-
- You can have any "normal" conditions inside the proc, so the following will work too:
-
- has_many :things, :conditions => proc { ["foo = ?", bar] }
-
- Previously :insert_sql and :delete_sql on has_and_belongs_to_many association allowed you to call
- 'record' to get the record being inserted or deleted. This is now passed as an argument to
- the proc.
-
-* Added ActiveRecord::Base#has_secure_password (via ActiveModel::SecurePassword) to encapsulate dead-simple password usage with BCrypt encryption and salting [DHH]. Example:
-
- # Schema: User(name:string, password_digest:string, password_salt:string)
- class User < ActiveRecord::Base
- has_secure_password
- end
-
- user = User.new(:name => "david", :password => "", :password_confirmation => "nomatch")
- user.save # => false, password required
- user.password = "mUc3m00RsqyRe"
- user.save # => false, confirmation doesn't match
- user.password_confirmation = "mUc3m00RsqyRe"
- user.save # => true
- user.authenticate("notright") # => false
- user.authenticate("mUc3m00RsqyRe") # => user
- User.find_by_name("david").try(:authenticate, "notright") # => nil
- User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
-
-
-* When a model is generated add_index is added by default for belongs_to or references columns
-
- rails g model post user:belongs_to will generate the following:
-
- class CreatePosts < ActiveRecord::Migration
- def up
- create_table :posts do |t|
- t.belongs_to :user
- t.timestamps
- end
-
- add_index :posts, :user_id
- end
-
- def down
- drop_table :posts
- end
- end
-
- [Santiago Pastorino]
-
-* Setting the id of a belongs_to object will update the reference to the
-object. [#2989 state:resolved]
-
-* ActiveRecord::Base#dup and ActiveRecord::Base#clone semantics have changed
-to closer match normal Ruby dup and clone semantics.
-
-* Calling ActiveRecord::Base#clone will result in a shallow copy of the record,
-including copying the frozen state. No callbacks will be called.
-
-* Calling ActiveRecord::Base#dup will duplicate the record, including calling
-after initialize hooks. Frozen state will not be copied, and all associations
-will be cleared. A duped record will return true for new_record?, have a nil
-id field, and is saveable.
-
-* Migrations can be defined as reversible, meaning that the migration system
-will figure out how to reverse your migration. To use reversible migrations,
-just define the "change" method. For example:
-
- class MyMigration < ActiveRecord::Migration
- def change
- create_table(:horses) do
- t.column :content, :text
- t.column :remind_at, :datetime
- end
- end
- end
-
-Some things cannot be automatically reversed for you. If you know how to
-reverse those things, you should define 'up' and 'down' in your migration. If
-you define something in `change` that cannot be reversed, an
-IrreversibleMigration exception will be raised when going down.
-
-* Migrations should use instance methods rather than class methods:
- class FooMigration < ActiveRecord::Migration
- def up
- ...
- end
- end
-
- [Aaron Patterson]
-
-* has_one maintains the association with separate after_create/after_update instead
- of a single after_save. [fxn]
-
-* The following code:
-
- Model.limit(10).scoping { Model.count }
-
- now generates the following SQL:
-
- SELECT COUNT(*) FROM models LIMIT 10
-
- This may not return what you want. Instead, you may with to do something
- like this:
-
- Model.limit(10).scoping { Model.all.size }
-
- [Aaron Patterson]
-
-
-*Rails 3.0.7 (April 18, 2011)*
-
-* Destroying records via nested attributes works independent of reject_if LH #6006 [Durran Jordan]
-
-* Delegate any? and many? to Model.scoped for consistency [Andrew White]
-
-* Quote the ORDER BY clause in batched finds - fixes #6620 [Andrew White]
-
-* Change exists? so records are not instantiated - fixes #6127. This prevents after_find
- and after_initialize callbacks being triggered when checking for record existence.
- [Andrew White]
-
-* Fix performance bug with attribute accessors which only occurred on Ruby 1.8.7, and ensure we
- cache type-casted values when the column returned from the db contains non-standard chars.
- [Jon Leighton]
-
-* Fix a performance regression introduced here 86acbf1cc050c8fa8c74a10c735e467fb6fd7df8
- related to read_attribute method [Stian Grytøyr]
-
-
-*Rails 3.0.6 (April 5, 2011)*
-
-* Un-deprecate reorder method [Sebastian Martinez]
-
-* Extensions are applied when calling +except+ or +only+ on relations.
- Thanks to Iain Hecker.
-
-* Schemas set in set_table_name are respected by the mysql adapter. LH #5322
-
-* Fixed a bug when empty? was called on a grouped Relation that wasn't loaded.
- LH #5829
-
-* Reapply extensions when using except and only. Thanks Iain Hecker.
-
-* Binary data is escaped when being inserted to SQLite3 Databases. Thanks
- Naruse!
-
-
-*Rails 3.0.5 (February 26, 2011)*
-
-* Model.where(:column => 1).where(:column => 2) will always produce an AND
-query.
-
- [Aaron Patterson]
-
-* Deprecated support for interpolated association conditions in the form of :conditions => 'foo = #{bar}'.
-
- Instead, you should use a proc, like so:
-
- Before:
-
- has_many :things, :conditions => 'foo = #{bar}'
-
- After:
-
- has_many :things, :conditions => proc { "foo = #{bar}" }
-
- Inside the proc, 'self' is the object which is the owner of the association, unless you are
- eager loading the association, in which case 'self' is the class which the association is within.
-
- You can have any "normal" conditions inside the proc, so the following will work too:
-
- has_many :things, :conditions => proc { ["foo = ?", bar] }
-
- Previously :insert_sql and :delete_sql on has_and_belongs_to_many association allowed you to call
- 'record' to get the record being inserted or deleted. This is now passed as an argument to
- the proc.
-
- [Jon Leighton]
-
-
-*Rails 3.0.4 (February 8, 2011)*
-
-* Added deprecation warning for has_and_belongs_to_many associations where the join table has
- additional attributes other than the keys. Access to these attributes is removed in 3.1.
- Please use has_many :through instead. [Jon Leighton]
-
-
-*Rails 3.0.3 (November 16, 2010)*
-
-* Support find by class like this: Post.where(:name => Post)
-
-
-*Rails 3.0.2 (November 15, 2010)*
-
-* Dramatic speed increase (see: http://engineering.attinteractive.com/2010/10/arel-two-point-ohhhhh-yaaaaaa/) [Aaron Patterson]
-
-* reorder is deprecated in favor of except(:order).order(...) [Santiago Pastorino]
-
-* except is now AR public API
-
- Model.order('name').except(:order).order('salary')
-
- generates:
-
- SELECT * FROM models ORDER BY salary
-
- [Santiago Pastorino]
-
-* The following code:
-
- Model.limit(10).scoping { Model.count }
-
- now generates the following SQL:
-
- SELECT COUNT(*) FROM models LIMIT 10
-
- This may not return what you want. Instead, you may with to do something
- like this:
-
- Model.limit(10).scoping { Model.all.size }
-
- [Aaron Patterson]
-
-
-*Rails 3.0.1 (October 15, 2010)*
-
-* Introduce a fix for CVE-2010-3993
-
-
-*Rails 3.0.0 (August 29, 2010)*
-
-* Changed update_attribute to not run callbacks and update the record directly in the database [Neeraj Singh]
-
-* Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope [José Valim]
-
-* New rake task, db:migrate:status, displays status of migrations #4947 [Kevin Skoglund]
-
-* select and order for ActiveRecord now always concatenate nested calls. Use reorder if you want the original order to be overwritten [Santiago Pastorino]
-
-* PostgreSQL: ensure the database time zone matches Ruby's time zone #4895 [Aaron Patterson]
-
-* Fixed that ActiveRecord::Base.compute_type would swallow NoMethodError #4751 [Andrew Bloomgarden, Andrew White]
-
-* Add index length support for MySQL. #1852 [Emili Parreno, Pratik Naik]
-
- Example:
-
- add_index(:accounts, :name, :name => 'by_name', :length => 10)
- => CREATE INDEX by_name ON accounts(name(10))
-
- add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
- => CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
-
-* find_or_create_by_attr(value, ...) works when attr is protected. #4457 [Santiago Pastorino, Marc-André Lafortune]
-
-* New callbacks: after_commit and after_rollback. Do expensive operations like image thumbnailing after_commit instead of after_save. #2991 [Brian Durand]
-
-* Serialized attributes are not converted to YAML if they are any of the formats that can be serialized to XML (like Hash, Array and Strings). [José Valim]
-
-* Destroy uses optimistic locking. If lock_version on the record you're destroying doesn't match lock_version in the database, a StaleObjectError is raised. #1966 [Curtis Hawthorne]
-
-* PostgreSQL: drop support for old postgres driver. Use pg 0.9.0 or later. [Jeremy Kemper]
-
-* Observers can prevent records from saving by returning false, just like before_save and friends. #4087 [Mislav Marohnić]
-
-* Add Relation extensions. [Pratik Naik]
-
- users = User.where(:admin => true).extending(User::AdminPowers)
-
- latest_users = User.order('created_at DESC') do
- def posts_count
- Post.count(:user_id => to_a.map(&:id))
- end
- end
-
-* To prefix the table names of all models in a module, define self.table_name_prefix on the module. #4032 [Andrew White]
-
-* Silenced "SHOW FIELDS" and "SET SQL_AUTO_IS_NULL=0" statements from the MySQL driver to improve log signal to noise ration in development [DHH]
-
-* PostgreSQLAdapter: set time_zone to UTC when Base.default_timezone == :utc so that Postgres doesn't incorrectly offset-adjust values inserted into TIMESTAMP WITH TIME ZONE columns. #3777 [Jack Christensen]
-
-* Allow relations to be used as scope.
-
- class Item
- scope :red, where(:colour => 'red')
- end
-
- Item.red.limit(10) # Ten red items
-
-* Rename named_scope to scope. [Pratik Naik]
-
-* Changed ActiveRecord::Base.store_full_sti_class to be true by default reflecting the previously announced Rails 3 default [DHH]
-
-* Add Relation#except. [Pratik Naik]
-
- one_red_item = Item.where(:colour => 'red').limit(1)
- all_items = one_red_item.except(:where, :limit)
-
-* Add Relation#delete_all. [Pratik Naik]
-
- Item.where(:colour => 'red').delete_all
-
-* Add Model.having and Relation#having. [Pratik Naik]
-
- Developer.group("salary").having("sum(salary) > 10000").select("salary")
-
-* Add Relation#count. [Pratik Naik]
-
- legends = People.where("age > 100")
- legends.count
- legends.count(:age, :distinct => true)
- legends.select('id').count
-
-* Add Model.readonly and association_collection#readonly finder method. [Pratik Naik]
-
- Post.readonly.to_a # Load all posts in readonly mode
- @user.items.readonly(false).to_a # Load all the user items in writable mode
-
-* Add .lock finder method [Pratik Naik]
-
- User.lock.where(:name => 'lifo').to_a
-
- old_items = Item.where("age > 100")
- old_items.lock.each {|i| .. }
-
-* Add Model.from and association_collection#from finder methods [Pratik Naik]
-
- user = User.scoped
- user.select('*').from('users, items')
-
-* Add relation.destroy_all [Pratik Naik]
-
- old_items = Item.where("age > 100")
- old_items.destroy_all
-
-* Add relation.exists? [Pratik Naik]
-
- red_items = Item.where(:colours => 'red')
- red_items.exists?
- red_items.exists?(1)
-
-* Add find(ids) to relations. [Pratik Naik]
-
- old_users = User.order("age DESC")
- old_users.find(1)
- old_users.find(1, 2, 3)
-
-* Add new finder methods to association collection. [Pratik Naik]
-
- class User < ActiveRecord::Base
- has_many :items
- end
-
- user = User.first
- user.items.where(:items => {:colour => 'red'})
- user.items.select('items.id')
-
-* Add relation.reload to force reloading the records. [Pratik Naik]
-
- topics = Topic.scoped
- topics.to_a # force load
- topics.first # returns a cached record
- topics.reload
- topics.first # Fetches a new record from the database
-
-* Rename Model.conditions and relation.conditions to .where. [Pratik Naik]
-
- Before :
- User.conditions(:name => 'lifo')
- User.select('id').conditions(["age > ?", 21])
-
- Now :
- User.where(:name => 'lifo')
- User.select('id').where(["age > ?", 21])
-
-* Add Model.select/group/order/limit/joins/conditions/preload/eager_load class methods returning a lazy relation. [Pratik Naik]
-
- Examples :
-
- posts = Post.select('id).order('name') # Returns a lazy relation
- posts.each {|p| puts p.id } # Fires "select id from posts order by name"
-
-* Model.scoped now returns a relation if invoked without any arguments. [Pratik Naik]
-
- Example :
-
- posts = Post.scoped
- posts.size # Fires "select count(*) from posts" and returns the count
- posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
-
-* Association inverses for belongs_to, has_one, and has_many. Optimization to reduce database queries. #3533 [Murray Steele]
-
- # post.comments sets each comment's post without needing to :include
- class Post < ActiveRecord::Base
- has_many :comments, :inverse_of => :post
- end
-
-* MySQL: add_ and change_column support positioning. #3286 [Ben Marini]
-
-* Reset your Active Record counter caches with the reset_counter_cache class method. #1211 [Mike Breen, Gabe da Silveira]
-
-* Remove support for SQLite 2. Please upgrade to SQLite 3+ or install the plugin from git://github.com/rails/sqlite2_adapter.git [Pratik Naik]
-
-* PostgreSQL: XML datatype support. #1874 [Leonardo Borges]
-
-* quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing]
-
-* SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper]
-
-* Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha]
- # employees.company_name references companies.name
- Employee.belongs_to :company, :primary_key => 'name', :foreign_key => 'company_name'
-
-* Implement #many? for NamedScope and AssociationCollection using #size. #1500 [Chris Kampmeier]
-
-* Added :touch option to belongs_to associations that will touch the parent record when the current record is saved or destroyed [DHH]
-
-* Added ActiveRecord::Base#touch to update the updated_at/on attributes (or another specified timestamp) with the current time [DHH]
-
-
-*2.3.2 [Final] (March 15, 2009)*
-
-* Added ActiveRecord::Base.find_each and ActiveRecord::Base.find_in_batches for batch processing [DHH/Jamis Buck]
-
-* Added that ActiveRecord::Base.exists? can be called with no arguments #1817 [Scott Taylor]
-
-* Add Support for updating deeply nested models from a single form. #1202 [Eloy Duran]
-
- class Book < ActiveRecord::Base
- has_one :author
- has_many :pages
-
- accepts_nested_attributes_for :author, :pages
- end
-
-* Make after_save callbacks fire only if the record was successfully saved. #1735 [Michael Lovitt]
-
- Previously the callbacks would fire if a before_save cancelled saving.
-
-* Support nested transactions using database savepoints. #383 [Jonathan Viney, Hongli Lai]
-
-* Added dynamic scopes ala dynamic finders #1648 [Yaroslav Markin]
-
-* Fixed that ActiveRecord::Base#new_record? should return false (not nil) for existing records #1219 [Yaroslav Markin]
-
-* I18n the word separator for error messages. Introduces the activerecord.errors.format.separator translation key. #1294 [Akira Matsuda]
-
-* Add :having as a key to find and the relevant associations. [Emilio Tagua]
-
-* Added default_scope to Base #1381 [Paweł Kondzior]. Example:
-
- class Person < ActiveRecord::Base
- default_scope :order => 'last_name, first_name'
- end
-
- class Company < ActiveRecord::Base
- has_many :people
- end
-
- Person.all # => Person.find(:all, :order => 'last_name, first_name')
- Company.find(1).people # => Person.find(:all, :order => 'last_name, first_name', :conditions => { :company_id => 1 })
-
-
-*2.2.1 [RC2] (November 14th, 2008)*
-
-* Ensure indices don't flip order in schema.rb #1266 [Jordi Bunster]
-
-* Fixed that serialized strings should never be type-casted (i.e. turning "Yes" to a boolean) #857 [Andreas Korth]
-
-
-*2.2.0 [RC1] (October 24th, 2008)*
-
-* Skip collection ids reader optimization if using :finder_sql [Jeremy Kemper]
-
-* Add Model#delete instance method, similar to Model.delete class method. #1086 [Hongli Lai (Phusion)]
-
-* MySQL: cope with quirky default values for not-null text columns. #1043 [Frederick Cheung]
-
-* Multiparameter attributes skip time zone conversion for time-only columns [#1030 state:resolved] [Geoff Buesing]
-
-* Base.skip_time_zone_conversion_for_attributes uses class_inheritable_accessor, so that subclasses don't overwrite Base [#346 state:resolved] [Emilio Tagua]
-
-* Added find_last_by dynamic finder #762 [Emilio Tagua]
-
-* Internal API: configurable association options and build_association method for reflections so plugins may extend and override. #985 [Hongli Lai (Phusion)]
-
-* Changed benchmarks to be reported in milliseconds [David Heinemeier Hansson]
-
-* Connection pooling. #936 [Nick Sieger]
-
-* Merge scoped :joins together instead of overwriting them. May expose scoping bugs in your code! #501 [Andrew White]
-
-* before_save, before_validation and before_destroy callbacks that return false will now ROLLBACK the transaction. Previously this would have been committed before the processing was aborted. #891 [Xavier Noria]
-
-* Transactional migrations for databases which support them. #834 [divoxx, Adam Wiggins, Tarmo Tänav]
-
-* Set config.active_record.timestamped_migrations = false to have migrations with numeric prefix instead of UTC timestamp. #446. [Andrew Stone, Nik Wakelin]
-
-* change_column_default preserves the not-null constraint. #617 [Tarmo Tänav]
-
-* Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) [#334]
-
-* Add :tokenizer option to validates_length_of to specify how to split up the attribute string. #507. [David Lowenfels] Example :
-
- # Ensure essay contains at least 100 words.
- validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
-
-* Allow conditions on multiple tables to be specified using hash. [Pratik Naik]. Example:
-
- User.all :joins => :items, :conditions => { :age => 10, :items => { :color => 'black' } }
- Item.first :conditions => { :items => { :color => 'red' } }
-
-* Always treat integer :limit as byte length. #420 [Tarmo Tänav]
-
-* Partial updates don't update lock_version if nothing changed. #426 [Daniel Morrison]
-
-* Fix column collision with named_scope and :joins. #46 [Duncan Beevers, Mark Catley]
-
-* db:migrate:down and :up update schema_migrations. #369 [Michael Raidel, RaceCondition]
-
-* PostgreSQL: support :conditions => [':foo::integer', { :foo => 1 }] without treating the ::integer typecast as a bind variable. [Tarmo Tänav]
-
-* MySQL: rename_column preserves column defaults. #466 [Diego Algorta]
-
-* Add :from option to calculations. #397 [Ben Munat]
-
-* Add :validate option to associations to enable/disable the automatic validation of associated models. Resolves #301. [Jan De Poorter]
-
-* PostgreSQL: use 'INSERT ... RETURNING id' for 8.2 and later. [Jeremy Kemper]
-
-* Added SQL escaping for :limit and :offset in MySQL [Jonathan Wiess]
-
-
-*2.1.0 (May 31st, 2008)*
-
-* Add ActiveRecord::Base.sti_name that checks ActiveRecord::Base#store_full_sti_class? and returns either the full or demodulized name. [Rick Olson]
-
-* Add first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
-
-* Added SQL escaping for :limit and :offset #288 [Aaron Bedra, Steven Bristol, Jonathan Wiess]
-
-* Added first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
-
-* Ensure hm:t preloading honours reflection options. Resolves #137. [Frederick Cheung]
-
-* Added protection against duplicate migration names (Aslak Hellesøy) [#112]
-
-* Base#instantiate_time_object: eliminate check for Time.zone, since we can assume this is set if time_zone_aware_attributes is set to true [Geoff Buesing]
-
-* Time zone aware attribute methods use Time.zone.parse instead of #to_time for String arguments, so that offset information in String is respected. Resolves #105. [Scott Fleckenstein, Geoff Buesing]
-
-* Added change_table for migrations (Jeff Dean) [#71]. Example:
-
- change_table :videos do |t|
- t.timestamps # adds created_at, updated_at
- t.belongs_to :goat # adds goat_id integer
- t.string :name, :email, :limit => 20 # adds name and email both with a 20 char limit
- t.remove :name, :email # removes the name and email columns
- end
-
-* Fixed has_many :through .create with no parameters caused a "can't dup NilClass" error (Steven Soroka) [#85]
-
-* Added block-setting of attributes for Base.create like Base.new already has (Adam Meehan) [#39]
-
-* Fixed that pessimistic locking you reference the quoted table name (Josh Susser) [#67]
-
-* Fixed that change_column should be able to use :null => true on a field that formerly had false [Nate Wiger] [#26]
-
-* Added that the MySQL adapter should map integer to either smallint, int, or bigint depending on the :limit just like PostgreSQL [David Heinemeier Hansson]
-
-* Change validates_uniqueness_of :case_sensitive option default back to true (from [9160]). Love your database columns, don't LOWER them. [Rick Olson]
-
-* Add support for interleaving migrations by storing which migrations have run in the new schema_migrations table. Closes #11493 [Jordi Bunster]
-
-* ActiveRecord::Base#sum defaults to 0 if no rows are returned. Closes #11550 [Kamal Fariz Mahyuddin]
-
-* Ensure that respond_to? considers dynamic finder methods. Closes #11538. [James Mead]
-
-* Ensure that save on parent object fails for invalid has_one association. Closes #10518. [Pratik Naik]
-
-* Remove duplicate code from associations. [Pratik Naik]
-
-* Refactor HasManyThroughAssociation to inherit from HasManyAssociation. Association callbacks and <association>_ids= now work with hm:t. #11516 [Ruy Asan]
-
-* Ensure HABTM#create and HABTM#build do not load entire association. [Pratik Naik]
-
-* Improve documentation. [Xavier Noria, Jack Danger Canty, leethal]
-
-* Tweak ActiveRecord::Base#to_json to include a root value in the returned hash: {"post": {"title": ...}} [Rick Olson]
-
- Post.find(1).to_json # => {"title": ...}
- config.active_record.include_root_in_json = true
- Post.find(1).to_json # => {"post": {"title": ...}}
-
-* Add efficient #include? to AssociationCollection (for has_many/has_many :through/habtm). [stopdropandrew]
-
-* PostgreSQL: create_ and drop_database support. #9042 [ez, pedz, Nick Sieger]
-
-* Ensure that validates_uniqueness_of works with with_scope. Closes #9235. [Nik Wakelin, cavalle]
-
-* Partial updates include only unsaved attributes. Off by default; set YourClass.partial_updates = true to enable. [Jeremy Kemper]
-
-* Removing unnecessary uses_tzinfo helper from tests, given that TZInfo is now bundled [Geoff Buesing]
-
-* Fixed that validates_size_of :within works in associations #11295, #10019 [cavalle]
-
-* Track changes to unsaved attributes. [Jeremy Kemper]
-
-* Switched to UTC-timebased version numbers for migrations and the schema. This will as good as eliminate the problem of multiple migrations getting the same version assigned in different branches. Also added rake db:migrate:up/down to apply individual migrations that may need to be run when you merge branches #11458 [John Barnette]
-
-* Fixed that has_many :through would ignore the hash conditions #11447 [Emilio Tagua]
-
-* Fix issue where the :uniq option of a has_many :through association is ignored when find(:all) is called. Closes #9407 [cavalle]
-
-* Fix duplicate table alias error when including an association with a has_many :through association on the same join table. Closes #7310 [cavalle]
-
-* More efficient association preloading code that compacts a through_records array in a central location. Closes #11427 [Jack Danger Canty]
-
-* Improve documentation. [Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert]
-
-* Fixed that ActiveRecord#Base.find_or_create/initialize would not honor attr_protected/accessible when used with a hash #11422 [Emilio Tagua]
-
-* Added ActiveRecord#Base.all/first/last as aliases for find(:all/:first/:last) #11413 [nkallen, Chris O'Sullivan]
-
-* Merge the has_finder gem, renamed as 'named_scope'. #11404 [nkallen]
-
- class Article < ActiveRecord::Base
- named_scope :published, :conditions => {:published => true}
- named_scope :popular, :conditions => ...
- end
-
- Article.published.paginate(:page => 1)
- Article.published.popular.count
- Article.popular.find(:first)
- Article.popular.find(:all, :conditions => {...})
-
- See http://pivots.pivotallabs.com/users/nick/blog/articles/284-hasfinder-it-s-now-easier-than-ever-to-create-complex-re-usable-sql-queries
-
-* Add has_one :through support. #4756 [Chris O'Sullivan]
-
-* Migrations: create_table supports primary_key_prefix_type. #10314 [student, Chris O'Sullivan]
-
-* Added logging for dependency load errors with fixtures #11056 [stuthulhu]
-
-* Time zone aware attributes use Time#in_time_zone [Geoff Buesing]
-
-* Fixed that scoped joins would not always be respected #6821 [Theory/Jack Danger Canty]
-
-* Ensure that ActiveRecord::Calculations disambiguates field names with the table name. #11027 [cavalle]
-
-* Added add/remove_timestamps to the schema statements for adding the created_at/updated_at columns on existing tables #11129 [jramirez]
-
-* Added ActiveRecord::Base.find(:last) #11338 [Emilio Tagua]
-
-* test_native_types expects DateTime.local_offset instead of DateTime.now.offset; fixes test breakage due to dst transition [Geoff Buesing]
-
-* Add :readonly option to HasManyThrough associations. #11156 [Emilio Tagua]
-
-* Improve performance on :include/:conditions/:limit queries by selectively joining in the pre-query. #9560 [dasil003]
-
-* Perf fix: Avoid the use of named block arguments. Closes #11109 [adymo]
-
-* PostgreSQL: support server versions 7.4 through 8.0 and the ruby-pg driver. #11127 [jdavis]
-
-* Ensure association preloading doesn't break when an association returns nil. ##11145 [GMFlash]
-
-* Make dynamic finders respect the :include on HasManyThrough associations. #10998. [cpytel]
-
-* Base#instantiate_time_object only uses Time.zone when Base.time_zone_aware_attributes is true; leverages Time#time_with_datetime_fallback for readability [Geoff Buesing]
-
-* Refactor ConnectionAdapters::Column.new_time: leverage DateTime failover behavior of Time#time_with_datetime_fallback [Geoff Buesing]
-
-* Improve associations performance by using symbol callbacks instead of string callbacks. #11108 [adymo]
-
-* Optimise the BigDecimal conversion code. #11110 [adymo]
-
-* Introduce the :readonly option to all associations. Records from the association cannot be saved. #11084 [Emilio Tagua]
-
-* Multiparameter attributes for time columns fail over to DateTime when out of range of Time [Geoff Buesing]
-
-* Base#instantiate_time_object uses Time.zone.local() [Geoff Buesing]
-
-* Add timezone-aware attribute readers and writers. #10982 [Geoff Buesing]
-
-* Instantiating time objects in multiparameter attributes uses Time.zone if available. #10982 [Rick Olson]
-
-* Add note about how ActiveRecord::Observer classes are initialized in a Rails app. #10980 [Xavier Noria]
-
-* MySQL: omit text/blob defaults from the schema instead of using an empty string. #10963 [mdeiters]
-
-* belongs_to supports :dependent => :destroy and :delete. #10592 [Jonathan Viney]
-
-* Introduce preload query strategy for eager :includes. #9640 [Frederick Cheung, Aliaksey Kandratsenka, codafoo]
-
-* Support aggregations in finder conditions. #10572 [Ryan Kinderman]
-
-* Organize and clean up the Active Record test suite. #10742 [John Barnette]
-
-* Ensure that modifying has_and_belongs_to_many actions clear the query cache. Closes #10840 [john.andrews]
-
-* Fix issue where Table#references doesn't pass a :null option to a *_type attribute for polymorphic associations. Closes #10753 [railsjitsu]
-
-* Fixtures: removed support for the ancient pre-YAML file format. #10736 [John Barnette]
-
-* More thoroughly quote table names. #10698 [dimdenis, lotswholetime, Jeremy Kemper]
-
-* update_all ignores scoped :order and :limit, so post.comments.update_all doesn't try to include the comment order in the update statement. #10686 [Brendan Ribera]
-
-* Added ActiveRecord::Base.cache_key to make it easier to cache Active Records in combination with the new ActiveSupport::Cache::* libraries [David Heinemeier Hansson]
-
-* Make sure CSV fixtures are compatible with ruby 1.9's new csv implementation. [JEG2]
-
-* Added by parameter to increment, decrement, and their bang varieties so you can do player1.increment!(:points, 5) #10542 [Sam]
-
-* Optimize ActiveRecord::Base#exists? to use #select_all instead of #find. Closes #10605 [jamesh, Frederick Cheung, protocool]
-
-* Don't unnecessarily load has_many associations in after_update callbacks. Closes #6822 [stopdropandrew, canadaduane]
-
-* Eager belongs_to :include infers the foreign key from the association name rather than the class name. #10517 [Jonathan Viney]
-
-* SQLite: fix rename_ and remove_column for columns with unique indexes. #10576 [Brandon Keepers]
-
-* Ruby 1.9 compatibility. #10655 [Jeremy Kemper, Dirkjan Bussink]
-
-
-*2.0.2* (December 16th, 2007)
-
-* Ensure optimistic locking handles nil #lock_version values properly. Closes #10510 [Rick Olson]
-
-* Make the Fixtures Test::Unit enhancements more supporting for double-loaded test cases. Closes #10379 [brynary]
-
-* Fix that validates_acceptance_of still works for non-existent tables (useful for bootstrapping new databases). Closes #10474 [Josh Susser]
-
-* Ensure that the :uniq option for has_many :through associations retains the order. #10463 [remvee]
-
-* Base.exists? doesn't rescue exceptions to avoid hiding SQL errors. #10458 [Michael Klishin]
-
-* Documentation: Active Record exceptions, destroy_all and delete_all. #10444, #10447 [Michael Klishin]
-
-
-*2.0.1* (December 7th, 2007)
-
-* Removed query cache rescue as it could cause code to be run twice (closes #10408) [David Heinemeier Hansson]
-
-
-*2.0.0* (December 6th, 2007)
-
-* Anchor DateTimeTest to fixed DateTime instead of a variable value based on Time.now#advance#to_datetime, so that this test passes on 64-bit platforms running Ruby 1.8.6+ [Geoff Buesing]
-
-* Fixed that the Query Cache should just be ignored if the database is misconfigured (so that the "About your applications environment" works even before the database has been created) [David Heinemeier Hansson]
-
-* Fixed that the truncation of strings longer than 50 chars should use inspect
-so newlines etc are escaped #10385 [Norbert Crombach]
-
-* Fixed that habtm associations should be able to set :select as part of their definition and have that honored [David Heinemeier Hansson]
-
-* Document how the :include option can be used in Calculations::calculate. Closes #7446 [adamwiggins, ultimoamore]
-
-* Fix typo in documentation for polymorphic associations w/STI. Closes #7461 [johnjosephbachir]
-
-* Reveal that the type option in migrations can be any supported column type for your database but also include caveat about agnosticism. Closes #7531 [adamwiggins, mikong]
-
-* More complete documentation for find_by_sql. Closes #7912 [fearoffish]
-
-* Added ActiveRecord::Base#becomes to turn a record into one of another class (mostly relevant for STIs) [David Heinemeier Hansson]. Example:
-
- render :partial => @client.becomes(Company) # renders companies/company instead of clients/client
-
-* Fixed that to_xml should not automatically pass :procs to associations included with :include #10162 [Cheah Chu Yeow]
-
-* Fix documentation typo introduced in [8250]. Closes #10339 [Henrik N]
-
-* Foxy fixtures: support single-table inheritance. #10234 [tom]
-
-* Foxy fixtures: allow mixed usage to make migration easier and more attractive. #10004 [lotswholetime]
-
-* Make the record_timestamps class-inheritable so it can be set per model. #10004 [tmacedo]
-
-* Allow validates_acceptance_of to use a real attribute instead of only virtual (so you can record that the acceptance occured) #7457 [ambethia]
-
-* DateTimes use Ruby's default calendar reform setting. #10201 [Geoff Buesing]
-
-* Dynamic finders on association collections respect association :order and :limit. #10211, #10227 [Patrick Joyce, Rick Olson, Jack Danger Canty]
-
-* Add 'foxy' support for fixtures of polymorphic associations. #10183 [John Barnette, David Lowenfels]
-
-* validates_inclusion_of and validates_exclusion_of allow formatted :message strings. #8132 [devrieda, Mike Naberezny]
-
-* attr_readonly behaves well with optimistic locking. #10188 [Nick Bugajski]
-
-* Base#to_xml supports the nil="true" attribute like Hash#to_xml. #8268 [Jonathan del Strother]
-
-* Change plings to the more conventional quotes in the documentation. Closes #10104 [Jack Danger Canty]
-
-* Fix HasManyThrough Association so it uses :conditions on the HasMany Association. Closes #9729 [Jack Danger Canty]
-
-* Ensure that column names are quoted. Closes #10134 [wesley.moxam]
-
-* Smattering of grammatical fixes to documentation. Closes #10083 [Bob Silva]
-
-* Enhance explanation with more examples for attr_accessible macro. Closes #8095 [fearoffish, Marcel Molina Jr.]
-
-* Update association/method mapping table to refected latest collection methods for has_many :through. Closes #8772 [Pratik Naik]
-
-* Explain semantics of having several different AR instances in a transaction block. Closes #9036 [jacobat, Marcel Molina Jr.]
-
-* Update Schema documentation to use updated sexy migration notation. Closes #10086 [Sam Granieri]
-
-* Make fixtures work with the new test subclasses. [Tarmo Tänav, Michael Koziarski]
-
-* Introduce finder :joins with associations. Same :include syntax but with inner rather than outer joins. #10012 [RubyRedRick]
- # Find users with an avatar
- User.find(:all, :joins => :avatar)
-
- # Find posts with a high-rated comment.
- Post.find(:all, :joins => :comments, :conditions => 'comments.rating > 3')
-
-* Associations: speedup duplicate record check. #10011 [Pratik Naik]
-
-* Make sure that << works on has_many associations on unsaved records. Closes #9989 [Josh Susser]
-
-* Allow association redefinition in subclasses. #9346 [wildchild]
-
-* Fix has_many :through delete with custom foreign keys. #6466 [naffis]
-
-* Foxy fixtures, from rathole (http://svn.geeksomnia.com/rathole/trunk/README)
- - stable, autogenerated IDs
- - specify associations (belongs_to, has_one, has_many) by label, not ID
- - specify HABTM associations as inline lists
- - autofill timestamp columns
- - support YAML defaults
- - fixture label interpolation
- Enabled for fixtures that correspond to a model class and don't specify a primary key value. #9981 [John Barnette]
-
-* Add docs explaining how to protect all attributes using attr_accessible with no arguments. Closes #9631 [boone, rmm5t]
-
-* Update add_index documentation to use new options api. Closes #9787 [Kamal Fariz Mahyuddin]
-
-* Allow find on a has_many association defined with :finder_sql to accept id arguments as strings like regular find does. Closes #9916 [krishna]
-
-* Use VALID_FIND_OPTIONS when resolving :find scoping rather than hard coding the list of valid find options. Closes #9443 [sur]
-
-* Limited eager loading no longer ignores scoped :order. Closes #9561 [Jack Danger Canty, Josh Peek]
-
-* Assigning an instance of a foreign class to a composed_of aggregate calls an optional conversion block. Refactor and simplify composed_of implementation. #6322 [brandon, Chris Cruft]
-
-* Assigning nil to a composed_of aggregate also sets its immediate value to nil. #9843 [Chris Cruft]
-
-* Ensure that mysql quotes table names with database names correctly. Closes #9911 [crayz]
-
- "foo.bar" => "`foo`.`bar`"
-
-* Complete the assimilation of Sexy Migrations from ErrFree [Chris Wanstrath, PJ Hyett]
- http://errtheblog.com/post/2381
-
-* Qualified column names work in hash conditions, like :conditions => { 'comments.created_at' => ... }. #9733 [Jack Danger Canty]
-
-* Fix regression where the association would not construct new finder SQL on save causing bogus queries for "WHERE owner_id = NULL" even after owner was saved. #8713 [Bryan Helmkamp]
-
-* Refactor association create and build so before & after callbacks behave consistently. #8854 [Pratik Naik, mortent]
-
-* Quote table names. Defaults to column quoting. #4593 [Justin Lynn, gwcoffey, eadz, Dmitry V. Sabanin, Jeremy Kemper]
-
-* Alias association #build to #new so it behaves predictably. #8787 [Pratik Naik]
-
-* Add notes to documentation regarding attr_readonly behavior with counter caches and polymorphic associations. Closes #9835 [saimonmoore, Rick Olson]
-
-* Observers can observe model names as symbols properly now. Closes #9869 [queso]
-
-* find_and_(initialize|create)_by methods can now properly initialize protected attributes [Tobias Lütke]
-
-* belongs_to infers the foreign key from the association name instead of from the class name. [Jeremy Kemper]
-
-* PostgreSQL: support multiline default values. #7533 [Carl Lerche, aguynamedryan, Rein Henrichs, Tarmo Tänav]
-
-* MySQL: fix change_column on not-null columns that don't accept dfeault values of ''. #6663 [Jonathan Viney, Tarmo Tänav]
-
-* validates_uniqueness_of behaves well with abstract superclasses and
-single-table inheritance. #3833, #9886 [Gabriel Gironda, rramdas, François Beausoleil, Josh Peek, Tarmo Tänav, pat]
-
-* Warn about protected attribute assigments in development and test environments when mass-assigning to an attr_protected attribute. #9802 [Henrik N]
-
-* Speedup database date/time parsing. [Jeremy Kemper, Tarmo Tänav]
-
-* Fix calling .clear on a has_many :dependent=>:delete_all association. [Tarmo Tänav]
-
-* Allow change_column to set NOT NULL in the PostgreSQL adapter [Tarmo Tänav]
-
-* Fix that ActiveRecord would create attribute methods and override custom attribute getters if the method is also defined in Kernel.methods. [Rick Olson]
-
-* Don't call attr_readonly on polymorphic belongs_to associations, in case it matches the name of some other non-ActiveRecord class/module. [Rick Olson]
-
-* Try loading activerecord-<adaptername>-adapter gem before trying a plain require so you can use custom gems for the bundled adapters. Also stops gems from requiring an adapter from an old Active Record gem. [Jeremy Kemper, Derrick Spell]
-
-
-*2.0.0 [Preview Release]* (September 29th, 2007) [Includes duplicates of changes from 1.14.2 - 1.15.3]
-
-* Add attr_readonly to specify columns that are skipped during a normal ActiveRecord #save operation. Closes #6896 [Dan Manges]
-
- class Comment < ActiveRecord::Base
- # Automatically sets Article#comments_count as readonly.
- belongs_to :article, :counter_cache => :comments_count
- end
-
- class Article < ActiveRecord::Base
- attr_readonly :approved_comments_count
- end
-
-* Make size for has_many :through use counter cache if it exists. Closes #9734 [Xavier Shay]
-
-* Remove DB2 adapter since IBM chooses to maintain their own adapter instead. [Jeremy Kemper]
-
-* Extract Oracle, SQLServer, and Sybase adapters into gems. [Jeremy Kemper]
-
-* Added fixture caching that'll speed up a normal fixture-powered test suite between 50% and 100% #9682 [Frederick Cheung]
-
-* Correctly quote id list for limited eager loading. #7482 [tmacedo]
-
-* Fixed that using version-targetted migrates would fail on loggers other than the default one #7430 [valeksenko]
-
-* Fixed rename_column for SQLite when using symbols for the column names #8616 [drodriguez]
-
-* Added the possibility of using symbols in addition to concrete classes with ActiveRecord::Observer#observe. #3998 [Robby Russell, Tarmo Tänav]
-
-* Added ActiveRecord::Base#to_json/from_json [David Heinemeier Hansson, Cheah Chu Yeow]
-
-* Added ActiveRecord::Base#from_xml [David Heinemeier Hansson]. Example:
-
- xml = "<person><name>David</name></person>"
- Person.new.from_xml(xml).name # => "David"
-
-* Define dynamic finders as real methods after first usage. [bscofield]
-
-* Deprecation: remove deprecated threaded_connections methods. Use allow_concurrency instead. [Jeremy Kemper]
-
-* Associations macros accept extension blocks alongside modules. #9346 [Josh Peek]
-
-* Speed up and simplify query caching. [Jeremy Kemper]
-
-* connection.select_rows 'sql' returns an array (rows) of arrays (field values). #2329 [Michael Schuerig]
-
-* Eager loading respects explicit :joins. #9496 [dasil003]
-
-* Extract Firebird, FrontBase, and OpenBase adapters into gems. #9508, #9509, #9510 [Jeremy Kemper]
-
-* RubyGem database adapters: expects a gem named activerecord-<database>-adapter with active_record/connection_adapters/<database>_adapter.rb in its load path. [Jeremy Kemper]
-
-* Fixed that altering join tables in migrations would fail w/ sqlite3 #7453 [TimoMihaljov/brandon]
-
-* Fix association writer with :dependent => :nullify. #7314 [Jonathan Viney]
-
-* OpenBase: update for new lib and latest Rails. Support migrations. #8748 [dcsesq]
-
-* Moved acts_as_tree into a plugin of the same name on the official Rails svn. #9514 [Pratik Naik]
-
-* Moved acts_as_nested_set into a plugin of the same name on the official Rails svn. #9516 [Josh Peek]
-
-* Moved acts_as_list into a plugin of the same name on the official Rails svn. [Josh Peek]
-
-* Explicitly require active_record/query_cache before using it. [Jeremy Kemper]
-
-* Fix bug where unserializing an attribute attempts to modify a frozen @attributes hash for a deleted record. [Rick Olson, marclove]
-
-* Performance: absorb instantiate and initialize_with_callbacks into the Base methods. [Jeremy Kemper]
-
-* Fixed that eager loading queries and with_scope should respect the :group option [David Heinemeier Hansson]
-
-* Improve performance and functionality of the postgresql adapter. Closes #8049 [roderickvd]
-
- For more information see: http://dev.rubyonrails.org/ticket/8049
-
-* Don't clobber includes passed to has_many.count [Jack Danger Canty]
-
-* Make sure has_many uses :include when counting [Jack Danger Canty]
-
-* Change the implementation of ActiveRecord's attribute reader and writer methods [Michael Koziarski]
- - Generate Reader and Writer methods which cache attribute values in hashes. This is to avoid repeatedly parsing the same date or integer columns.
- - Change exception raised when users use find with :select then try to access a skipped column. Plugins could override missing_attribute() to lazily load the columns.
- - Move method definition to the class, instead of the instance
- - Always generate the readers, writers and predicate methods.
-
-* Perform a deep #dup on query cache results so that modifying activerecord attributes does not modify the cached attributes. [Rick Olson]
-
-# Ensure that has_many :through associations use a count query instead of loading the target when #size is called. Closes #8800 [Pratik Naik]
-
-* Added :unless clause to validations #8003 [monki]. Example:
-
- def using_open_id?
- !identity_url.blank?
- end
-
- validates_presence_of :identity_url, :if => using_open_id?
- validates_presence_of :username, :unless => using_open_id?
- validates_presence_of :password, :unless => using_open_id?
-
-* Fix #count on a has_many :through association so that it recognizes the :uniq option. Closes #8801 [Pratik Naik]
-
-* Fix and properly document/test count(column_name) usage. Closes #8999 [Pratik Naik]
-
-* Remove deprecated count(conditions=nil, joins=nil) usage. Closes #8993 [Pratik Naik]
-
-* Change belongs_to so that the foreign_key assumption is taken from the association name, not the class name. Closes #8992 [Josh Susser]
-
- OLD
- belongs_to :visitor, :class_name => 'User' # => inferred foreign_key is user_id
-
- NEW
- belongs_to :visitor, :class_name => 'User' # => inferred foreign_key is visitor_id
-
-* Remove spurious tests from deprecated_associations_test, most of these aren't deprecated, and are duplicated in associations_test. Closes #8987 [Pratik Naik]
-
-* Make create! on a has_many :through association return the association object. Not the collection. Closes #8786 [Pratik Naik]
-
-* Move from select * to select tablename.* to avoid clobbering IDs. Closes #8889 [dasil003]
-
-* Don't call unsupported methods on associated objects when using :include, :method with to_xml #7307, [Manfred Stienstra, jwilger]
-
-* Define collection singular ids method for has_many :through associations. #8763 [Pratik Naik]
-
-* Array attribute conditions work with proxied association collections. #8318 [Kamal Fariz Mahyuddin, theamazingrando]
-
-* Fix polymorphic has_one associations declared in an abstract class. #8638 [Pratik Naik, Dax Huiberts]
-
-* Fixed validates_associated should not stop on the first error. #4276 [mrj, Manfred Stienstra, Josh Peek]
-
-* Rollback if commit raises an exception. #8642 [kik, Jeremy Kemper]
-
-* Update tests' use of fixtures for the new collections api. #8726 [Kamal Fariz Mahyuddin]
-
-* Save associated records only if the association is already loaded. #8713 [Blaine]
-
-* MySQL: fix show_variable. #8448 [matt, Jeremy Kemper]
-
-* Fixtures: correctly delete and insert fixtures in a single transaction. #8553 [Michael Schuerig]
-
-* Fixtures: people(:technomancy, :josh) returns both fixtures. #7880 [technomancy, Josh Peek]
-
-* Calculations support non-numeric foreign keys. #8154 [Kamal Fariz Mahyuddin]
-
-* with_scope is protected. #8524 [Josh Peek]
-
-* Quickref for association methods. #7723 [marclove, Mindsweeper]
-
-* Calculations: return nil average instead of 0 when there are no rows to average. #8298 [davidw]
-
-* acts_as_nested_set: direct_children is sorted correctly. #4761 [Josh Peek, rails@33lc0.net]
-
-* Raise an exception if both attr_protected and attr_accessible are declared. #8507 [stellsmi]
-
-* SQLite, MySQL, PostgreSQL, Oracle: quote column names in column migration SQL statements. #8466 [marclove, lorenjohnson]
-
-* Allow nil serialized attributes with a set class constraint. #7293 [sandofsky]
-
-* Oracle: support binary fixtures. #7987 [Michael Schoen]
-
-* Fixtures: pull fixture insertion into the database adapters. #7987 [Michael Schoen]
-
-* Announce migration versions as they're performed. [Jeremy Kemper]
-
-* find gracefully copes with blank :conditions. #7599 [Dan Manges, johnnyb]
-
-* validates_numericality_of takes :greater_than, :greater_than_or_equal_to, :equal_to, :less_than, :less_than_or_equal_to, :odd, and :even options. #3952 [Bob Silva, Dan Kubb, Josh Peek]
-
-* MySQL: create_database takes :charset and :collation options. Charset defaults to utf8. #8448 [matt]
-
-* Find with a list of ids supports limit/offset. #8437 [hrudududu]
-
-* Optimistic locking: revert the lock version when an update fails. #7840 [plang]
-
-* Migrations: add_column supports custom column types. #7742 [jsgarvin, Theory]
-
-* Load database adapters on demand. Eliminates config.connection_adapters and RAILS_CONNECTION_ADAPTERS. Add your lib directory to the $LOAD_PATH and put your custom adapter in lib/active_record/connection_adapters/adaptername_adapter.rb. This way you can provide custom adapters as plugins or gems without modifying Rails. [Jeremy Kemper]
-
-* Ensure that associations with :dependent => :delete_all respect :conditions option. Closes #8034 [Jack Danger Canty, Josh Peek, Rick Olson]
-
-* belongs_to assignment creates a new proxy rather than modifying its target in-place. #8412 [mmangino@elevatedrails.com]
-
-* Fix column type detection while loading fixtures. Closes #7987 [roderickvd]
-
-* Document deep eager includes. #6267 [Josh Susser, Dan Manges]
-
-* Document warning that associations names shouldn't be reserved words. #4378 [murphy@cYcnus.de, Josh Susser]
-
-* Sanitize Base#inspect. #8392, #8623 [Nik Wakelin, jnoon]
-
-* Replace the transaction {|transaction|..} semantics with a new Exception ActiveRecord::Rollback. [Michael Koziarski]
-
-* Oracle: extract column length for CHAR also. #7866 [ymendel]
-
-* Document :allow_nil option for validates_acceptance_of since it defaults to true. [tzaharia]
-
-* Update documentation for :dependent declaration so that it explicitly uses the non-deprecated API. [Jack Danger Canty]
-
-* Add documentation caveat about when to use count_by_sql. [fearoffish]
-
-* Enhance documentation for increment_counter and decrement_counter. [fearoffish]
-
-* Provide brief introduction to what optimistic locking is. [fearoffish]
-
-* Add documentation for :encoding option to mysql adapter. [marclove]
-
-* Added short-hand declaration style to migrations (inspiration from Sexy Migrations, http://errtheblog.com/post/2381) [David Heinemeier Hansson]. Example:
-
- create_table "products" do |t|
- t.column "shop_id", :integer
- t.column "creator_id", :integer
- t.column "name", :string, :default => "Untitled"
- t.column "value", :string, :default => "Untitled"
- t.column "created_at", :datetime
- t.column "updated_at", :datetime
- end
-
- ...can now be written as:
-
- create_table :products do |t|
- t.integer :shop_id, :creator_id
- t.string :name, :value, :default => "Untitled"
- t.timestamps
- end
-
-* Use association name for the wrapper element when using .to_xml. Previous behavior lead to non-deterministic situations with STI and polymorphic associations. [Michael Koziarski, jstrachan]
-
-* Improve performance of calling .create on has_many :through associations. [evan]
-
-* Improved cloning performance by relying less on exception raising #8159 [Blaine]
-
-* Added ActiveRecord::Base.inspect to return a column-view like #<Post id:integer, title:string, body:text> [David Heinemeier Hansson]
-
-* Added yielding of Builder instance for ActiveRecord::Base#to_xml calls [David Heinemeier Hansson]
-
-* Small additions and fixes for ActiveRecord documentation. Closes #7342 [Jeremy McAnally]
-
-* Add helpful debugging info to the ActiveRecord::StatementInvalid exception in ActiveRecord::ConnectionAdapters::SqliteAdapter#table_structure. Closes #7925. [court3nay]
-
-* SQLite: binary escaping works with $KCODE='u'. #7862 [tsuka]
-
-* Base#to_xml supports serialized attributes. #7502 [jonathan]
-
-* Base.update_all :order and :limit options. Useful for MySQL updates that must be ordered to avoid violating unique constraints. [Jeremy Kemper]
-
-* Remove deprecated object transactions. People relying on this functionality should install the object_transactions plugin at http://code.bitsweat.net/svn/object_transactions. Closes #5637 [Michael Koziarski, Jeremy Kemper]
-
-* PostgreSQL: remove DateTime -> Time downcast. Warning: do not enable translate_results for the C bindings if you have timestamps outside Time's domain. [Jeremy Kemper]
-
-* find_or_create_by_* takes a hash so you can create with more attributes than are in the method name. For example, Person.find_or_create_by_name(:name => 'Henry', :comments => 'Hi new user!') is equivalent to Person.find_by_name('Henry') || Person.create(:name => 'Henry', :comments => 'Hi new user!'). #7368 [Josh Susser]
-
-* Make sure with_scope takes both :select and :joins into account when setting :readonly. Allows you to save records you retrieve using method_missing on a has_many :through associations. [Michael Koziarski]
-
-* Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]
-
-* Consistent public/protected/private visibility for chained methods. #7813 [Dan Manges]
-
-* Oracle: fix quoted primary keys and datetime overflow. #7798 [Michael Schoen]
-
-* Consistently quote primary key column names. #7763 [toolmantim]
-
-* Fixtures: fix YAML ordered map support. #2665 [Manuel Holtgrewe, nfbuckley]
-
-* DateTimes assume the default timezone. #7764 [Geoff Buesing]
-
-* Sybase: hide timestamp columns since they're inherently read-only. #7716 [Mike Joyce]
-
-* Oracle: overflow Time to DateTime. #7718 [Michael Schoen]
-
-* PostgreSQL: don't use async_exec and async_query with postgres-pr. #7727, #7762 [flowdelic, toolmantim]
-
-* Fix has_many :through << with custom foreign keys. #6466, #7153 [naffis, Rich Collins]
-
-* Test DateTime native type in migrations, including an edge case with dates
-during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
-
-* SQLServer: correctly schema-dump tables with no indexes or descending indexes. #7333, #7703 [Jakob Skjerning, Tom Ward]
-
-* SQLServer: recognize real column type as Ruby float. #7057 [sethladd, Tom Ward]
-
-* Added fixtures :all as a way of loading all fixtures in the fixture directory at once #7214 [Manfred Stienstra]
-
-* Added database connection as a yield parameter to ActiveRecord::Base.transaction so you can manually rollback [David Heinemeier Hansson]. Example:
-
- transaction do |transaction|
- david.withdrawal(100)
- mary.deposit(100)
- transaction.rollback! # rolls back the transaction that was otherwise going to be successful
- end
-
-* Made increment_counter/decrement_counter play nicely with optimistic locking, and added a more general update_counters method [Jamis Buck]
-
-* Reworked David's query cache to be available as Model.cache {...}. For the duration of the block no select query should be run more then once. Any inserts/deletes/executes will flush the whole cache however [Tobias Lütke]
- Task.cache { Task.find(1); Task.find(1) } # => 1 query
-
-* When dealing with SQLite3, use the table_info pragma helper, so that the bindings can do some translation for when sqlite3 breaks incompatibly between point releases. [Jamis Buck]
-
-* Oracle: fix lob and text default handling. #7344 [gfriedrich, Michael Schoen]
-
-* SQLServer: don't choke on strings containing 'null'. #7083 [Jakob Skjerning]
-
-* MySQL: blob and text columns may not have defaults in 5.x. Update fixtures schema for strict mode. #6695 [Dan Kubb]
-
-* update_all can take a Hash argument. sanitize_sql splits into two methods for conditions and assignment since NULL values and delimiters are handled differently. #6583, #7365 [sandofsky, Assaf]
-
-* MySQL: SET SQL_AUTO_IS_NULL=0 so 'where id is null' doesn't select the last inserted id. #6778 [Jonathan Viney, timc]
-
-* Use Date#to_s(:db) for quoted dates. #7411 [Michael Schoen]
-
-* Don't create instance writer methods for class attributes. Closes #7401 [Rick Olson]
-
-* Docs: validations examples. #7343 [zackchandler]
-
-* Add missing tests ensuring callbacks work with class inheritance. Closes #7339 [sandofsky]
-
-* Fixtures use the table name and connection from set_fixture_class. #7330 [Anthony Eden]
-
-* Remove useless code in #attribute_present? since 0 != blank?. Closes #7249 [Josh Susser]
-
-* Fix minor doc typos. Closes #7157 [Josh Susser]
-
-* Fix incorrect usage of #classify when creating the eager loading join statement. Closes #7044 [Josh Susser]
-
-* SQLServer: quote table name in indexes query. #2928 [keithm@infused.org]
-
-* Subclasses of an abstract class work with single-table inheritance. #5704, #7284 [BertG, nick+rails@ag.arizona.edu]
-
-* Make sure sqlite3 driver closes open connections on disconnect [Rob Rasmussen]
-
-* [DOC] clear up some ambiguity with the way has_and_belongs_to_many creates the default join table name. #7072 [Jeremy McAnally]
-
-* change_column accepts :default => nil. Skip column options for primary keys. #6956, #7048 [Dan Manges, Jeremy Kemper]
-
-* MySQL, PostgreSQL: change_column_default quotes the default value and doesn't lose column type information. #3987, #6664 [Jonathan Viney, Manfred Stienstra, altano@bigfoot.com]
-
-* Oracle: create_table takes a :sequence_name option to override the 'tablename_seq' default. #7000 [Michael Schoen]
-
-* MySQL: retain SSL settings on reconnect. #6976 [randyv2]
-
-* Apply scoping during initialize instead of create. Fixes setting of foreign key when using find_or_initialize_by with scoping. [Cody Fauser]
-
-* SQLServer: handle [quoted] table names. #6635 [rrich]
-
-* acts_as_nested_set works with single-table inheritance. #6030 [Josh Susser]
-
-* PostgreSQL, Oracle: correctly perform eager finds with :limit and :order. #4668, #7021 [eventualbuddha, Michael Schoen]
-
-* Pass a range in :conditions to use the SQL BETWEEN operator. #6974 [Dan Manges]
- Student.find(:all, :conditions => { :grade => 9..12 })
-
-* Fix the Oracle adapter for serialized attributes stored in CLOBs. Closes #6825 [mschoen, tdfowler]
-
-* [DOCS] Apply more documentation for ActiveRecord Reflection. Closes #4055 [Robby Russell]
-
-* [DOCS] Document :allow_nil option of #validate_uniqueness_of. Closes #3143 [Caio Chassot]
-
-* Bring the sybase adapter up to scratch for 1.2 release. [jsheets]
-
-* Rollback new_record? and id when an exception is raised in a save callback. #6910 [Ben Curren, outerim]
-
-* Pushing a record on an association collection doesn't unnecessarily load all the associated records. [Obie Fernandez, Jeremy Kemper]
-
-* Oracle: fix connection reset failure. #6846 [leonlleslie]
-
-* Subclass instantiation doesn't try to explicitly require the corresponding subclass. #6840 [leei, Jeremy Kemper]
-
-* fix faulty inheritance tests and that eager loading grabs the wrong inheritance column when the class of your association is an STI subclass. Closes #6859 [protocool]
-
-* Consolidated different create and create! versions to call through to the base class with scope. This fixes inconsistencies, especially related to protected attribtues. Closes #5847 [Alexander Dymo, Tobias Lütke]
-
-* find supports :lock with :include. Check whether your database allows SELECT ... FOR UPDATE with outer joins before using. #6764 [vitaly, Jeremy Kemper]
-
-* Add AssociationCollection#create! to be consistent with AssociationCollection#create when dealing with a foreign key that is a protected attribute [Cody Fauser]
-
-* Added counter optimization for AssociationCollection#any? so person.friends.any? won't actually load the full association if we have the count in a cheaper form [David Heinemeier Hansson]
-
-* Change fixture_path to a class inheritable accessor allowing test cases to have their own custom set of fixtures. #6672 [Zach Dennis]
-
-* Quote ActiveSupport::Multibyte::Chars. #6653 [Julian Tarkhanov]
-
-* Simplify query_attribute by typecasting the attribute value and checking whether it's nil, false, zero or blank. #6659 [Jonathan Viney]
-
-* validates_numericality_of uses \A \Z to ensure the entire string matches rather than ^ $ which may match one valid line of a multiline string. #5716 [Andreas Schwarz]
-
-* Run validations in the order they were declared. #6657 [obrie]
-
-* MySQL: detect when a NOT NULL column without a default value is misreported as default ''. Can't detect for string, text, and binary columns since '' is a legitimate default. #6156 [simon@redhillconsulting.com.au, obrie, Jonathan Viney, Jeremy Kemper]
-
-* Simplify association proxy implementation by factoring construct_scope out of method_missing. #6643 [martin]
-
-* Oracle: automatically detect the primary key. #6594 [vesaria, Michael Schoen]
-
-* Oracle: to increase performance, prefetch 100 rows and enable similar cursor sharing. Both are configurable in database.yml. #6607 [philbogle@gmail.com, ray.fortna@jobster.com, Michael Schoen]
-
-* Don't inspect unloaded associations. #2905 [lmarlow]
-
-* SQLite: use AUTOINCREMENT primary key in >= 3.1.0. #6588, #6616 [careo, lukfugl]
-
-* Cache inheritance_column. #6592 [Stefan Kaes]
-
-* Firebird: decimal/numeric support. #6408 [macrnic]
-
-* make add_order a tad faster. #6567 [Stefan Kaes]
-
-* Find with :include respects scoped :order. #5850
-
-* Support nil and Array in :conditions => { attr => value } hashes. #6548 [Assaf, Jeremy Kemper]
- find(:all, :conditions => { :topic_id => [1, 2, 3], :last_read => nil }
-
-* Consistently use LOWER() for uniqueness validations (rather than mixing with UPPER()) so the database can always use a functional index on the lowercased column. #6495 [Si]
-
-* SQLite: fix calculations workaround, remove count(distinct) query rewrite, cleanup test connection scripts. [Jeremy Kemper]
-
-* SQLite: count(distinct) queries supported in >= 3.2.6. #6544 [Bob Silva]
-
-* Dynamically generate reader methods for serialized attributes. #6362 [Stefan Kaes]
-
-* Deprecation: object transactions warning. [Jeremy Kemper]
-
-* has_one :dependent => :nullify ignores nil associates. #4848, #6528 [bellis@deepthought.org, janovetz, Jeremy Kemper]
-
-* Oracle: resolve test failures, use prefetched primary key for inserts, check for null defaults, fix limited id selection for eager loading. Factor out some common methods from all adapters. #6515 [Michael Schoen]
-
-* Make add_column use the options hash with the Sqlite Adapter. Closes #6464 [obrie]
-
-* Document other options available to migration's add_column. #6419 [grg]
-
-* MySQL: all_hashes compatibility with old MysqlRes class. #6429, #6601 [Jeremy Kemper]
-
-* Fix has_many :through to add the appropriate conditions when going through an association using STI. Closes #5783. [Jonathan Viney]
-
-* fix select_limited_ids_list issues in postgresql, retain current behavior in other adapters [Rick Olson]
-
-* Restore eager condition interpolation, document it's differences [Rick Olson]
-
-* Don't rollback in teardown unless a transaction was started. Don't start a transaction in create_fixtures if a transaction is started. #6282 [Jacob Fugal, Jeremy Kemper]
-
-* Add #delete support to has_many :through associations. Closes #6049 [Martin Landers]
-
-* Reverted old select_limited_ids_list postgresql fix that caused issues in mysql. Closes #5851 [Rick Olson]
-
-* Removes the ability for eager loaded conditions to be interpolated, since there is no model instance to use as a context for interpolation. #5553 [turnip@turnipspatch.com]
-
-* Added timeout option to SQLite3 configurations to deal more gracefully with SQLite3::BusyException, now the connection can instead retry for x seconds to see if the db clears up before throwing that exception #6126 [wreese@gmail.com]
-
-* Added update_attributes! which uses save! to raise an exception if a validation error prevents saving #6192 [jonathan]
-
-* Deprecated add_on_boundary_breaking (use validates_length_of instead) #6292 [Bob Silva]
-
-* The has_many create method works with polymorphic associations. #6361 [Dan Peterson]
-
-* MySQL: introduce Mysql::Result#all_hashes to support further optimization. #5581 [Stefan Kaes]
-
-* save! shouldn't validate twice. #6324 [maiha, Bob Silva]
-
-* Association collections have an _ids reader method to match the existing writer for collection_select convenience (e.g. employee.task_ids). The writer method skips blank ids so you can safely do @employee.task_ids = params[:tasks] without checking every time for an empty list or blank values. #1887, #5780 [Michael Schuerig]
-
-* Add an attribute reader method for ActiveRecord::Base.observers [Rick Olson]
-
-* Deprecation: count class method should be called with an options hash rather than two args for conditions and joins. #6287 [Bob Silva]
-
-* has_one associations with a nil target may be safely marshaled. #6279 [norbauer, Jeremy Kemper]
-
-* Duplicate the hash provided to AR::Base#to_xml to prevent unexpected side effects [Michael Koziarski]
-
-* Add a :namespace option to AR::Base#to_xml [Michael Koziarski]
-
-* Deprecation tests. Remove warnings for dynamic finders and for the foo_count method if it's also an attribute. [Jeremy Kemper]
-
-* Mock Time.now for more accurate Touch mixin tests. #6213 [Dan Peterson]
-
-* Improve yaml fixtures error reporting. #6205 [Bruce Williams]
-
-* Rename AR::Base#quote so people can use that name in their models. #3628 [Michael Koziarski]
-
-* Add deprecation warning for inferred foreign key. #6029 [Josh Susser]
-
-* Fixed the Ruby/MySQL adapter we ship with Active Record to work with the new authentication handshake that was introduced in MySQL 4.1, along with the other protocol changes made at that time #5723 [jimw@mysql.com]
-
-* Deprecation: use :dependent => :delete_all rather than :exclusively_dependent => true. #6024 [Josh Susser]
-
-* Document validates_presences_of behavior with booleans: you probably want validates_inclusion_of :attr, :in => [true, false]. #2253 [Bob Silva]
-
-* Optimistic locking: gracefully handle nil versions, treat as zero. #5908 [Tom Ward]
-
-* to_xml: the :methods option works on arrays of records. #5845 [Josh Starcher]
-
-* Deprecation: update docs. #5998 [Jakob Skjerning, Kevin Clark]
-
-* Add some XmlSerialization tests for ActiveRecord [Rick Olson]
-
-* has_many :through conditions are sanitized by the associating class. #5971 [martin.emde@gmail.com]
-
-* Tighten rescue clauses. #5985 [james@grayproductions.net]
-
-* Fix spurious newlines and spaces in AR::Base#to_xml output [Jamis Buck]
-
-* has_one supports the :dependent => :delete option which skips the typical callback chain and deletes the associated object directly from the database. #5927 [Chris Mear, Jonathan Viney]
-
-* Nested subclasses are not prefixed with the parent class' table_name since they should always use the base class' table_name. #5911 [Jonathan Viney]
-
-* SQLServer: work around bug where some unambiguous date formats are not correctly identified if the session language is set to german. #5894 [Tom Ward, kruth@bfpi]
-
-* SQLServer: fix eager association test. #5901 [Tom Ward]
-
-* Clashing type columns due to a sloppy join shouldn't wreck single-table inheritance. #5838 [Kevin Clark]
-
-* Fixtures: correct escaping of \n and \r. #5859 [evgeny.zislis@gmail.com]
-
-* Migrations: gracefully handle missing migration files. #5857 [eli.gordon@gmail.com]
-
-* MySQL: update test schema for MySQL 5 strict mode. #5861 [Tom Ward]
-
-* to_xml: correct naming of included associations. #5831 [Josh Starcher]
-
-* Pushing a record onto a has_many :through sets the association's foreign key to the associate's primary key and adds it to the correct association. #5815, #5829 [Josh Susser]
-
-* Add records to has_many :through using <<, push, and concat by creating the association record. Raise if base or associate are new records since both ids are required to create the association. #build raises since you can't associate an unsaved record. #create! takes an attributes hash and creates the associated record and its association in a transaction. [Jeremy Kemper]
-
- # Create a tagging to associate the post and tag.
- post.tags << Tag.find_by_name('old')
- post.tags.create! :name => 'general'
-
- # Would have been:
- post.taggings.create!(:tag => Tag.find_by_name('finally')
- transaction do
- post.taggings.create!(:tag => Tag.create!(:name => 'general'))
- end
-
-* Cache nil results for :included has_one associations also. #5787 [Michael Schoen]
-
-* Fixed a bug which would cause .save to fail after trying to access a empty has_one association on a unsaved record. [Tobias Lütke]
-
-* Nested classes are given table names prefixed by the singular form of the parent's table name. [Jeremy Kemper]
- Example: Invoice::Lineitem is given table name invoice_lineitems
-
-* Migrations: uniquely name multicolumn indexes so you don't have to. [Jeremy Kemper]
- # people_active_last_name_index, people_active_deactivated_at_index
- add_index :people, [:active, :last_name]
- add_index :people, [:active, :deactivated_at]
- remove_index :people, [:active, :last_name]
- remove_index :people, [:active, :deactivated_at]
-
- WARNING: backward-incompatibility. Multicolumn indexes created before this
- revision were named using the first column name only. Now they're uniquely
- named using all indexed columns.
-
- To remove an old multicolumn index, remove_index :table_name, :first_column
-
-* Fix for deep includes on the same association. [richcollins@gmail.com]
-
-* Tweak fixtures so they don't try to use a non-ActiveRecord class. [Kevin Clark]
-
-* Remove ActiveRecord::Base.reset since Dispatcher doesn't use it anymore. [Rick Olson]
-
-* Document find's :from option. Closes #5762. [andrew@redlinesoftware.com]
-
-* PostgreSQL: autodetected sequences work correctly with multiple schemas. Rely on the schema search_path instead of explicitly qualifying the sequence name with its schema. #5280 [guy.naor@famundo.com]
-
-* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
-
-* Cache nil results for has_one associations so multiple calls don't call the database. Closes #5757. [Michael Schoen]
-
-* Add documentation for how to disable timestamps on a per model basis. Closes #5684. [matt@mattmargolis.net Marcel Molina Jr.]
-
-* Don't save has_one associations unnecessarily. #5735 [Jonathan Viney]
-
-* Refactor ActiveRecord::Base.reset_subclasses to #reset, and add global observer resetting. [Rick Olson]
-
-* Formally deprecate the deprecated finders. [Michael Koziarski]
-
-* Formally deprecate rich associations. [Michael Koziarski]
-
-* Fixed that default timezones for new / initialize should uphold utc setting #5709 [daniluk@yahoo.com]
-
-* Fix announcement of very long migration names. #5722 [blake@near-time.com]
-
-* The exists? class method should treat a string argument as an id rather than as conditions. #5698 [jeremy@planetargon.com]
-
-* Fixed to_xml with :include misbehaviors when invoked on array of model instances #5690 [alexkwolfe@gmail.com]
-
-* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
-
- assert (Topic.exists?(:author_name => "David"))
- assert (Topic.exists?(:author_name => "Mary", :approved => true))
- assert (Topic.exists?(["parent_id = ?", 1]))
-
-* Schema dumper quotes date :default values. [Dave Thomas]
-
-* Calculate sum with SQL, not Enumerable on HasManyThrough Associations. [Dan Peterson]
-
-* Factor the attribute#{suffix} methods out of method_missing for easier extension. [Jeremy Kemper]
-
-* Patch sql injection vulnerability when using integer or float columns. [Jamis Buck]
-
-* Allow #count through a has_many association to accept :include. [Dan Peterson]
-
-* create_table rdoc: suggest :id => false for habtm join tables. [Zed Shaw]
-
-* PostgreSQL: return array fields as strings. #4664 [Robby Russell]
-
-* SQLServer: added tests to ensure all database statements are closed, refactored identity_insert management code to use blocks, removed update/delete rowcount code out of execute and into update/delete, changed insert to go through execute method, removed unused quoting methods, disabled pessimistic locking tests as feature is currently unsupported, fixed RakeFile to load sqlserver specific tests whether running in ado or odbc mode, fixed support for recently added decimal types, added support for limits on integer types. #5670 [Tom Ward]
-
-* SQLServer: fix db:schema:dump case-sensitivity. #4684 [Will Rogers]
-
-* Oracle: BigDecimal support. #5667 [Michael Schoen]
-
-* Numeric and decimal columns map to BigDecimal instead of Float. Those with scale 0 map to Integer. #5454 [robbat2@gentoo.org, work@ashleymoran.me.uk]
-
-* Firebird migrations support. #5337 [Ken Kunz <kennethkunz@gmail.com>]
-
-* PostgreSQL: create/drop as postgres user. #4790 [mail@matthewpainter.co.uk, mlaster@metavillage.com]
-
-* Update callbacks documentation. #3970 [Robby Russell <robby@planetargon.com>]
-
-* PostgreSQL: correctly quote the ' in pk_and_sequence_for. #5462 [tietew@tietew.net]
-
-* PostgreSQL: correctly quote microseconds in timestamps. #5641 [rick@rickbradley.com]
-
-* Clearer has_one/belongs_to model names (account has_one :user). #5632 [matt@mattmargolis.net]
-
-* Oracle: use nonblocking queries if allow_concurrency is set, fix pessimistic locking, don't guess date vs. time by default (set OracleAdapter.emulate_dates = true for the old behavior), adapter cleanup. #5635 [Michael Schoen]
-
-* Fixed a few Oracle issues: Allows Oracle's odd date handling to still work consistently within #to_xml, Passes test that hardcode insert statement by dropping the :id column, Updated RUNNING_UNIT_TESTS with Oracle instructions, Corrects method signature for #exec #5294 [Michael Schoen]
-
-* Added :group to available options for finds done on associations #5516 [mike@michaeldewey.org]
-
-* Minor tweak to improve performance of ActiveRecord::Base#to_param.
-
-* Observers also watch subclasses created after they are declared. #5535 [daniels@pronto.com.au]
-
-* Removed deprecated timestamps_gmt class methods. [Jeremy Kemper]
-
-* rake build_mysql_database grants permissions to rails@localhost. #5501 [brianegge@yahoo.com]
-
-* PostgreSQL: support microsecond time resolution. #5492 [alex@msgpad.com]
-
-* Add AssociationCollection#sum since the method_missing invokation has been shadowed by Enumerable#sum.
-
-* Added find_or_initialize_by_X which works like find_or_create_by_X but doesn't save the newly instantiated record. [Sam Stephenson]
-
-* Row locking. Provide a locking clause with the :lock finder option or true for the default "FOR UPDATE". Use the #lock! method to obtain a row lock on a single record (reloads the record with :lock => true). [Shugo Maeda]
- # Obtain an exclusive lock on person 1 so we can safely increment visits.
- Person.transaction do
- # select * from people where id=1 for update
- person = Person.find(1, :lock => true)
- person.visits += 1
- person.save!
- end
-
-* PostgreSQL: introduce allow_concurrency option which determines whether to use blocking or asynchronous #execute. Adapters with blocking #execute will deadlock Ruby threads. The default value is ActiveRecord::Base.allow_concurrency. [Jeremy Kemper]
-
-* Use a per-thread (rather than global) transaction mutex so you may execute concurrent transactions on separate connections. [Jeremy Kemper]
-
-* Change AR::Base#to_param to return a String instead of a Fixnum. Closes #5320. [Nicholas Seckar]
-
-* Use explicit delegation instead of method aliasing for AR::Base.to_param -> AR::Base.id. #5299 (skaes@web.de)
-
-* Refactored ActiveRecord::Base.to_xml to become a delegate for XmlSerializer, which restores sanity to the mega method. This refactoring also reinstates the opinions that type="string" is redundant and ugly and nil-differentiation is not a concern of serialization [David Heinemeier Hansson]
-
-* Added simple hash conditions to find that'll just convert hash to an AND-based condition string #5143 [Hampton Catlin]. Example:
-
- Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2)
-
-...is the same as:
-
- Person.find(:all, :conditions => [ "last_name = ? and status = ?", "Catlin", 1 ], :limit => 2)
-
- This makes it easier to pass in the options from a form or otherwise outside.
-
-
-* Fixed issues with BLOB limits, charsets, and booleans for Firebird #5194, #5191, #5189 [kennethkunz@gmail.com]
-
-* Fixed usage of :limit and with_scope when the association in scope is a 1:m #5208 [alex@purefiction.net]
-
-* Fixed migration trouble with SQLite when NOT NULL is used in the new definition #5215 [greg@lapcominc.com]
-
-* Fixed problems with eager loading and counting on SQL Server #5212 [kajism@yahoo.com]
-
-* Fixed that count distinct should use the selected column even when using :include #5251 [anna@wota.jp]
-
-* Fixed that :includes merged from with_scope won't cause the same association to be loaded more than once if repetition occurs in the clauses #5253 [alex@purefiction.net]
-
-* Allow models to override to_xml. #4989 [Blair Zajac <blair@orcaware.com>]
-
-* PostgreSQL: don't ignore port when host is nil since it's often used to label the domain socket. #5247 [shimbo@is.naist.jp]
-
-* Records and arrays of records are bound as quoted ids. [Jeremy Kemper]
- Foo.find(:all, :conditions => ['bar_id IN (?)', bars])
- Foo.find(:first, :conditions => ['bar_id = ?', bar])
-
-* Fixed that Base.find :all, :conditions => [ "id IN (?)", collection ] would fail if collection was empty [David Heinemeier Hansson]
-
-* Add a list of regexes assert_queries skips in the ActiveRecord test suite. [Rick Olson]
-
-* Fix the has_and_belongs_to_many #create doesn't populate the join for new records. Closes #3692 [Josh Susser]
-
-* Provide Association Extensions access to the instance that the association is being accessed from.
- Closes #4433 [Josh Susser]
-
-* Update OpenBase adaterp's maintainer's email address. Closes #5176. [Derrick Spell]
-
-* Add a quick note about :select and eagerly included associations. [Rick Olson]
-
-* Add docs for the :as option in has_one associations. Closes #5144 [cdcarter@gmail.com]
-
-* Fixed that has_many collections shouldn't load the entire association to do build or create [David Heinemeier Hansson]
-
-* Added :allow_nil option for aggregations #5091 [Ian White]
-
-* Fix Oracle boolean support and tests. Closes #5139. [Michael Schoen]
-
-* create! no longer blows up when no attributes are passed and a :create scope is in effect (e.g. foo.bars.create! failed whereas foo.bars.create!({}) didn't.) [Jeremy Kemper]
-
-* Call Inflector#demodulize on the class name when eagerly including an STI model. Closes #5077 [info@loobmedia.com]
-
-* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. [pdcawley@bofh.org.uk]
-
-* PostgreSQL: migrations support :limit with :integer columns by mapping limit < 4 to smallint, > 4 to bigint, and anything else to integer. #2900 [keegan@thebasement.org]
-
-* Dates and times interpret empty strings as nil rather than 2000-01-01. #4830 [kajism@yahoo.com]
-
-* Allow :uniq => true with has_many :through associations. [Jeremy Kemper]
-
-* Ensure that StringIO is always available for the Schema dumper. [Marcel Molina Jr.]
-
-* Allow AR::Base#to_xml to include methods too. Closes #4921. [johan@textdrive.com]
-
-* Replace superfluous name_to_class_name variant with camelize. [Marcel Molina Jr.]
-
-* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
-
-* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
-
-* Remove duplicate fixture entry in comments.yml. Closes #4923. [Blair Zajac <blair@orcaware.com>]
-
-* Update FrontBase adapter to check binding version. Closes #4920. [mlaster@metavillage.com]
-
-* New Frontbase connections don't start in auto-commit mode. Closes #4922. [mlaster@metavillage.com]
-
-* When grouping, use the appropriate option key. [Marcel Molina Jr.]
-
-* Only modify the sequence name in the FrontBase adapter if the FrontBase adapter is actually being used. [Marcel Molina Jr.]
-
-* Add support for FrontBase (http://www.frontbase.com/) with a new adapter thanks to the hard work of one Mike Laster. Closes #4093. [mlaster@metavillage.com]
-
-* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. [Francois Beausoleil <francois.beausoleil@gmail.com>]
-
-* Fix syntax error in documentation. Closes #4679. [Mislav Marohnić]
-
-* Add Oracle support for CLOB inserts. Closes #4748. [schoenm@earthlink.net sandra.metz@duke.edu]
-
-* Various fixes for sqlserver_adapter (odbc statement finishing, ado schema dumper, drop index). Closes #4831. [kajism@yahoo.com]
-
-* Add support for :order option to with_scope. Closes #3887. [eric.daspet@survol.net]
-
-* Prettify output of schema_dumper by making things line up. Closes #4241 [Caio Chassot <caio@v2studio.com>]
-
-* Make build_postgresql_databases task make databases owned by the postgres user. Closes #4790. [mlaster@metavillage.com]
-
-* Sybase Adapter type conversion cleanup. Closes #4736. [dev@metacasa.net]
-
-* Fix bug where calculations with long alias names return null. [Rick Olson]
-
-* Raise error when trying to add to a has_many :through association. Use the Join Model instead. [Rick Olson]
-
- @post.tags << @tag # BAD
- @post.taggings.create(:tag => @tag) # GOOD
-
-* Allow all calculations to take the :include option, not just COUNT (closes #4840) [Rick Olson]
-
-* Update inconsistent migrations documentation. #4683 [machomagna@gmail.com]
-
-* Add ActiveRecord::Errors#to_xml [Jamis Buck]
-
-* Properly quote index names in migrations (closes #4764) [John Long]
-
-* Fix the HasManyAssociation#count method so it uses the new ActiveRecord::Base#count syntax, while maintaining backwards compatibility. [Rick Olson]
-
-* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions [Rick Olson]
-
-* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. [Rick Olson]
-
-* DRY up association collection reader method generation. [Marcel Molina Jr.]
-
-* DRY up and tweak style of the validation error object. [Marcel Molina Jr.]
-
-* Add :case_sensitive option to validates_uniqueness_of (closes #3090) [Rick Olson]
-
- class Account < ActiveRecord::Base
- validates_uniqueness_of :email, :case_sensitive => false
- end
-
-* Allow multiple association extensions with :extend option (closes #4666) [Josh Susser]
-
- class Account < ActiveRecord::Base
- has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
- end
-
- *1.15.3* (March 12th, 2007)
-
- * Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]
-
- * Consistently quote primary key column names. #7763 [toolmantim]
-
- * Fixtures: fix YAML ordered map support. #2665 [Manuel Holtgrewe, nfbuckley]
-
- * Fix has_many :through << with custom foreign keys. #6466, #7153 [naffis, Rich Collins]
-
-
-*1.15.2* (February 5th, 2007)
-
-* Pass a range in :conditions to use the SQL BETWEEN operator. #6974 [Dan Manges]
- Student.find(:all, :conditions => { :grade => 9..12 })
-
-* Don't create instance writer methods for class attributes. [Rick Olson]
-
-* When dealing with SQLite3, use the table_info pragma helper, so that the bindings can do some translation for when sqlite3 breaks incompatibly between point releases. [Jamis Buck]
-
-* SQLServer: don't choke on strings containing 'null'. #7083 [Jakob Skjerning]
-
-* Consistently use LOWER() for uniqueness validations (rather than mixing with UPPER()) so the database can always use a functional index on the lowercased column. #6495 [Si]
-
-* MySQL: SET SQL_AUTO_IS_NULL=0 so 'where id is null' doesn't select the last inserted id. #6778 [Jonathan Viney, timc]
-
-* Fixtures use the table name and connection from set_fixture_class. #7330 [Anthony Eden]
-
-* SQLServer: quote table name in indexes query. #2928 [keithm@infused.org]
-
-
-*1.15.1* (January 17th, 2007)
-
-* Fix nodoc breaking of adapters
-
-
-*1.15.0* (January 16th, 2007)
-
-* [DOC] clear up some ambiguity with the way has_and_belongs_to_many creates the default join table name. #7072 [Jeremy McAnally]
-
-* change_column accepts :default => nil. Skip column options for primary keys. #6956, #7048 [Dan Manges, Jeremy Kemper]
-
-* MySQL, PostgreSQL: change_column_default quotes the default value and doesn't lose column type information. #3987, #6664 [Jonathan Viney, Manfred Stienstra, altano@bigfoot.com]
-
-* Oracle: create_table takes a :sequence_name option to override the 'tablename_seq' default. #7000 [Michael Schoen]
-
-* MySQL: retain SSL settings on reconnect. #6976 [randyv2]
-
-* SQLServer: handle [quoted] table names. #6635 [rrich]
-
-* acts_as_nested_set works with single-table inheritance. #6030 [Josh Susser]
-
-* PostgreSQL, Oracle: correctly perform eager finds with :limit and :order. #4668, #7021 [eventualbuddha, Michael Schoen]
-
-* Fix the Oracle adapter for serialized attributes stored in CLOBs. Closes #6825 [mschoen, tdfowler]
-
-* [DOCS] Apply more documentation for ActiveRecord Reflection. Closes #4055 [Robby Russell]
-
-* [DOCS] Document :allow_nil option of #validate_uniqueness_of. Closes #3143 [Caio Chassot]
-
-* Bring the sybase adapter up to scratch for 1.2 release. [jsheets]
-
-* Oracle: fix connection reset failure. #6846 [leonlleslie]
-
-* Subclass instantiation doesn't try to explicitly require the corresponding subclass. #6840 [leei, Jeremy Kemper]
-
-* fix faulty inheritance tests and that eager loading grabs the wrong inheritance column when the class of your association is an STI subclass. Closes #6859 [protocool]
-
-* find supports :lock with :include. Check whether your database allows SELECT ... FOR UPDATE with outer joins before using. #6764 [vitaly, Jeremy Kemper]
-
-* Support nil and Array in :conditions => { attr => value } hashes. #6548 [Assaf, Jeremy Kemper]
- find(:all, :conditions => { :topic_id => [1, 2, 3], :last_read => nil }
-
-* Quote ActiveSupport::Multibyte::Chars. #6653 [Julian Tarkhanov]
-
-* MySQL: detect when a NOT NULL column without a default value is misreported as default ''. Can't detect for string, text, and binary columns since '' is a legitimate default. #6156 [simon@redhillconsulting.com.au, obrie, Jonathan Viney, Jeremy Kemper]
-
-* validates_numericality_of uses \A \Z to ensure the entire string matches rather than ^ $ which may match one valid line of a multiline string. #5716 [Andreas Schwarz]
-
-* Oracle: automatically detect the primary key. #6594 [vesaria, Michael Schoen]
-
-* Oracle: to increase performance, prefetch 100 rows and enable similar cursor sharing. Both are configurable in database.yml. #6607 [philbogle@gmail.com, ray.fortna@jobster.com, Michael Schoen]
-
-* Firebird: decimal/numeric support. #6408 [macrnic]
-
-* Find with :include respects scoped :order. #5850
-
-* Dynamically generate reader methods for serialized attributes. #6362 [Stefan Kaes]
-
-* Deprecation: object transactions warning. [Jeremy Kemper]
-
-* has_one :dependent => :nullify ignores nil associates. #6528 [janovetz, Jeremy Kemper]
-
-* Oracle: resolve test failures, use prefetched primary key for inserts, check for null defaults, fix limited id selection for eager loading. Factor out some common methods from all adapters. #6515 [Michael Schoen]
-
-* Make add_column use the options hash with the Sqlite Adapter. Closes #6464 [obrie]
-
-* Document other options available to migration's add_column. #6419 [grg]
-
-* MySQL: all_hashes compatibility with old MysqlRes class. #6429, #6601 [Jeremy Kemper]
-
-* Fix has_many :through to add the appropriate conditions when going through an association using STI. Closes #5783. [Jonathan Viney]
-
-* fix select_limited_ids_list issues in postgresql, retain current behavior in other adapters [Rick Olson]
-
-* Restore eager condition interpolation, document it's differences [Rick Olson]
-
-* Don't rollback in teardown unless a transaction was started. Don't start a transaction in create_fixtures if a transaction is started. #6282 [Jacob Fugal, Jeremy Kemper]
-
-* Add #delete support to has_many :through associations. Closes #6049 [Martin Landers]
-
-* Reverted old select_limited_ids_list postgresql fix that caused issues in mysql. Closes #5851 [Rick Olson]
-
-* Removes the ability for eager loaded conditions to be interpolated, since there is no model instance to use as a context for interpolation. #5553 [turnip@turnipspatch.com]
-
-* Added timeout option to SQLite3 configurations to deal more gracefully with SQLite3::BusyException, now the connection can instead retry for x seconds to see if the db clears up before throwing that exception #6126 [wreese@gmail.com]
-
-* Added update_attributes! which uses save! to raise an exception if a validation error prevents saving #6192 [jonathan]
-
-* Deprecated add_on_boundary_breaking (use validates_length_of instead) #6292 [Bob Silva]
-
-* The has_many create method works with polymorphic associations. #6361 [Dan Peterson]
-
-* MySQL: introduce Mysql::Result#all_hashes to support further optimization. #5581 [Stefan Kaes]
-
-* save! shouldn't validate twice. #6324 [maiha, Bob Silva]
-
-* Association collections have an _ids reader method to match the existing writer for collection_select convenience (e.g. employee.task_ids). The writer method skips blank ids so you can safely do @employee.task_ids = params[:tasks] without checking every time for an empty list or blank values. #1887, #5780 [Michael Schuerig]
-
-* Add an attribute reader method for ActiveRecord::Base.observers [Rick Olson]
-
-* Deprecation: count class method should be called with an options hash rather than two args for conditions and joins. #6287 [Bob Silva]
-
-* has_one associations with a nil target may be safely marshaled. #6279 [norbauer, Jeremy Kemper]
-
-* Duplicate the hash provided to AR::Base#to_xml to prevent unexpected side effects [Michael Koziarski]
-
-* Add a :namespace option to AR::Base#to_xml [Michael Koziarski]
-
-* Deprecation tests. Remove warnings for dynamic finders and for the foo_count method if it's also an attribute. [Jeremy Kemper]
-
-* Mock Time.now for more accurate Touch mixin tests. #6213 [Dan Peterson]
-
-* Improve yaml fixtures error reporting. #6205 [Bruce Williams]
-
-* Rename AR::Base#quote so people can use that name in their models. #3628 [Michael Koziarski]
-
-* Add deprecation warning for inferred foreign key. #6029 [Josh Susser]
-
-* Fixed the Ruby/MySQL adapter we ship with Active Record to work with the new authentication handshake that was introduced in MySQL 4.1, along with the other protocol changes made at that time #5723 [jimw@mysql.com]
-
-* Deprecation: use :dependent => :delete_all rather than :exclusively_dependent => true. #6024 [Josh Susser]
-
-* Optimistic locking: gracefully handle nil versions, treat as zero. #5908 [Tom Ward]
-
-* to_xml: the :methods option works on arrays of records. #5845 [Josh Starcher]
-
-* has_many :through conditions are sanitized by the associating class. #5971 [martin.emde@gmail.com]
-
-* Fix spurious newlines and spaces in AR::Base#to_xml output [Jamis Buck]
-
-* has_one supports the :dependent => :delete option which skips the typical callback chain and deletes the associated object directly from the database. #5927 [Chris Mear, Jonathan Viney]
-
-* Nested subclasses are not prefixed with the parent class' table_name since they should always use the base class' table_name. #5911 [Jonathan Viney]
-
-* SQLServer: work around bug where some unambiguous date formats are not correctly identified if the session language is set to german. #5894 [Tom Ward, kruth@bfpi]
-
-* Clashing type columns due to a sloppy join shouldn't wreck single-table inheritance. #5838 [Kevin Clark]
-
-* Fixtures: correct escaping of \n and \r. #5859 [evgeny.zislis@gmail.com]
-
-* Migrations: gracefully handle missing migration files. #5857 [eli.gordon@gmail.com]
-
-* MySQL: update test schema for MySQL 5 strict mode. #5861 [Tom Ward]
-
-* to_xml: correct naming of included associations. #5831 [Josh Starcher]
-
-* Pushing a record onto a has_many :through sets the association's foreign key to the associate's primary key and adds it to the correct association. #5815, #5829 [Josh Susser]
-
-* Add records to has_many :through using <<, push, and concat by creating the association record. Raise if base or associate are new records since both ids are required to create the association. #build raises since you can't associate an unsaved record. #create! takes an attributes hash and creates the associated record and its association in a transaction. [Jeremy Kemper]
-
- # Create a tagging to associate the post and tag.
- post.tags << Tag.find_by_name('old')
- post.tags.create! :name => 'general'
-
- # Would have been:
- post.taggings.create!(:tag => Tag.find_by_name('finally')
- transaction do
- post.taggings.create!(:tag => Tag.create!(:name => 'general'))
- end
-
-* Cache nil results for :included has_one associations also. #5787 [Michael Schoen]
-
-* Fixed a bug which would cause .save to fail after trying to access a empty has_one association on a unsaved record. [Tobias Lütke]
-
-* Nested classes are given table names prefixed by the singular form of the parent's table name. [Jeremy Kemper]
- Example: Invoice::Lineitem is given table name invoice_lineitems
-
-* Migrations: uniquely name multicolumn indexes so you don't have to. [Jeremy Kemper]
- # people_active_last_name_index, people_active_deactivated_at_index
- add_index :people, [:active, :last_name]
- add_index :people, [:active, :deactivated_at]
- remove_index :people, [:active, :last_name]
- remove_index :people, [:active, :deactivated_at]
-
- WARNING: backward-incompatibility. Multicolumn indexes created before this
- revision were named using the first column name only. Now they're uniquely
- named using all indexed columns.
-
- To remove an old multicolumn index, remove_index :table_name, :first_column
-
-* Fix for deep includes on the same association. [richcollins@gmail.com]
-
-* Tweak fixtures so they don't try to use a non-ActiveRecord class. [Kevin Clark]
-
-* Remove ActiveRecord::Base.reset since Dispatcher doesn't use it anymore. [Rick Olson]
-
-* PostgreSQL: autodetected sequences work correctly with multiple schemas. Rely on the schema search_path instead of explicitly qualifying the sequence name with its schema. #5280 [guy.naor@famundo.com]
-
-* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
-
-* Cache nil results for has_one associations so multiple calls don't call the database. Closes #5757. [Michael Schoen]
-
-* Don't save has_one associations unnecessarily. #5735 [Jonathan Viney]
-
-* Refactor ActiveRecord::Base.reset_subclasses to #reset, and add global observer resetting. [Rick Olson]
-
-* Formally deprecate the deprecated finders. [Michael Koziarski]
-
-* Formally deprecate rich associations. [Michael Koziarski]
-
-* Fixed that default timezones for new / initialize should uphold utc setting #5709 [daniluk@yahoo.com]
-
-* Fix announcement of very long migration names. #5722 [blake@near-time.com]
-
-* The exists? class method should treat a string argument as an id rather than as conditions. #5698 [jeremy@planetargon.com]
-
-* Fixed to_xml with :include misbehaviors when invoked on array of model instances #5690 [alexkwolfe@gmail.com]
-
-* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
-
- assert (Topic.exists?(:author_name => "David"))
- assert (Topic.exists?(:author_name => "Mary", :approved => true))
- assert (Topic.exists?(["parent_id = ?", 1]))
-
-* Schema dumper quotes date :default values. [Dave Thomas]
-
-* Calculate sum with SQL, not Enumerable on HasManyThrough Associations. [Dan Peterson]
-
-* Factor the attribute#{suffix} methods out of method_missing for easier extension. [Jeremy Kemper]
-
-* Patch sql injection vulnerability when using integer or float columns. [Jamis Buck]
-
-* Allow #count through a has_many association to accept :include. [Dan Peterson]
-
-* create_table rdoc: suggest :id => false for habtm join tables. [Zed Shaw]
-
-* PostgreSQL: return array fields as strings. #4664 [Robby Russell]
-
-* SQLServer: added tests to ensure all database statements are closed, refactored identity_insert management code to use blocks, removed update/delete rowcount code out of execute and into update/delete, changed insert to go through execute method, removed unused quoting methods, disabled pessimistic locking tests as feature is currently unsupported, fixed RakeFile to load sqlserver specific tests whether running in ado or odbc mode, fixed support for recently added decimal types, added support for limits on integer types. #5670 [Tom Ward]
-
-* SQLServer: fix db:schema:dump case-sensitivity. #4684 [Will Rogers]
-
-* Oracle: BigDecimal support. #5667 [Michael Schoen]
-
-* Numeric and decimal columns map to BigDecimal instead of Float. Those with scale 0 map to Integer. #5454 [robbat2@gentoo.org, work@ashleymoran.me.uk]
-
-* Firebird migrations support. #5337 [Ken Kunz <kennethkunz@gmail.com>]
-
-* PostgreSQL: create/drop as postgres user. #4790 [mail@matthewpainter.co.uk, mlaster@metavillage.com]
-
-* PostgreSQL: correctly quote the ' in pk_and_sequence_for. #5462 [tietew@tietew.net]
-
-* PostgreSQL: correctly quote microseconds in timestamps. #5641 [rick@rickbradley.com]
-
-* Clearer has_one/belongs_to model names (account has_one :user). #5632 [matt@mattmargolis.net]
-
-* Oracle: use nonblocking queries if allow_concurrency is set, fix pessimistic locking, don't guess date vs. time by default (set OracleAdapter.emulate_dates = true for the old behavior), adapter cleanup. #5635 [Michael Schoen]
-
-* Fixed a few Oracle issues: Allows Oracle's odd date handling to still work consistently within #to_xml, Passes test that hardcode insert statement by dropping the :id column, Updated RUNNING_UNIT_TESTS with Oracle instructions, Corrects method signature for #exec #5294 [Michael Schoen]
-
-* Added :group to available options for finds done on associations #5516 [mike@michaeldewey.org]
-
-* Observers also watch subclasses created after they are declared. #5535 [daniels@pronto.com.au]
-
-* Removed deprecated timestamps_gmt class methods. [Jeremy Kemper]
-
-* rake build_mysql_database grants permissions to rails@localhost. #5501 [brianegge@yahoo.com]
-
-* PostgreSQL: support microsecond time resolution. #5492 [alex@msgpad.com]
-
-* Add AssociationCollection#sum since the method_missing invokation has been shadowed by Enumerable#sum.
-
-* Added find_or_initialize_by_X which works like find_or_create_by_X but doesn't save the newly instantiated record. [Sam Stephenson]
-
-* Row locking. Provide a locking clause with the :lock finder option or true for the default "FOR UPDATE". Use the #lock! method to obtain a row lock on a single record (reloads the record with :lock => true). [Shugo Maeda]
- # Obtain an exclusive lock on person 1 so we can safely increment visits.
- Person.transaction do
- # select * from people where id=1 for update
- person = Person.find(1, :lock => true)
- person.visits += 1
- person.save!
- end
-
-* PostgreSQL: introduce allow_concurrency option which determines whether to use blocking or asynchronous #execute. Adapters with blocking #execute will deadlock Ruby threads. The default value is ActiveRecord::Base.allow_concurrency. [Jeremy Kemper]
-
-* Use a per-thread (rather than global) transaction mutex so you may execute concurrent transactions on separate connections. [Jeremy Kemper]
-
-* Change AR::Base#to_param to return a String instead of a Fixnum. Closes #5320. [Nicholas Seckar]
-
-* Use explicit delegation instead of method aliasing for AR::Base.to_param -> AR::Base.id. #5299 (skaes@web.de)
-
-* Refactored ActiveRecord::Base.to_xml to become a delegate for XmlSerializer, which restores sanity to the mega method. This refactoring also reinstates the opinions that type="string" is redundant and ugly and nil-differentiation is not a concern of serialization [David Heinemeier Hansson]
-
-* Added simple hash conditions to find that'll just convert hash to an AND-based condition string #5143 [Hampton Catlin]. Example:
-
- Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2)
-
-...is the same as:
-
- Person.find(:all, :conditions => [ "last_name = ? and status = ?", "Catlin", 1 ], :limit => 2)
-
- This makes it easier to pass in the options from a form or otherwise outside.
-
-
-* Fixed issues with BLOB limits, charsets, and booleans for Firebird #5194, #5191, #5189 [kennethkunz@gmail.com]
-
-* Fixed usage of :limit and with_scope when the association in scope is a 1:m #5208 [alex@purefiction.net]
-
-* Fixed migration trouble with SQLite when NOT NULL is used in the new definition #5215 [greg@lapcominc.com]
-
-* Fixed problems with eager loading and counting on SQL Server #5212 [kajism@yahoo.com]
-
-* Fixed that count distinct should use the selected column even when using :include #5251 [anna@wota.jp]
-
-* Fixed that :includes merged from with_scope won't cause the same association to be loaded more than once if repetition occurs in the clauses #5253 [alex@purefiction.net]
-
-* Allow models to override to_xml. #4989 [Blair Zajac <blair@orcaware.com>]
-
-* PostgreSQL: don't ignore port when host is nil since it's often used to label the domain socket. #5247 [shimbo@is.naist.jp]
-
-* Records and arrays of records are bound as quoted ids. [Jeremy Kemper]
- Foo.find(:all, :conditions => ['bar_id IN (?)', bars])
- Foo.find(:first, :conditions => ['bar_id = ?', bar])
-
-* Fixed that Base.find :all, :conditions => [ "id IN (?)", collection ] would fail if collection was empty [David Heinemeier Hansson]
-
-* Add a list of regexes assert_queries skips in the ActiveRecord test suite. [Rick Olson]
-
-* Fix the has_and_belongs_to_many #create doesn't populate the join for new records. Closes #3692 [Josh Susser]
-
-* Provide Association Extensions access to the instance that the association is being accessed from.
- Closes #4433 [Josh Susser]
-
-* Update OpenBase adaterp's maintainer's email address. Closes #5176. [Derrick Spell]
-
-* Add a quick note about :select and eagerly included associations. [Rick Olson]
-
-* Add docs for the :as option in has_one associations. Closes #5144 [cdcarter@gmail.com]
-
-* Fixed that has_many collections shouldn't load the entire association to do build or create [David Heinemeier Hansson]
-
-* Added :allow_nil option for aggregations #5091 [Ian White]
-
-* Fix Oracle boolean support and tests. Closes #5139. [Michael Schoen]
-
-* create! no longer blows up when no attributes are passed and a :create scope is in effect (e.g. foo.bars.create! failed whereas foo.bars.create!({}) didn't.) [Jeremy Kemper]
-
-* Call Inflector#demodulize on the class name when eagerly including an STI model. Closes #5077 [info@loobmedia.com]
-
-* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. [pdcawley@bofh.org.uk]
-
-* PostgreSQL: migrations support :limit with :integer columns by mapping limit < 4 to smallint, > 4 to bigint, and anything else to integer. #2900 [keegan@thebasement.org]
-
-* Dates and times interpret empty strings as nil rather than 2000-01-01. #4830 [kajism@yahoo.com]
-
-* Allow :uniq => true with has_many :through associations. [Jeremy Kemper]
-
-* Ensure that StringIO is always available for the Schema dumper. [Marcel Molina Jr.]
-
-* Allow AR::Base#to_xml to include methods too. Closes #4921. [johan@textdrive.com]
-
-* Remove duplicate fixture entry in comments.yml. Closes #4923. [Blair Zajac <blair@orcaware.com>]
-
-* When grouping, use the appropriate option key. [Marcel Molina Jr.]
-
-* Add support for FrontBase (http://www.frontbase.com/) with a new adapter thanks to the hard work of one Mike Laster. Closes #4093. [mlaster@metavillage.com]
-
-* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. [Francois Beausoleil <francois.beausoleil@gmail.com>]
-
-* Fix syntax error in documentation. Closes #4679. [Mislav Marohnić]
-
-* Add Oracle support for CLOB inserts. Closes #4748. [schoenm@earthlink.net sandra.metz@duke.edu]
-
-* Various fixes for sqlserver_adapter (odbc statement finishing, ado schema dumper, drop index). Closes #4831. [kajism@yahoo.com]
-
-* Add support for :order option to with_scope. Closes #3887. [eric.daspet@survol.net]
-
-* Prettify output of schema_dumper by making things line up. Closes #4241 [Caio Chassot <caio@v2studio.com>]
-
-* Make build_postgresql_databases task make databases owned by the postgres user. Closes #4790. [mlaster@metavillage.com]
-
-* Sybase Adapter type conversion cleanup. Closes #4736. [dev@metacasa.net]
-
-* Fix bug where calculations with long alias names return null. [Rick Olson]
-
-* Raise error when trying to add to a has_many :through association. Use the Join Model instead. [Rick Olson]
-
- @post.tags << @tag # BAD
- @post.taggings.create(:tag => @tag) # GOOD
-
-* Allow all calculations to take the :include option, not just COUNT (closes #4840) [Rick Olson]
-
-* Add ActiveRecord::Errors#to_xml [Jamis Buck]
-
-* Properly quote index names in migrations (closes #4764) [John Long]
-
-* Fix the HasManyAssociation#count method so it uses the new ActiveRecord::Base#count syntax, while maintaining backwards compatibility. [Rick Olson]
-
-* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions [Rick Olson]
-
-* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. [Rick Olson]
-
-* Add :case_sensitive option to validates_uniqueness_of (closes #3090) [Rick Olson]
-
- class Account < ActiveRecord::Base
- validates_uniqueness_of :email, :case_sensitive => false
- end
-
-* Allow multiple association extensions with :extend option (closes #4666) [Josh Susser]
-
- class Account < ActiveRecord::Base
- has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
- end
-
-
-*1.14.4* (August 8th, 2006)
-
-* Add warning about the proper way to validate the presence of a foreign key. #4147 [Francois Beausoleil <francois.beausoleil@gmail.com>]
-
-* Fix syntax error in documentation. #4679 [Mislav Marohnić]
-
-* Update inconsistent migrations documentation. #4683 [machomagna@gmail.com]
-
-
-*1.14.3* (June 27th, 2006)
-
-* Fix announcement of very long migration names. #5722 [blake@near-time.com]
-
-* Update callbacks documentation. #3970 [Robby Russell <robby@planetargon.com>]
-
-* Properly quote index names in migrations (closes #4764) [John Long]
-
-* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions [Rick Olson]
-
-* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. [Rick Olson]
-
-
-*1.14.2* (April 9th, 2006)
-
-* Fixed calculations for the Oracle Adapter (closes #4626) [Michael Schoen]
-
-
-*1.14.1* (April 6th, 2006)
-
-* Fix type_name_with_module to handle type names that begin with '::'. Closes #4614. [Nicholas Seckar]
-
-* Fixed that that multiparameter assignment doesn't work with aggregations (closes #4620) [Lars Pind]
-
-* Enable Limit/Offset in Calculations (closes #4558) [lmarlow]
-
-* Fixed that loading including associations returns all results if Load IDs For Limited Eager Loading returns none (closes #4528) [Rick Olson]
-
-* Fixed HasManyAssociation#find bugs when :finder_sql is set #4600 [lagroue@free.fr]
-
-* Allow AR::Base#respond_to? to behave when @attributes is nil [Ryan Davis]
-
-* Support eager includes when going through a polymorphic has_many association. [Rick Olson]
-
-* Added support for eagerly including polymorphic has_one associations. (closes #4525) [Rick Olson]
-
- class Post < ActiveRecord::Base
- has_one :tagging, :as => :taggable
- end
-
- Post.find :all, :include => :tagging
-
-* Added descriptive error messages for invalid has_many :through associations: going through :has_one or :has_and_belongs_to_many [Rick Olson]
-
-* Added support for going through a polymorphic has_many association: (closes #4401) [Rick Olson]
-
- class PhotoCollection < ActiveRecord::Base
- has_many :photos, :as => :photographic
- belongs_to :firm
- end
-
- class Firm < ActiveRecord::Base
- has_many :photo_collections
- has_many :photos, :through => :photo_collections
- end
-
-* Multiple fixes and optimizations in PostgreSQL adapter, allowing ruby-postgres gem to work properly. [ruben.nine@gmail.com]
-
-* Fixed that AssociationCollection#delete_all should work even if the records of the association are not loaded yet. [Florian Weber]
-
-* Changed those private ActiveRecord methods to take optional third argument :auto instead of nil for performance optimizations. (closes #4456) [Stefan]
-
-* Private ActiveRecord methods add_limit!, add_joins!, and add_conditions! take an OPTIONAL third argument 'scope' (closes #4456) [Rick Olson]
-
-* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model [David Heinemeier Hansson]
-
-* Fixed that records returned from has_and_belongs_to_many associations with additional attributes should be marked as read only (fixes #4512) [David Heinemeier Hansson]
-
-* Do not implicitly mark recordss of has_many :through as readonly but do mark habtm records as readonly (eventually only on join tables without rich attributes). [Marcel Mollina Jr.]
-
-* Fixed broken OCIAdapter #4457 [Michael Schoen]
-
-
-*1.14.0* (March 27th, 2006)
-
-* Replace 'rescue Object' with a finer grained rescue. Closes #4431. [Nicholas Seckar]
-
-* Fixed eager loading so that an aliased table cannot clash with a has_and_belongs_to_many join table [Rick Olson]
-
-* Add support for :include to with_scope [andrew@redlinesoftware.com]
-
-* Support the use of public synonyms with the Oracle adapter; required ruby-oci8 v0.1.14 #4390 [Michael Schoen]
-
-* Change periods (.) in table aliases to _'s. Closes #4251 [jeff@ministrycentered.com]
-
-* Changed has_and_belongs_to_many join to INNER JOIN for Mysql 3.23.x. Closes #4348 [Rick Olson]
-
-* Fixed issue that kept :select options from being scoped [Rick Olson]
-
-* Fixed db_schema_import when binary types are present #3101 [David Heinemeier Hansson]
-
-* Fixed that MySQL enums should always be returned as strings #3501 [David Heinemeier Hansson]
-
-* Change has_many :through to use the :source option to specify the source association. :class_name is now ignored. [Rick Olson]
-
- class Connection < ActiveRecord::Base
- belongs_to :user
- belongs_to :channel
- end
-
- class Channel < ActiveRecord::Base
- has_many :connections
- has_many :contacts, :through => :connections, :class_name => 'User' # OLD
- has_many :contacts, :through => :connections, :source => :user # NEW
- end
-
-* Fixed DB2 adapter so nullable columns will be determines correctly now and quotes from column default values will be removed #4350 [contact@maik-schmidt.de]
-
-* Allow overriding of find parameters in scoped has_many :through calls [Rick Olson]
-
- In this example, :include => false disables the default eager association from loading. :select changes the standard
- select clause. :joins specifies a join that is added to the end of the has_many :through query.
-
- class Post < ActiveRecord::Base
- has_many :tags, :through => :taggings, :include => :tagging do
- def add_joins_and_select
- find :all, :select => 'tags.*, authors.id as author_id', :include => false,
- :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
- end
- end
- end
-
-* Fixed that schema changes while the database was open would break any connections to an SQLite database (now we reconnect if that error is throw) [David Heinemeier Hansson]
-
-* Don't classify the has_one class when eager loading, it is already singular. Add tests. (closes #4117) [Jonathan Viney]
-
-* Quit ignoring default :include options in has_many :through calls [Mark James]
-
-* Allow has_many :through associations to find the source association by setting a custom class (closes #4307) [Jonathan Viney]
-
-* Eager Loading support added for has_many :through => :has_many associations (see below). [Rick Olson]
-
-* Allow has_many :through to work on has_many associations (closes #3864) [sco@scottraymond.net] Example:
-
- class Firm < ActiveRecord::Base
- has_many :clients
- has_many :invoices, :through => :clients
- end
-
- class Client < ActiveRecord::Base
- belongs_to :firm
- has_many :invoices
- end
-
- class Invoice < ActiveRecord::Base
- belongs_to :client
- end
-
-* Raise error when trying to select many polymorphic objects with has_many :through or :include (closes #4226) [Josh Susser]
-
-* Fixed has_many :through to include :conditions set on the :through association. closes #4020 [Jonathan Viney]
-
-* Fix that has_many :through honors the foreign key set by the belongs_to association in the join model (closes #4259) [andylien@gmail.com / Rick Olson]
-
-* SQL Server adapter gets some love #4298 [Ryan Tomayko]
-
-* Added OpenBase database adapter that builds on top of the http://www.spice-of-life.net/ruby-openbase/ driver. All functionality except LIMIT/OFFSET is supported #3528 [derrickspell@cdmplus.com]
-
-* Rework table aliasing to account for truncated table aliases. Add smarter table aliasing when doing eager loading of STI associations. This allows you to use the association name in the order/where clause. [Jonathan Viney / Rick Olson] #4108 Example (SpecialComment is using STI):
-
- Author.find(:all, :include => { :posts => :special_comments }, :order => 'special_comments.body')
-
-* Add AbstractAdapter#table_alias_for to create table aliases according to the rules of the current adapter. [Rick Olson]
-
-* Provide access to the underlying database connection through Adapter#raw_connection. Enables the use of db-specific methods without complicating the adapters. #2090 [Michael Koziarski]
-
-* Remove broken attempts at handling columns with a default of 'now()' in the postgresql adapter. #2257 [Michael Koziarski]
-
-* Added connection#current_database that'll return of the current database (only works in MySQL, SQL Server, and Oracle so far -- please help implement for the rest of the adapters) #3663 [Tom Ward]
-
-* Fixed that Migration#execute would have the table name prefix appended to its query #4110 [mark.imbriaco@pobox.com]
-
-* Make all tinyint(1) variants act like boolean in mysql (tinyint(1) unsigned, etc.) [Jamis Buck]
-
-* Use association's :conditions when eager loading. [Jeremy Evans] #4144
-
-* Alias the has_and_belongs_to_many join table on eager includes. #4106 [Jeremy Evans]
-
- This statement would normally error because the projects_developers table is joined twice, and therefore joined_on would be ambiguous.
-
- Developer.find(:all, :include => {:projects => :developers}, :conditions => 'join_project_developers.joined_on IS NOT NULL')
-
-* Oracle adapter gets some love #4230 [Michael Schoen]
-
- * Changes :text to CLOB rather than BLOB [Moses Hohman]
- * Fixes an issue with nil numeric length/scales (several)
- * Implements support for XMLTYPE columns [wilig / Kubo Takehiro]
- * Tweaks a unit test to get it all green again
- * Adds support for #current_database
-
-* Added Base.abstract_class? that marks which classes are not part of the Active Record hierarchy #3704 [Rick Olson]
-
- class CachedModel < ActiveRecord::Base
- self.abstract_class = true
- end
-
- class Post < CachedModel
- end
-
- CachedModel.abstract_class?
- => true
-
- Post.abstract_class?
- => false
-
- Post.base_class
- => Post
-
- Post.table_name
- => 'posts'
-
-* Allow :dependent options to be used with polymorphic joins. #3820 [Rick Olson]
-
- class Foo < ActiveRecord::Base
- has_many :attachments, :as => :attachable, :dependent => :delete_all
- end
-
-* Nicer error message on has_many :through when :through reflection can not be found. #4042 [court3nay]
-
-* Upgrade to Transaction::Simple 1.3 [Jamis Buck]
-
-* Catch FixtureClassNotFound when using instantiated fixtures on a fixture that has no ActiveRecord model [Rick Olson]
-
-* Allow ordering of calculated results and/or grouped fields in calculations [solo@gatelys.com]
-
-* Make ActiveRecord::Base#save! return true instead of nil on success. #4173 [johan@johansorensen.com]
-
-* Dynamically set allow_concurrency. #4044 [Stefan Kaes]
-
-* Added Base#to_xml that'll turn the current record into a XML representation [David Heinemeier Hansson]. Example:
-
- topic.to_xml
-
- ...returns:
-
- <?xml version="1.0" encoding="UTF-8"?>
- <topic>
- <title>The First Topic</title>
- <author-name>David</author-name>
- <id type="integer">1</id>
- <approved type="boolean">false</approved>
- <replies-count type="integer">0</replies-count>
- <bonus-time type="datetime">2000-01-01 08:28:00</bonus-time>
- <written-on type="datetime">2003-07-16 09:28:00</written-on>
- <content>Have a nice day</content>
- <author-email-address>david@loudthinking.com</author-email-address>
- <parent-id></parent-id>
- <last-read type="date">2004-04-15</last-read>
- </topic>
-
- ...and you can configure with:
-
- topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
-
- ...that'll return:
-
- <topic>
- <title>The First Topic</title>
- <author-name>David</author-name>
- <approved type="boolean">false</approved>
- <content>Have a nice day</content>
- <author-email-address>david@loudthinking.com</author-email-address>
- <parent-id></parent-id>
- <last-read type="date">2004-04-15</last-read>
- </topic>
-
- You can even do load first-level associations as part of the document:
-
- firm.to_xml :include => [ :account, :clients ]
-
- ...that'll return something like:
-
- <?xml version="1.0" encoding="UTF-8"?>
- <firm>
- <id type="integer">1</id>
- <rating type="integer">1</rating>
- <name>37signals</name>
- <clients>
- <client>
- <rating type="integer">1</rating>
- <name>Summit</name>
- </client>
- <client>
- <rating type="integer">1</rating>
- <name>Microsoft</name>
- </client>
- </clients>
- <account>
- <id type="integer">1</id>
- <credit-limit type="integer">50</credit-limit>
- </account>
- </firm>
-
-* Allow :counter_cache to take a column name for custom counter cache columns [Jamis Buck]
-
-* Documentation fixes for :dependent [robby@planetargon.com]
-
-* Stop the MySQL adapter crashing when views are present. #3782 [Jonathan Viney]
-
-* Don't classify the belongs_to class, it is already singular #4117 [keithm@infused.org]
-
-* Allow set_fixture_class to take Classes instead of strings for a class in a module. Raise FixtureClassNotFound if a fixture can't load. [Rick Olson]
-
-* Fix quoting of inheritance column for STI eager loading #4098 [Jonathan Viney <jonathan@bluewire.net.nz>]
-
-* Added smarter table aliasing for eager associations for multiple self joins #3580 [Rick Olson]
-
- * The first time a table is referenced in a join, no alias is used.
- * After that, the parent class name and the reflection name are used.
-
- Tree.find(:all, :include => :children) # LEFT OUTER JOIN trees AS tree_children ...
-
- * Any additional join references get a numerical suffix like '_2', '_3', etc.
-
-* Fixed eager loading problems with single-table inheritance #3580 [Rick Olson]. Post.find(:all, :include => :special_comments) now returns all posts, and any special comments that the posts may have. And made STI work with has_many :through and polymorphic belongs_to.
-
-* Added cascading eager loading that allows for queries like Author.find(:all, :include=> { :posts=> :comments }), which will fetch all authors, their posts, and the comments belonging to those posts in a single query (using LEFT OUTER JOIN) #3913 [anna@wota.jp]. Examples:
-
- # cascaded in two levels
- >> Author.find(:all, :include=>{:posts=>:comments})
- => authors
- +- posts
- +- comments
-
- # cascaded in two levels and normal association
- >> Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
- => authors
- +- posts
- +- comments
- +- categorizations
-
- # cascaded in two levels with two has_many associations
- >> Author.find(:all, :include=>{:posts=>[:comments, :categorizations]})
- => authors
- +- posts
- +- comments
- +- categorizations
-
- # cascaded in three levels
- >> Company.find(:all, :include=>{:groups=>{:members=>{:favorites}}})
- => companies
- +- groups
- +- members
- +- favorites
-
-* Make counter cache work when replacing an association #3245 [eugenol@gmail.com]
-
-* Make migrations verbose [Jamis Buck]
-
-* Make counter_cache work with polymorphic belongs_to [Jamis Buck]
-
-* Fixed that calling HasOneProxy#build_model repeatedly would cause saving to happen #4058 [anna@wota.jp]
-
-* Added Sybase database adapter that relies on the Sybase Open Client bindings (see http://raa.ruby-lang.org/project/sybase-ctlib) #3765 [John Sheets]. It's almost completely Active Record compliant (including migrations), but has the following caveats:
-
- * Does not support DATE SQL column types; use DATETIME instead.
- * Date columns on HABTM join tables are returned as String, not Time.
- * Insertions are potentially broken for :polymorphic join tables
- * BLOB column access not yet fully supported
-
-* Clear stale, cached connections left behind by defunct threads. [Jeremy Kemper]
-
-* CHANGED DEFAULT: set ActiveRecord::Base.allow_concurrency to false. Most AR usage is in single-threaded applications. [Jeremy Kemper]
-
-* Renamed the "oci" adapter to "oracle", but kept the old name as an alias #4017 [Michael Schoen]
-
-* Fixed that Base.save should always return false if the save didn't succeed, including if it has halted by before_save's #1861, #2477 [David Heinemeier Hansson]
-
-* Speed up class -> connection caching and stale connection verification. #3979 [Stefan Kaes]
-
-* Add set_fixture_class to allow the use of table name accessors with models which use set_table_name. [Kevin Clark]
-
-* Added that fixtures to placed in subdirectories of the main fixture files are also loaded #3937 [dblack@wobblini.net]
-
-* Define attribute query methods to avoid method_missing calls. #3677 [Jonathan Viney]
-
-* ActiveRecord::Base.remove_connection explicitly closes database connections and doesn't corrupt the connection cache. Introducing the disconnect! instance method for the PostgreSQL, MySQL, and SQL Server adapters; implementations for the others are welcome. #3591 [Simon Stapleton, Tom Ward]
-
-* Added support for nested scopes #3407 [anna@wota.jp]. Examples:
-
- Developer.with_scope(:find => { :conditions => "salary > 10000", :limit => 10 }) do
- Developer.find(:all) # => SELECT * FROM developers WHERE (salary > 10000) LIMIT 10
-
- # inner rule is used. (all previous parameters are ignored)
- Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
- Developer.find(:all) # => SELECT * FROM developers WHERE (name = 'Jamis')
- end
-
- # parameters are merged
- Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
- Developer.find(:all) # => SELECT * FROM developers WHERE (( salary > 10000 ) AND ( name = 'Jamis' )) LIMIT 10
- end
- end
-
-* Fixed db2 connection with empty user_name and auth options #3622 [phurley@gmail.com]
-
-* Fixed validates_length_of to work on UTF-8 strings by using characters instead of bytes #3699 [Masao Mutoh]
-
-* Fixed that reflections would bleed across class boundaries in single-table inheritance setups #3796 [Lars Pind]
-
-* Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Base.maxmium, and the generic Base.calculate. All can be used with :group and :having. Calculations and statitics need no longer require custom SQL. #3958 [Rick Olson]. Examples:
-
- Person.average :age
- Person.minimum :age
- Person.maximum :age
- Person.sum :salary, :group => :last_name
-
-* Renamed Errors#count to Errors#size but kept an alias for the old name (and included an alias for length too) #3920 [Luke Redpath]
-
-* Reflections don't attempt to resolve module nesting of association classes. Simplify type computation. [Jeremy Kemper]
-
-* Improved the Oracle OCI Adapter with better performance for column reflection (from #3210), fixes to migrations (from #3476 and #3742), tweaks to unit tests (from #3610), and improved documentation (from #2446) #3879 [Aggregated by schoenm@earthlink.net]
-
-* Fixed that the schema_info table used by ActiveRecord::Schema.define should respect table pre- and suffixes #3834 [rubyonrails@atyp.de]
-
-* Added :select option to Base.count that'll allow you to select something else than * to be counted on. Especially important for count queries using DISTINCT #3839 [Stefan Kaes]
-
-* Correct syntax error in mysql DDL, and make AAACreateTablesTest run first [Bob Silva]
-
-* Allow :include to be used with has_many :through associations #3611 [Michael Schoen]
-
-* PostgreSQL: smarter schema dumps using pk_and_sequence_for(table). #2920 [Blair Zajac]
-
-* SQLServer: more compatible limit/offset emulation. #3779 [Tom Ward]
-
-* Polymorphic join support for has_one associations (has_one :foo, :as => :bar) #3785 [Rick Olson]
-
-* PostgreSQL: correctly parse negative integer column defaults. #3776 [bellis@deepthought.org]
-
-* Fix problems with count when used with :include [Jeremy Hopple and Kevin Clark]
-
-* ActiveRecord::RecordInvalid now states which validations failed in its default error message [Tobias Lütke]
-
-* Using AssociationCollection#build with arrays of hashes should call build, not create [David Heinemeier Hansson]
-
-* Remove definition of reloadable? from ActiveRecord::Base to make way for new Reloadable code. [Nicholas Seckar]
-
-* Fixed schema handling for DB2 adapter that didn't work: an initial schema could be set, but it wasn't used when getting tables and indexes #3678 [Maik Schmidt]
-
-* Support the :column option for remove_index with the PostgreSQL adapter. #3661 [Shugo Maeda]
-
-* Add documentation for add_index and remove_index. #3600 [Manfred Stienstra <m.stienstra@fngtps.com>]
-
-* If the OCI library is not available, raise an exception indicating as much. #3593 [Michael Schoen]
-
-* Add explicit :order in finder tests as postgresql orders results differently by default. #3577. [Rick Olson]
-
-* Make dynamic finders honor additional passed in :conditions. #3569 [Oleg Pudeyev <pudeyo@rpi.edu>, Marcel Molina Jr.]
-
-* Show a meaningful error when the DB2 adapter cannot be loaded due to missing dependencies. [Nicholas Seckar]
-
-* Make .count work for has_many associations with multi line finder sql [Michael Schoen]
-
-* Add AR::Base.base_class for querying the ancestor AR::Base subclass [Jamis Buck]
-
-* Allow configuration of the column used for optimistic locking [wilsonb@gmail.com]
-
-* Don't hardcode 'id' in acts as list. [ror@philippeapril.com]
-
-* Fix date errors for SQLServer in association tests. #3406 [Kevin Clark]
-
-* Escape database name in MySQL adapter when creating and dropping databases. #3409 [anna@wota.jp]
-
-* Disambiguate table names for columns in validates_uniqueness_of's WHERE clause. #3423 [alex.borovsky@gmail.com]
-
-* .with_scope imposed create parameters now bypass attr_protected [Tobias Lütke]
-
-* Don't raise an exception when there are more keys than there are named bind variables when sanitizing conditions. [Marcel Molina Jr.]
-
-* Multiple enhancements and adjustments to DB2 adaptor. #3377 [contact@maik-schmidt.de]
-
-* Sanitize scoped conditions. [Marcel Molina Jr.]
-
-* Added option to Base.reflection_of_all_associations to specify a specific association to scope the call. For example Base.reflection_of_all_associations(:has_many) [David Heinemeier Hansson]
-
-* Added ActiveRecord::SchemaDumper.ignore_tables which tells SchemaDumper which tables to ignore. Useful for tables with funky column like the ones required for tsearch2. [Tobias Lütke]
-
-* SchemaDumper now doesn't fail anymore when there are unknown column types in the schema. Instead the table is ignored and a Comment is left in the schema.rb. [Tobias Lütke]
-
-* Fixed that saving a model with multiple habtm associations would only save the first one. #3244 [yanowitz-rubyonrails@quantumfoam.org, Florian Weber]
-
-* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 [wejn@box.cz, Rick Olson, Scott Barron]
-
-* removed :piggyback in favor of just allowing :select on :through associations. [Tobias Lütke]
-
-* made method missing delegation to class methods on relation target work on :through associations. [Tobias Lütke]
-
-* made .find() work on :through relations. [Tobias Lütke]
-
-* Fix typo in association docs. #3296. [Blair Zajac]
-
-* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model [Tobias Lütke]
-
-*1.13.2* (December 13th, 2005)
-
-* Become part of Rails 1.0
-
-* MySQL: allow encoding option for mysql.rb driver. [Jeremy Kemper]
-
-* Added option inheritance for find calls on has_and_belongs_to_many and has_many assosociations [David Heinemeier Hansson]. Example:
-
- class Post
- has_many :recent_comments, :class_name => "Comment", :limit => 10, :include => :author
- end
-
- post.recent_comments.find(:all) # Uses LIMIT 10 and includes authors
- post.recent_comments.find(:all, :limit => nil) # Uses no limit but include authors
- post.recent_comments.find(:all, :limit => nil, :include => nil) # Uses no limit and doesn't include authors
-
-* Added option to specify :group, :limit, :offset, and :select options from find on has_and_belongs_to_many and has_many assosociations [David Heinemeier Hansson]
-
-* MySQL: fixes for the bundled mysql.rb driver. #3160 [Justin Forder]
-
-* SQLServer: fix obscure optimistic locking bug. #3068 [kajism@yahoo.com]
-
-* SQLServer: support uniqueidentifier columns. #2930 [keithm@infused.org]
-
-* SQLServer: cope with tables names qualified by owner. #3067 [jeff@ministrycentered.com]
-
-* SQLServer: cope with columns with "desc" in the name. #1950 [Ron Lusk, Ryan Tomayko]
-
-* SQLServer: cope with primary keys with "select" in the name. #3057 [rdifrango@captechventures.com]
-
-* Oracle: active? performs a select instead of a commit. #3133 [Michael Schoen]
-
-* MySQL: more robust test for nullified result hashes. #3124 [Stefan Kaes]
-
-* Reloading an instance refreshes its aggregations as well as its associations. #3024 [François Beausoleil]
-
-* Fixed that using :include together with :conditions array in Base.find would cause NoMethodError #2887 [Paul Hammmond]
-
-* PostgreSQL: more robust sequence name discovery. #3087 [Rick Olson]
-
-* Oracle: use syntax compatible with Oracle 8. #3131 [Michael Schoen]
-
-* MySQL: work around ruby-mysql/mysql-ruby inconsistency with mysql.stat. Eliminate usage of mysql.ping because it doesn't guarantee reconnect. Explicitly close and reopen the connection instead. [Jeremy Kemper]
-
-* Added preliminary support for polymorphic associations [David Heinemeier Hansson]
-
-* Added preliminary support for join models [David Heinemeier Hansson]
-
-* Allow validate_uniqueness_of to be scoped by more than just one column. #1559. [jeremy@jthopple.com, Marcel Molina Jr.]
-
-* Firebird: active? and reconnect! methods for handling stale connections. #428 [Ken Kunz <kennethkunz@gmail.com>]
-
-* Firebird: updated for FireRuby 0.4.0. #3009 [Ken Kunz <kennethkunz@gmail.com>]
-
-* MySQL and PostgreSQL: active? compatibility with the pure-Ruby driver. #428 [Jeremy Kemper]
-
-* Oracle: active? check pings the database rather than testing the last command status. #428 [Michael Schoen]
-
-* SQLServer: resolve column aliasing/quoting collision when using limit or offset in an eager find. #2974 [kajism@yahoo.com]
-
-* Reloading a model doesn't lose track of its connection. #2996 [junk@miriamtech.com, Jeremy Kemper]
-
-* Fixed bug where using update_attribute after pushing a record to a habtm association of the object caused duplicate rows in the join table. #2888 [colman@rominato.com, Florian Weber, Michael Schoen]
-
-* MySQL, PostgreSQL: reconnect! also reconfigures the connection. Otherwise, the connection 'loses' its settings if it times out and is reconnected. #2978 [Shugo Maeda]
-
-* has_and_belongs_to_many: use JOIN instead of LEFT JOIN. [Jeremy Kemper]
-
-* MySQL: introduce :encoding option to specify the character set for client, connection, and results. Only available for MySQL 4.1 and later with the mysql-ruby driver. Do SHOW CHARACTER SET in mysql client to see available encodings. #2975 [Shugo Maeda]
-
-* Add tasks to create, drop and rebuild the MySQL and PostgreSQL test databases. [Marcel Molina Jr.]
-
-* Correct boolean handling in generated reader methods. #2945 [Don Park, Stefan Kaes]
-
-* Don't generate read methods for columns whose names are not valid ruby method names. #2946 [Stefan Kaes]
-
-* Document :force option to create_table. #2921 [Blair Zajac <blair@orcaware.com>]
-
-* Don't add the same conditions twice in has_one finder sql. #2916 [Jeremy Evans]
-
-* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
-
-* Introducing the Firebird adapter. Quote columns and use attribute_condition more consistently. Setup guide: http://wiki.rubyonrails.com/rails/pages/Firebird+Adapter #1874 [Ken Kunz <kennethkunz@gmail.com>]
-
-* SQLServer: active? and reconnect! methods for handling stale connections. #428 [kajism@yahoo.com, Tom Ward <tom@popdog.net>]
-
-* Associations handle case-equality more consistently: item.parts.is_a?(Array) and item.parts === Array. #1345 [MarkusQ@reality.com]
-
-* SQLServer: insert uses given primary key value if not nil rather than SELECT @@IDENTITY. #2866 [kajism@yahoo.com, Tom Ward <tom@popdog.net>]
-
-* Oracle: active? and reconnect! methods for handling stale connections. Optionally retry queries after reconnect. #428 [Michael Schoen <schoenm@earthlink.net>]
-
-* Correct documentation for Base.delete_all. #1568 [Newhydra]
-
-* Oracle: test case for column default parsing. #2788 [Michael Schoen <schoenm@earthlink.net>]
-
-* Update documentation for Migrations. #2861 [Tom Werner <tom@cube6media.com>]
-
-* When AbstractAdapter#log rescues an exception, attempt to detect and reconnect to an inactive database connection. Connection adapter must respond to the active? and reconnect! instance methods. Initial support for PostgreSQL, MySQL, and SQLite. Make certain that all statements which may need reconnection are performed within a logged block: for example, this means no avoiding log(sql, name) { } if @logger.nil? #428 [Jeremy Kemper]
-
-* Oracle: Much faster column reflection. #2848 [Michael Schoen <schoenm@earthlink.net>]
-
-* Base.reset_sequence_name analogous to reset_table_name (mostly useful for testing). Base.define_attr_method allows nil values. [Jeremy Kemper]
-
-* PostgreSQL: smarter sequence name defaults, stricter last_insert_id, warn on pk without sequence. [Jeremy Kemper]
-
-* PostgreSQL: correctly discover custom primary key sequences. #2594 [Blair Zajac <blair@orcaware.com>, meadow.nnick@gmail.com, Jeremy Kemper]
-
-* SQLServer: don't report limits for unsupported field types. #2835 [Ryan Tomayko]
-
-* Include the Enumerable module in ActiveRecord::Errors. [Rick Bradley <rick@rickbradley.com>]
-
-* Add :group option, correspond to GROUP BY, to the find method and to the has_many association. #2818 [rubyonrails@atyp.de]
-
-* Don't cast nil or empty strings to a dummy date. #2789 [Rick Bradley <rick@rickbradley.com>]
-
-* acts_as_list plays nicely with inheritance by remembering the class which declared it. #2811 [rephorm@rephorm.com]
-
-* Fix sqlite adaptor's detection of missing dbfile or database declaration. [Nicholas Seckar]
-
-* Fixed acts_as_list for definitions without an explicit :order #2803 [Jonathan Viney]
-
-* Upgrade bundled ruby-mysql 0.2.4 with mysql411 shim (see #440) to ruby-mysql 0.2.6 with a patchset for 4.1 protocol support. Local change [301] is now a part of the main driver; reapplied local change [2182]. Removed GC.start from Result.free. [tommy@tmtm.org, akuroda@gmail.com, Doug Fales <doug.fales@gmail.com>, Jeremy Kemper]
-
-* Correct handling of complex order clauses with SQL Server limit emulation. #2770 [Tom Ward <tom@popdog.net>, Matt B.]
-
-* Correct whitespace problem in Oracle default column value parsing. #2788 [rick@rickbradley.com]
-
-* Destroy associated has_and_belongs_to_many records after all before_destroy callbacks but before destroy. This allows you to act on the habtm association as you please while preserving referential integrity. #2065 [larrywilliams1@gmail.com, sam.kirchmeier@gmail.com, elliot@townx.org, Jeremy Kemper]
-
-* Deprecate the old, confusing :exclusively_dependent option in favor of :dependent => :delete_all. [Jeremy Kemper]
-
-* More compatible Oracle column reflection. #2771 [Ryan Davis <ryand-ruby@zenspider.com>, Michael Schoen <schoenm@earthlink.net>]
-
-
-*1.13.0* (November 7th, 2005)
-
-* Fixed faulty regex in get_table_name method (SQLServerAdapter) #2639 [Ryan Tomayko]
-
-* Added :include as an option for association declarations [David Heinemeier Hansson]. Example:
-
- has_many :posts, :include => [ :author, :comments ]
-
-* Rename Base.constrain to Base.with_scope so it doesn't conflict with existing concept of database constraints. Make scoping more robust: uniform method => parameters, validated method names and supported finder parameters, raise exception on nested scopes. [Jeremy Kemper] Example:
-
- Comment.with_scope(:find => { :conditions => 'active=true' }, :create => { :post_id => 5 }) do
- # Find where name = ? and active=true
- Comment.find :all, :conditions => ['name = ?', name]
- # Create comment associated with :post_id
- Comment.create :body => "Hello world"
- end
-
-* Fixed that SQL Server should ignore :size declarations on anything but integer and string in the agnostic schema representation #2756 [Ryan Tomayko]
-
-* Added constrain scoping for creates using a hash of attributes bound to the :creation key [David Heinemeier Hansson]. Example:
-
- Comment.constrain(:creation => { :post_id => 5 }) do
- # Associated with :post_id
- Comment.create :body => "Hello world"
- end
-
- This is rarely used directly, but allows for find_or_create on associations. So you can do:
-
- # If the tag doesn't exist, a new one is created that's associated with the person
- person.tags.find_or_create_by_name("Summer")
-
-* Added find_or_create_by_X as a second type of dynamic finder that'll create the record if it doesn't already exist [David Heinemeier Hansson]. Example:
-
- # No 'Summer' tag exists
- Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
-
- # Now the 'Summer' tag does exist
- Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
-
-* Added extension capabilities to has_many and has_and_belongs_to_many proxies [David Heinemeier Hansson]. Example:
-
- class Account < ActiveRecord::Base
- has_many :people do
- def find_or_create_by_name(name)
- first_name, *last_name = name.split
- last_name = last_name.join " "
-
- find_or_create_by_first_name_and_last_name(first_name, last_name)
- end
- end
- end
-
- person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
- person.first_name # => "David"
- person.last_name # => "Heinemeier Hansson"
-
- Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation).
-
-* Omit internal dtproperties table from SQLServer table list. #2729 [Ryan Tomayko]
-
-* Quote column names in generated SQL. #2728 [Ryan Tomayko]
-
-* Correct the pure-Ruby MySQL 4.1.1 shim's version test. #2718 [Jeremy Kemper]
-
-* Add Model.create! to match existing model.save! method. When save! raises RecordInvalid, you can catch the exception, retrieve the invalid record (invalid_exception.record), and see its errors (invalid_exception.record.errors). [Jeremy Kemper]
-
-* Correct fixture behavior when table name pluralization is off. #2719 [Rick Bradley <rick@rickbradley.com>]
-
-* Changed :dbfile to :database for SQLite adapter for consistency (old key still works as an alias) #2644 [Dan Peterson]
-
-* Added migration support for Oracle #2647 [Michael Schoen]
-
-* Worked around that connection can't be reset if allow_concurrency is off. #2648 [Michael Schoen <schoenm@earthlink.net>]
-
-* Fixed SQL Server adapter to pass even more tests and do even better #2634 [Ryan Tomayko]
-
-* Fixed SQL Server adapter so it honors options[:conditions] when applying :limits #1978 [Tom Ward]
-
-* Added migration support to SQL Server adapter (please someone do the same for Oracle and DB2) #2625 [Tom Ward]
-
-* Use AR::Base.silence rather than AR::Base.logger.silence in fixtures to preserve Log4r compatibility. #2618 [dansketcher@gmail.com]
-
-* Constraints are cloned so they can't be inadvertently modified while they're
-in effect. Added :readonly finder constraint. Calling an association collection's class method (Part.foobar via item.parts.foobar) constrains :readonly => false since the collection's :joins constraint would otherwise force it to true. [Jeremy Kemper <rails@bitsweat.net>]
-
-* Added :offset and :limit to the kinds of options that Base.constrain can use #2466 [duane.johnson@gmail.com]
-
-* Fixed handling of nil number columns on Oracle and cleaned up tests for Oracle in general #2555 [Michael Schoen]
-
-* Added quoted_true and quoted_false methods and tables to db2_adapter and cleaned up tests for DB2 #2493, #2624 [maik schmidt]
-
-
-*1.12.2* (October 26th, 2005)
-
-* Allow symbols to rename columns when using SQLite adapter. #2531 [Kevin Clark]
-
-* Map Active Record time to SQL TIME. #2575, #2576 [Robby Russell <robby@planetargon.com>]
-
-* Clarify semantics of ActiveRecord::Base#respond_to? #2560 [Stefan Kaes]
-
-* Fixed Association#clear for associations which have not yet been accessed. #2524 [Patrick Lenz <patrick@lenz.sh>]
-
-* HABTM finders shouldn't return readonly records. #2525 [Patrick Lenz <patrick@lenz.sh>]
-
-* Make all tests runnable on their own. #2521. [Blair Zajac <blair@orcaware.com>]
-
-
-*1.12.1* (October 19th, 2005)
-
-* Always parenthesize :conditions options so they may be safely combined with STI and constraints.
-
-* Correct PostgreSQL primary key sequence detection. #2507 [tmornini@infomania.com]
-
-* Added support for using limits in eager loads that involve has_many and has_and_belongs_to_many associations
-
-
-*1.12.0* (October 16th, 2005)
-
-* Update/clean up documentation (rdoc)
-
-* PostgreSQL sequence support. Use set_sequence_name in your model class to specify its primary key sequence. #2292 [Rick Olson <technoweenie@gmail.com>, Robby Russell <robby@planetargon.com>]
-
-* Change default logging colors to work on both white and black backgrounds. [Sam Stephenson]
-
-* YAML fixtures support ordered hashes for fixtures with foreign key dependencies in the same table. #1896 [purestorm@ggnore.net]
-
-* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 [Robby Russell <robby@planetargon.com>]
-
-* Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around.
-
-* Avoid memleak in dev mode when using fcgi
-
-* Simplified .clear on active record associations by using the existing delete_records method. #1906 [Caleb <me@cpb.ca>]
-
-* Delegate access to a customized primary key to the conventional id method. #2444. [Blair Zajac <blair@orcaware.com>]
-
-* Fix errors caused by assigning a has-one or belongs-to property to itself
-
-* Add ActiveRecord::Base.schema_format setting which specifies how databases should be dumped [Sam Stephenson]
-
-* Update DB2 adapter. #2206. [contact@maik-schmidt.de]
-
-* Corrections to SQLServer native data types. #2267. [rails.20.clarry@spamgourmet.com]
-
-* Deprecated ActiveRecord::Base.threaded_connection in favor of ActiveRecord::Base.allow_concurrency.
-
-* Protect id attribute from mass assigment even when the primary key is set to something else. #2438. [Blair Zajac <blair@orcaware.com>]
-
-* Misc doc fixes (typos/grammar/etc.). #2430. [coffee2code]
-
-* Add test coverage for content_columns. #2432. [coffee2code]
-
-* Speed up for unthreaded environments. #2431. [Stefan Kaes]
-
-* Optimization for Mysql selects using mysql-ruby extension greater than 2.6.3. #2426. [Stefan Kaes]
-
-* Speed up the setting of table_name. #2428. [Stefan Kaes]
-
-* Optimize instantiation of STI subclass records. In partial fullfilment of #1236. [Stefan Kaes]
-
-* Fix typo of 'constrains' to 'contraints'. #2069. [Michael Schuerig <michael@schuerig.de>]
-
-* Optimization refactoring for add_limit_offset!. In partial fullfilment of #1236. [Stefan Kaes]
-
-* Add ability to get all siblings, including the current child, with acts_as_tree. Recloses #2140. [Michael Schuerig <michael@schuerig.de>]
-
-* Add geometric type for postgresql adapter. #2233 [Andrew Kaspick]
-
-* Add option (true by default) to generate reader methods for each attribute of a record to avoid the overhead of calling method missing. In partial fullfilment of #1236. [Stefan Kaes]
-
-* Add convenience predicate methods on Column class. In partial fullfilment of #1236. [Stefan Kaes]
-
-* Raise errors when invalid hash keys are passed to ActiveRecord::Base.find. #2363 [Chad Fowler <chad@chadfowler.com>, Nicholas Seckar]
-
-* Added :force option to create_table that'll try to drop the table if it already exists before creating
-
-* Fix transactions so that calling return while inside a transaction will not leave an open transaction on the connection. [Nicholas Seckar]
-
-* Use foreign_key inflection uniformly. #2156 [Blair Zajac <blair@orcaware.com>]
-
-* model.association.clear should destroy associated objects if :dependent => true instead of nullifying their foreign keys. #2221 [joergd@pobox.com, ObieFernandez <obiefernandez@gmail.com>]
-
-* Returning false from before_destroy should cancel the action. #1829 [Jeremy Huffman]
-
-* Recognize PostgreSQL NOW() default as equivalent to CURRENT_TIMESTAMP or CURRENT_DATE, depending on the column's type. #2256 [mat <mat@absolight.fr>]
-
-* Extensive documentation for the abstract database adapter. #2250 [François Beausoleil <fbeausoleil@ftml.net>]
-
-* Clean up Fixtures.reset_sequences for PostgreSQL. Handle tables with no rows and models with custom primary keys. #2174, #2183 [jay@jay.fm, Blair Zajac <blair@orcaware.com>]
-
-* Improve error message when nil is assigned to an attr which validates_size_of within a range. #2022 [Manuel Holtgrewe <purestorm@ggnore.net>]
-
-* Make update_attribute use the same writer method that update_attributes uses.
- #2237 [trevor@protocool.com]
-
-* Make migrations honor table name prefixes and suffixes. #2298 [Jakob Skjerning, Marcel Molina Jr.]
-
-* Correct and optimize PostgreSQL bytea escaping. #1745, #1837 [dave@cherryville.org, ken@miriamtech.com, bellis@deepthought.org]
-
-* Fixtures should only reset a PostgreSQL sequence if it corresponds to an integer primary key named id. #1749 [chris@chrisbrinker.com]
-
-* Standardize the interpretation of boolean columns in the Mysql and Sqlite adapters. (Use MysqlAdapter.emulate_booleans = false to disable this behavior)
-
-* Added new symbol-driven approach to activating observers with Base#observers= [David Heinemeier Hansson]. Example:
-
- ActiveRecord::Base.observers = :cacher, :garbage_collector
-
-* Added AbstractAdapter#select_value and AbstractAdapter#select_values as convenience methods for selecting single values, instead of hashes, of the first column in a SELECT #2283 [solo@gatelys.com]
-
-* Wrap :conditions in parentheses to prevent problems with OR's #1871 [Jamis Buck]
-
-* Allow the postgresql adapter to work with the SchemaDumper. [Jamis Buck]
-
-* Add ActiveRecord::SchemaDumper for dumping a DB schema to a pure-ruby file, making it easier to consolidate large migration lists and port database schemas between databases. [Jamis Buck]
-
-* Fixed migrations for Windows when using more than 10 [David Naseby]
-
-* Fixed that the create_x method from belongs_to wouldn't save the association properly #2042 [Florian Weber]
-
-* Fixed saving a record with two unsaved belongs_to associations pointing to the same object #2023 [Tobias Lütke]
-
-* Improved migrations' behavior when the schema_info table is empty. [Nicholas Seckar]
-
-* Fixed that Observers didn't observe sub-classes #627 [Florian Weber]
-
-* Fix eager loading error messages, allow :include to specify tables using strings or symbols. Closes #2222 [Marcel Molina Jr.]
-
-* Added check for RAILS_CONNECTION_ADAPTERS on startup and only load the connection adapters specified within if its present (available in Rails through config.connection_adapters using the new config) #1958 [skae]
-
-* Fixed various problems with has_and_belongs_to_many when using customer finder_sql #2094 [Florian Weber]
-
-* Added better exception error when unknown column types are used with migrations #1814 [François Beausoleil]
-
-* Fixed "connection lost" issue with the bundled Ruby/MySQL driver (would kill the app after 8 hours of inactivity) #2163, #428 [kajism@yahoo.com]
-
-* Fixed comparison of Active Record objects so two new objects are not equal #2099 [deberg]
-
-* Fixed that the SQL Server adapter would sometimes return DBI::Timestamp objects instead of Time #2127 [Tom Ward]
-
-* Added the instance methods #root and #ancestors on acts_as_tree and fixed siblings to not include the current node #2142, #2140 [coffee2code]
-
-* Fixed that Active Record would call SHOW FIELDS twice (or more) for the same model when the cached results were available #1947 [sd@notso.net]
-
-* Added log_level and use_silence parameter to ActiveRecord::Base.benchmark. The first controls at what level the benchmark statement will be logged (now as debug, instead of info) and the second that can be passed false to include all logging statements during the benchmark block/
-
-* Make sure the schema_info table is created before querying the current version #1903
-
-* Fixtures ignore table name prefix and suffix #1987 [Jakob Skjerning]
-
-* Add documentation for index_type argument to add_index method for migrations #2005 [Blaine]
-
-* Modify read_attribute to allow a symbol argument #2024 [Ken Kunz]
-
-* Make destroy return self #1913 [Sebastian Kanthak]
-
-* Fix typo in validations documentation #1938 [court3nay]
-
-* Make acts_as_list work for insert_at(1) #1966 [hensleyl@papermountain.org]
-
-* Fix typo in count_by_sql documentation #1969 [Alexey Verkhovsky]
-
-* Allow add_column and create_table to specify NOT NULL #1712 [emptysands@gmail.com]
-
-* Fix create_table so that id column is implicitly added [Rick Olson]
-
-* Default sequence names for Oracle changed to #{table_name}_seq, which is the most commonly used standard. In addition, a new method ActiveRecord::Base#set_sequence_name allows the developer to set the sequence name per model. This is a non-backwards-compatible change -- anyone using the old-style "rails_sequence" will need to either create new sequences, or set: ActiveRecord::Base.set_sequence_name = "rails_sequence" #1798
-
-* OCIAdapter now properly handles synonyms, which are commonly used to separate out the schema owner from the application user #1798
-
-* Fixed the handling of camelCase columns names in Oracle #1798
-
-* Implemented for OCI the Rakefile tasks of :clone_structure_to_test, :db_structure_dump, and :purge_test_database, which enable Oracle folks to enjoy all the agile goodness of Rails for testing. Note that the current implementation is fairly limited -- only tables and sequences are cloned, not constraints or indexes. A full clone in Oracle generally requires some manual effort, and is version-specific. Post 9i, Oracle recommends the use of the DBMS_METADATA package, though that approach requires editing of the physical characteristics generated #1798
-
-* Fixed the handling of multiple blob columns in Oracle if one or more of them are null #1798
-
-* Added support for calling constrained class methods on has_many and has_and_belongs_to_many collections #1764 [Tobias Lütke]
-
- class Comment < AR:B
- def self.search(q)
- find(:all, :conditions => ["body = ?", q])
- end
- end
-
- class Post < AR:B
- has_many :comments
- end
-
- Post.find(1).comments.search('hi') # => SELECT * from comments WHERE post_id = 1 AND body = 'hi'
-
- NOTICE: This patch changes the underlying SQL generated by has_and_belongs_to_many queries. If your relying on that, such as
- by explicitly referencing the old t and j aliases, you'll need to update your code. Of course, you _shouldn't_ be relying on
- details like that no less than you should be diving in to touch private variables. But just in case you do, consider yourself
- noticed :)
-
-* Added migration support for SQLite (using temporary tables to simulate ALTER TABLE) #1771 [Sam Stephenson]
-
-* Remove extra definition of supports_migrations? from abstract_adaptor.rb [Nicholas Seckar]
-
-* Fix acts_as_list so that moving next-to-last item to the bottom does not result in duplicate item positions
-
-* Fixed incompatibility in DB2 adapter with the new limit/offset approach #1718 [Maik Schmidt]
-
-* Added :select option to find which can specify a different value than the default *, like find(:all, :select => "first_name, last_name"), if you either only want to select part of the columns or exclude columns otherwise included from a join #1338 [Stefan Kaes]
-
-
-*1.11.1* (11 July, 2005)
-
-* Added support for limit and offset with eager loading of has_one and belongs_to associations. Using the options with has_many and has_and_belongs_to_many associations will now raise an ActiveRecord::ConfigurationError #1692 [Rick Olson]
-
-* Fixed that assume_bottom_position (in acts_as_list) could be called on items already last in the list and they would move one position away from the list #1648 [tyler@kianta.com]
-
-* Added ActiveRecord::Base.threaded_connections flag to turn off 1-connection per thread (required for thread safety). By default it's on, but WEBrick in Rails need it off #1685 [Sam Stephenson]
-
-* Correct reflected table name for singular associations. #1688 [court3nay]
-
-* Fixed optimistic locking with SQL Server #1660 [tom@popdog.net]
-
-* Added ActiveRecord::Migrator.migrate that can figure out whether to go up or down based on the target version and the current
-
-* Added better error message for "packets out of order" #1630 [court3nay]
-
-* Fixed first run of "rake migrate" on PostgreSQL by not expecting a return value on the id #1640
-
-
-*1.11.0* (6 July, 2005)
-
-* Fixed that Yaml error message in fixtures hid the real error #1623 [Nicholas Seckar]
-
-* Changed logging of SQL statements to use the DEBUG level instead of INFO
-
-* Added new Migrations framework for describing schema transformations in a way that can be easily applied across multiple databases #1604 [Tobias Lütke] See documentation under ActiveRecord::Migration and the additional support in the Rails rakefile/generator.
-
-* Added callback hooks to association collections #1549 [Florian Weber]. Example:
-
- class Project
- has_and_belongs_to_many :developers, :before_add => :evaluate_velocity
-
- def evaluate_velocity(developer)
- ...
- end
- end
-
- ..raising an exception will cause the object not to be added (or removed, with before_remove).
-
-
-* Fixed Base.content_columns call for SQL Server adapter #1450 [DeLynn Berry]
-
-* Fixed Base#write_attribute to work with both symbols and strings #1190 [Paul Legato]
-
-* Fixed that has_and_belongs_to_many didn't respect single table inheritance types #1081 [Florian Weber]
-
-* Speed up ActiveRecord#method_missing for the common case (read_attribute).
-
-* Only notify observers on after_find and after_initialize if these methods are defined on the model. #1235 [Stefan Kaes]
-
-* Fixed that single-table inheritance sub-classes couldn't be used to limit the result set with eager loading #1215 [Chris McGrath]
-
-* Fixed validates_numericality_of to work with overrided getter-method when :allow_nil is on #1316 [raidel@onemail.at]
-
-* Added roots, root, and siblings to the batch of methods added by acts_as_tree #1541 [Michael Schuerig]
-
-* Added support for limit/offset with the MS SQL Server driver so that pagination will now work #1569 [DeLynn Berry]
-
-* Added support for ODBC connections to MS SQL Server so you can connect from a non-Windows machine #1569 [Mark Imbriaco/DeLynn Berry]
-
-* Fixed that multiparameter posts ignored attr_protected #1532 [alec+rails@veryclever.net]
-
-* Fixed problem with eager loading when using a has_and_belongs_to_many association using :association_foreign_key #1504 [flash@vanklinkenbergsoftware.nl]
-
-* Fixed Base#find to honor the documentation on how :joins work and make them consistent with Base#count #1405 [pritchie@gmail.com]. What used to be:
-
- Developer.find :all, :joins => 'developers_projects', :conditions => 'id=developer_id AND project_id=1'
-
- ...should instead be:
-
- Developer.find(
- :all,
- :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
- :conditions => 'project_id=1'
- )
-
-* Fixed that validations didn't respecting custom setting for too_short, too_long messages #1437 [Marcel Molina Jr.]
-
-* Fixed that clear_association_cache doesn't delete new associations on new records (so you can safely place new records in the session with Action Pack without having new associations wiped) #1494 [cluon]
-
-* Fixed that calling Model.find([]) returns [] and doesn't throw an exception #1379
-
-* Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 [Alisdair McDiarmid]
-
-* Fixed saving of in-memory association structures to happen as a after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice
-
-* Allow any Enumerable, not just Array, to work as bind variables #1344 [Jeremy Kemper]
-
-* Added actual database-changing behavior to collection assigment for has_many and has_and_belongs_to_many #1425 [Sebastian Kanthak].
- Example:
-
- david.projects = [Project.find(1), Project.new("name" => "ActionWebSearch")]
- david.save
-
- If david.projects already contain the project with ID 1, this is left unchanged. Any other projects are dropped. And the new
- project is saved when david.save is called.
-
- Also included is a way to do assignments through IDs, which is perfect for checkbox updating, so you get to do:
-
- david.project_ids = [1, 5, 7]
-
-* Corrected typo in find SQL for has_and_belongs_to_many. #1312 [ben@bensinclair.com]
-
-* Fixed sanitized conditions for has_many finder method. #1281 [jackc@hylesanderson.com, pragdave, Tobias Lütke]
-
-* Comprehensive PostgreSQL schema support. Use the optional schema_search_path directive in database.yml to give a comma-separated list of schemas to search for your tables. This allows you, for example, to have tables in a shared schema without having to use a custom table name. See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html to learn more. #827 [dave@cherryville.org]
-
-* Corrected @@configurations typo #1410 [david@ruppconsulting.com]
-
-* Return PostgreSQL columns in the order they were declared #1374 [perlguy@gmail.com]
-
-* Allow before/after update hooks to work on models using optimistic locking
-
-* Eager loading of dependent has_one associations won't delete the association #1212
-
-* Added a second parameter to the build and create method for has_one that controls whether the existing association should be replaced (which means nullifying its foreign key as well). By default this is true, but false can be passed to prevent it.
-
-* Using transactional fixtures now causes the data to be loaded only once.
-
-* Added fixture accessor methods that can be used when instantiated fixtures are disabled.
-
- fixtures :web_sites
-
- def test_something
- assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
- end
-
-* Added DoubleRenderError exception that'll be raised if render* is called twice #518 [Nicholas Seckar]
-
-* Fixed exceptions occuring after render has been called #1096 [Nicholas Seckar]
-
-* CHANGED: validates_presence_of now uses Errors#add_on_blank, which will make " " fail the validation where it didn't before #1309
-
-* Added Errors#add_on_blank which works like Errors#add_on_empty, but uses Object#blank? instead
-
-* Added the :if option to all validations that can either use a block or a method pointer to determine whether the validation should be run or not. #1324 [Duane Johnson/jhosteny]. Examples:
-
- Conditional validations such as the following are made possible:
- validates_numericality_of :income, :if => :employed?
-
- Conditional validations can also solve the salted login generator problem:
- validates_confirmation_of :password, :if => :new_password?
-
- Using blocks:
- validates_presence_of :username, :if => Proc.new { |user| user.signup_step > 1 }
-
-* Fixed use of construct_finder_sql when using :join #1288 [dwlt@dwlt.net]
-
-* Fixed that :delete_sql in has_and_belongs_to_many associations couldn't access record properties #1299 [Rick Olson]
-
-* Fixed that clone would break when an aggregate had the same name as one of its attributes #1307 [Jeremy Kemper]
-
-* Changed that destroying an object will only freeze the attributes hash, which keeps the object from having attributes changed (as that wouldn't make sense), but allows for the querying of associations after it has been destroyed.
-
-* Changed the callbacks such that observers are notified before the in-object callbacks are triggered. Without this change, it wasn't possible to act on the whole object in something like a before_destroy observer without having the objects own callbacks (like deleting associations) called first.
-
-* Added option for passing an array to the find_all version of the dynamic finders and have it evaluated as an IN fragment. Example:
-
- # SELECT * FROM topics WHERE title IN ('First', 'Second')
- Topic.find_all_by_title(["First", "Second"])
-
-* Added compatibility with camelCase column names for dynamic finders #533 [Dee Zsombor]
-
-* Fixed extraneous comma in count() function that made it not work with joins #1156 [Jarkko Laine/Dee Zsombor]
-
-* Fixed incompatibility with Base#find with an array of ids that would fail when using eager loading #1186 [Alisdair McDiarmid]
-
-* Fixed that validate_length_of lost :on option when :within was specified #1195 [jhosteny@mac.com]
-
-* Added encoding and min_messages options for PostgreSQL #1205 [Shugo Maeda]. Configuration example:
-
- development:
- adapter: postgresql
- database: rails_development
- host: localhost
- username: postgres
- password:
- encoding: UTF8
- min_messages: ERROR
-
-* Fixed acts_as_list where deleting an item that was removed from the list would ruin the positioning of other list items #1197 [Jamis Buck]
-
-* Added validates_exclusion_of as a negative of validates_inclusion_of
-
-* Optimized counting of has_many associations by setting the association to empty if the count is 0 so repeated calls doesn't trigger database calls
-
-
-*1.10.1* (20th April, 2005)
-
-* Fixed frivilous database queries being triggered with eager loading on empty associations and other things
-
-* Fixed order of loading in eager associations
-
-* Fixed stray comma when using eager loading and ordering together from has_many associations #1143
-
-
-*1.10.0* (19th April, 2005)
-
-* Added eager loading of associations as a way to solve the N+1 problem more gracefully without piggy-back queries. Example:
-
- for post in Post.find(:all, :limit => 100)
- puts "Post: " + post.title
- puts "Written by: " + post.author.name
- puts "Last comment on: " + post.comments.first.created_on
- end
-
- This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as:
-
- for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
-
- ...and the number of database queries needed is now 1.
-
-* Added new unified Base.find API and deprecated the use of find_first and find_all. See the documentation for Base.find. Examples:
-
- Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
- Person.find(1, 5, 6, :conditions => "administrator = 1", :order => "created_on DESC")
- Person.find(:first, :order => "created_on DESC", :offset => 5)
- Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
- Person.find(:all, :offset => 10, :limit => 10)
-
-* Added acts_as_nested_set #1000 [wschenk]. Introduction:
-
- This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with
- the added feature that you can select the children and all of it's descendants with
- a single query. A good use case for this is a threaded post system, where you want
- to display every reply to a comment without multiple selects.
-
-* Added Base.save! that attempts to save the record just like Base.save but will raise a RecordInvalid exception instead of returning false if the record is not valid [Dave Thomas]
-
-* Fixed PostgreSQL usage of fixtures with regards to public schemas and table names with dots #962 [gnuman1@gmail.com]
-
-* Fixed that fixtures were being deleted in the same order as inserts causing FK errors #890 [andrew.john.peters@gmail.com]
-
-* Fixed loading of fixtures in to be in the right order (or PostgreSQL would bark) #1047 [stephenh@chase3000.com]
-
-* Fixed page caching for non-vhost applications living underneath the root #1004 [Ben Schumacher]
-
-* Fixes a problem with the SQL Adapter which was resulting in IDENTITY_INSERT not being set to ON when it should be #1104 [adelle]
-
-* Added the option to specify the acceptance string in validates_acceptance_of #1106 [caleb@aei-tech.com]
-
-* Added insert_at(position) to acts_as_list #1083 [DeLynnB]
-
-* Removed the default order by id on has_and_belongs_to_many queries as it could kill performance on large sets (you can still specify by hand with :order)
-
-* Fixed that Base.silence should restore the old logger level when done, not just set it to DEBUG #1084 [yon@milliped.com]
-
-* Fixed boolean saving on Oracle #1093 [mparrish@pearware.org]
-
-* Moved build_association and create_association for has_one and belongs_to out of deprecation as they work when the association is nil unlike association.build and association.create, which require the association to be already in place #864
-
-* Added rollbacks of transactions if they're active as the dispatcher is killed gracefully (TERM signal) #1054 [Leon Bredt]
-
-* Added quoting of column names for fixtures #997 [jcfischer@gmail.com]
-
-* Fixed counter_sql when no records exist in database for PostgreSQL (would give error, not 0) #1039 [Caleb Tennis]
-
-* Fixed that benchmarking times for rendering included db runtimes #987 [Stefan Kaes]
-
-* Fixed boolean queries for t/f fields in PostgreSQL #995 [dave@cherryville.org]
-
-* Added that model.items.delete(child) will delete the child, not just set the foreign key to nil, if the child is dependent on the model #978 [Jeremy Kemper]
-
-* Fixed auto-stamping of dates (created_on/updated_on) for PostgreSQL #985 [dave@cherryville.org]
-
-* Fixed Base.silence/benchmark to only log if a logger has been configured #986 [Stefan Kaes]
-
-* Added a join parameter as the third argument to Base.find_first and as the second to Base.count #426, #988 [Stefan Kaes]
-
-* Fixed bug in Base#hash method that would treat records with the same string-based id as different [Dave Thomas]
-
-* Renamed DateHelper#distance_of_time_in_words_to_now to DateHelper#time_ago_in_words (old method name is still available as a deprecated alias)
-
-
-*1.9.1* (27th March, 2005)
-
-* Fixed that Active Record objects with float attribute could not be cloned #808
-
-* Fixed that MissingSourceFile's wasn't properly detected in production mode #925 [Nicholas Seckar]
-
-* Fixed that :counter_cache option would look for a line_items_count column for a LineItem object instead of lineitems_count
-
-* Fixed that AR exists?() would explode on postgresql if the passed id did not match the PK type #900 [Scott Barron]
-
-* Fixed the MS SQL adapter to work with the new limit/offset approach and with binary data (still suffering from 7KB limit, though) #901 [delynnb]
-
-
-*1.9.0* (22th March, 2005)
-
-* Added adapter independent limit clause as a two-element array with the first being the limit, the second being the offset #795 [Sam Stephenson]. Example:
-
- Developer.find_all nil, 'id ASC', 5 # return the first five developers
- Developer.find_all nil, 'id ASC', [3, 8] # return three developers, starting from #8 and forward
-
- This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
-
-* Added alias_method :to_param, :id to Base, such that Active Record objects to be used as URL parameters in Action Pack automatically #812 [Nicholas Seckar/Sam Stephenson]
-
-* Improved the performance of the OCI8 adapter for Oracle #723 [pilx/gjenkins]
-
-* Added type conversion before saving a record, so string-based values like "10.0" aren't left for the database to convert #820 [dave@cherryville.org]
-
-* Added with additional settings for working with transactional fixtures and pre-loaded test databases #865 [mindel]
-
-* Fixed acts_as_list to trigger remove_from_list on destroy after the fact, not before, so a unique position can be maintained #871 [Alisdair McDiarmid]
-
-* Added the possibility of specifying fixtures in multiple calls #816 [kim@tinker.com]
-
-* Added Base.exists?(id) that'll return true if an object of the class with the given id exists #854 [stian@grytoyr.net]
-
-* Added optionally allow for nil or empty strings with validates_numericality_of #801 [Sebastian Kanthak]
-
-* Fixed problem with using slashes in validates_format_of regular expressions #801 [Sebastian Kanthak]
-
-* Fixed that SQLite3 exceptions are caught and reported properly #823 [yerejm]
-
-* Added that all types of after_find/after_initialized callbacks are triggered if the explicit implementation is present, not only the explicit implementation itself
-
-* Fixed that symbols can be used on attribute assignment, like page.emails.create(:subject => data.subject, :body => data.body)
-
-
-*1.8.0* (7th March, 2005)
-
-* Added ActiveRecord::Base.colorize_logging to control whether to use colors in logs or not (on by default)
-
-* Added support for timestamp with time zone in PostgreSQL #560 [Scott Barron]
-
-* Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius]. Documentation:
-
- * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
- +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
- objects that should be inspected to determine which attributes triggered the errors.
- * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
- You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
-
-* Fixed that postgresql adapter would fails when reading bytea fields with null value #771 [rodrigo k]
-
-* Added transactional fixtures that uses rollback to undo changes to fixtures instead of DELETE/INSERT -- it's much faster. See documentation under Fixtures #760 [Jeremy Kemper]
-
-* Added destruction of dependent objects in has_one associations when a new assignment happens #742 [mindel]. Example:
-
- class Account < ActiveRecord::Base
- has_one :credit_card, :dependent => true
- end
- class CreditCard < ActiveRecord::Base
- belongs_to :account
- end
-
- account.credit_card # => returns existing credit card, lets say id = 12
- account.credit_card = CreditCard.create("number" => "123")
- account.save # => CC with id = 12 is destroyed
-
-
-* Added validates_numericality_of #716 [Sebastian Kanthak/Chris McGrath]. Docuemntation:
-
- Validates whether the value of the specified attribute is numeric by trying to convert it to
- a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
- <tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true).
-
- class Person < ActiveRecord::Base
- validates_numericality_of :value, :on => :create
- end
-
- Configuration options:
- * <tt>message</tt> - A custom error message (default is: "is not a number")
- * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
- * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
-
-
-* Fixed that HasManyAssociation#count was using :finder_sql rather than :counter_sql if it was available #445 [Scott Barron]
-
-* Added better defaults for composed_of, so statements like composed_of :time_zone, :mapping => %w( time_zone time_zone ) can be written without the mapping part (it's now assumed)
-
-* Added MacroReflection#macro which will return a symbol describing the macro used (like :composed_of or :has_many) #718, #248 [james@slashetc.com]
-
-
-*1.7.0* (24th February, 2005)
-
-* Changed the auto-timestamping feature to use ActiveRecord::Base.default_timezone instead of entertaining the parallel ActiveRecord::Base.timestamps_gmt method. The latter is now deprecated and will throw a warning on use (but still work) #710 [Jamis Buck]
-
-* Added a OCI8-based Oracle adapter that has been verified to work with Oracle 8 and 9 #629 [Graham Jenkins]. Usage notes:
-
- 1. Key generation uses a sequence "rails_sequence" for all tables. (I couldn't find a simple
- and safe way of passing table-specific sequence information to the adapter.)
- 2. Oracle uses DATE or TIMESTAMP datatypes for both dates and times. Consequently I have had to
- resort to some hacks to get data converted to Date or Time in Ruby.
- If the column_name ends in _at (like created_at, updated_at) it's created as a Ruby Time. Else if the
- hours/minutes/seconds are 0, I make it a Ruby Date. Else it's a Ruby Time.
- This is nasty - but if you use Duck Typing you'll probably not care very much.
- In 9i it's tempting to map DATE to Date and TIMESTAMP to Time but I don't think that is
- valid - too many databases use DATE for both.
- Timezones and sub-second precision on timestamps are not supported.
- 3. Default values that are functions (such as "SYSDATE") are not supported. This is a
- restriction of the way active record supports default values.
- 4. Referential integrity constraints are not fully supported. Under at least
- some circumstances, active record appears to delete parent and child records out of
- sequence and out of transaction scope. (Or this may just be a problem of test setup.)
-
- The OCI8 driver can be retrieved from http://rubyforge.org/projects/ruby-oci8/
-
-* Added option :schema_order to the PostgreSQL adapter to support the use of multiple schemas per database #697 [YuriSchimke]
-
-* Optimized the SQL used to generate has_and_belongs_to_many queries by listing the join table first #693 [yerejm]
-
-* Fixed that when using validation macros with a custom message, if you happened to use single quotes in the message string you would get a parsing error #657 [tonka]
-
-* Fixed that Active Record would throw Broken Pipe errors with FCGI when the MySQL connection timed out instead of reconnecting #428 [Nicholas Seckar]
-
-* Added options to specify an SSL connection for MySQL. Define the following attributes in the connection config (config/database.yml in Rails) to use it: sslkey, sslcert, sslca, sslcapath, sslcipher. To use SSL with no client certs, just set :sslca = '/dev/null'. http://dev.mysql.com/doc/mysql/en/secure-connections.html #604 [daniel@nightrunner.com]
-
-* Added automatic dropping/creating of test tables for running the unit tests on all databases #587 [adelle@bullet.net.au]
-
-* Fixed that find_by_* would fail when column names had numbers #670 [demetrius]
-
-* Fixed the SQL Server adapter on a bunch of issues #667 [DeLynn]
-
- 1. Created a new columns method that is much cleaner.
- 2. Corrected a problem with the select and select_all methods
- that didn't account for the LIMIT clause being passed into raw SQL statements.
- 3. Implemented the string_to_time method in order to create proper instances of the time class.
- 4. Added logic to the simplified_type method that allows the database to specify the scale of float data.
- 5. Adjusted the quote_column_name to account for the fact that MS SQL is bothered by a forward slash in the data string.
-
-* Fixed that the dynamic finder like find_all_by_something_boolean(false) didn't work #649 [lmarlow]
-
-* Added validates_each that validates each specified attribute against a block #610 [Jeremy Kemper]. Example:
-
- class Person < ActiveRecord::Base
- validates_each :first_name, :last_name do |record, attr|
- record.errors.add attr, 'starts with z.' if attr[0] == ?z
- end
- end
-
-* Added :allow_nil as an explicit option for validates_length_of, so unless that's set to true having the attribute as nil will also return an error if a range is specified as :within #610 [Jeremy Kemper]
-
-* Added that validates_* now accept blocks to perform validations #618 [Tim Bates]. Example:
-
- class Person < ActiveRecord::Base
- validate { |person| person.errors.add("title", "will never be valid") if SHOULD_NEVER_BE_VALID }
- end
-
-* Addded validation for validate all the associated objects before declaring failure with validates_associated #618 [Tim Bates]
-
-* Added keyword-style approach to defining the custom relational bindings #545 [Jamis Buck]. Example:
-
- class Project < ActiveRecord::Base
- primary_key "sysid"
- table_name "XYZ_PROJECT"
- inheritance_column { original_inheritance_column + "_id" }
- end
-
-* Fixed Base#clone for use with PostgreSQL #565 [hanson@surgery.wisc.edu]
-
-
-*1.6.0* (January 25th, 2005)
-
-* Added that has_many association build and create methods can take arrays of record data like Base#create and Base#build to build/create multiple records at once.
-
-* Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336
-
-* Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once.
-
-* Added the option of supplying an array of ids and attributes to Base#update, so that multiple records can be updated at once (inspired by #526/Duane Johnson). Example
-
- people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
- Person.update(people.keys, people.values)
-
-* Added ActiveRecord::Base.timestamps_gmt that can be set to true to make the automated timestamping use GMT instead of local time #520 [Scott Baron]
-
-* Added that update_all calls sanitize_sql on its updates argument, so stuff like MyRecord.update_all(['time = ?', Time.now]) works #519 [notahat]
-
-* Fixed that the dynamic finders didn't treat nil as a "IS NULL" but rather "= NULL" case #515 [Demetrius]
-
-* Added bind-named arrays for interpolating a group of ids or strings in conditions #528 [Jeremy Kemper]
-
-* Added that has_and_belongs_to_many associations with additional attributes also can be created between unsaved objects and only committed to the database when Base#save is called on the associator #524 [Eric Anderson]
-
-* Fixed that records fetched with piggy-back attributes or through rich has_and_belongs_to_many associations couldn't be saved due to the extra attributes not part of the table #522 [Eric Anderson]
-
-* Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not
-
-* Fixed that association proxies would fail === tests like PremiumSubscription === @account.subscription
-
-* Fixed that column aliases didn't work as expected with the new MySql411 driver #507 [Demetrius]
-
-* Fixed that find_all would produce invalid sql when called sequentialy #490 [Scott Baron]
-
-
-*1.5.1* (January 18th, 2005)
-
-* Fixed that the belongs_to and has_one proxy would fail a test like 'if project.manager' -- this unfortunately also means that you can't call methods like project.manager.build unless there already is a manager on the project #492 [Tim Bates]
-
-* Fixed that the Ruby/MySQL adapter wouldn't connect if the password was empty #503 [Pelle]
-
-
-*1.5.0* (January 17th, 2005)
-
-* Fixed that unit tests for MySQL are now run as the "rails" user instead of root #455 [Eric Hodel]
-
-* Added validates_associated that enables validation of objects in an unsaved association #398 [Tim Bates]. Example:
-
- class Book < ActiveRecord::Base
- has_many :pages
- belongs_to :library
-
- validates_associated :pages, :library
- end
-
-* Added support for associating unsaved objects #402 [Tim Bates]. Rules that govern this addition:
-
- == Unsaved objects and associations
-
- You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
- aware of, mostly involving the saving of associated objects.
-
- === One-to-one associations
-
- * Assigning an object to a has_one association automatically saves that object, and the object being replaced (if there is one), in
- order to update their primary keys - except if the parent object is unsaved (new_record? == true).
- * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
- is cancelled.
- * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
- * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
- not save the parent either.
-
- === Collections
-
- * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
- (the owner of the collection) is not yet stored in the database.
- * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
- * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
- * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
-
-* Added replace to associations, so you can do project.manager.replace(new_manager) or project.milestones.replace(new_milestones) #402 [Tim Bates]
-
-* Added build and create methods to has_one and belongs_to associations, so you can now do project.manager.build(attributes) #402 [Tim Bates]
-
-* Added that if a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last. #402 [Tim Bates]
-
-* Fixed that Base#== wouldn't work for multiple references to the same unsaved object #402 [Tim Bates]
-
-* Fixed binary support for PostgreSQL #444 [alex@byzantine.no]
-
-* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
- collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
- it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
- it'll take one less SELECT query if you use length.
-
-* Added Base#attributes that returns a hash of all the attributes with their names as keys and clones of their objects as values #433 [atyp.de]
-
-* Fixed that foreign keys named the same as the association would cause stack overflow #437 [Eric Anderson]
-
-* Fixed default scope of acts_as_list from "1" to "1 = 1", so it'll work in PostgreSQL (among other places) #427 [Alexey]
-
-* Added Base#reload that reloads the attributes of an object from the database #422 [Andreas Schwarz]
-
-* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 [Jeremy Kemper]
-
-* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower]
-
-* Added that Observers can use the observes class method instead of overwriting self.observed_class().
-
- Before:
- class ListSweeper < ActiveRecord::Base
- def self.observed_class() [ List, Item ]
- end
-
- After:
- class ListSweeper < ActiveRecord::Base
- observes List, Item
- end
-
-* Fixed that conditions in has_many and has_and_belongs_to_many should be interpolated just like the finder_sql is
-
-* Fixed Base#update_attribute to be indifferent to whether a string or symbol is used to describe the name
-
-* Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it easier to flip a switch or flag.
-
- Before: topic.update_attribute(:approved, !approved?)
- After : topic.toggle!(:approved)
-
-* Added Base#increment!(attribute) and Base#decrement!(attribute) that also saves the records. Example:
-
- page.views # => 1
- page.increment!(:views) # executes an UPDATE statement
- page.views # => 2
-
- page.increment(:views).increment!(:views)
- page.views # => 4
-
-* Added Base#increment(attribute) and Base#decrement(attribute) that encapsulates the += 1 and -= 1 patterns.
-
-
-
-
-*1.14.2* (April 9th, 2005)
-
-* Fixed calculations for the Oracle Adapter (closes #4626) [Michael Schoen]
-
-
-*1.14.1* (April 6th, 2006)
-
-* Fix type_name_with_module to handle type names that begin with '::'. Closes #4614. [Nicholas Seckar]
-
-* Fixed that that multiparameter assignment doesn't work with aggregations (closes #4620) [Lars Pind]
-
-* Enable Limit/Offset in Calculations (closes #4558) [lmarlow]
-
-* Fixed that loading including associations returns all results if Load IDs For Limited Eager Loading returns none (closes #4528) [Rick Olson]
-
-* Fixed HasManyAssociation#find bugs when :finder_sql is set #4600 [lagroue@free.fr]
-
-* Allow AR::Base#respond_to? to behave when @attributes is nil [Ryan Davis]
-
-* Support eager includes when going through a polymorphic has_many association. [Rick Olson]
-
-* Added support for eagerly including polymorphic has_one associations. (closes #4525) [Rick Olson]
-
- class Post < ActiveRecord::Base
- has_one :tagging, :as => :taggable
- end
-
- Post.find :all, :include => :tagging
-
-* Added descriptive error messages for invalid has_many :through associations: going through :has_one or :has_and_belongs_to_many [Rick Olson]
-
-* Added support for going through a polymorphic has_many association: (closes #4401) [Rick Olson]
-
- class PhotoCollection < ActiveRecord::Base
- has_many :photos, :as => :photographic
- belongs_to :firm
- end
-
- class Firm < ActiveRecord::Base
- has_many :photo_collections
- has_many :photos, :through => :photo_collections
- end
-
-* Multiple fixes and optimizations in PostgreSQL adapter, allowing ruby-postgres gem to work properly. [ruben.nine@gmail.com]
-
-* Fixed that AssociationCollection#delete_all should work even if the records of the association are not loaded yet. [Florian Weber]
-
-* Changed those private ActiveRecord methods to take optional third argument :auto instead of nil for performance optimizations. (closes #4456) [Stefan]
-
-* Private ActiveRecord methods add_limit!, add_joins!, and add_conditions! take an OPTIONAL third argument 'scope' (closes #4456) [Rick Olson]
-
-* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model [David Heinemeier Hansson]
-
-* Fixed that records returned from has_and_belongs_to_many associations with additional attributes should be marked as read only (fixes #4512) [David Heinemeier Hansson]
-
-* Do not implicitly mark recordss of has_many :through as readonly but do mark habtm records as readonly (eventually only on join tables without rich attributes). [Marcel Mollina Jr.]
-
-* Fixed broken OCIAdapter #4457 [Michael Schoen]
-
-
-*1.14.0* (March 27th, 2006)
-
-* Replace 'rescue Object' with a finer grained rescue. Closes #4431. [Nicholas Seckar]
-
-* Fixed eager loading so that an aliased table cannot clash with a has_and_belongs_to_many join table [Rick Olson]
-
-* Add support for :include to with_scope [andrew@redlinesoftware.com]
-
-* Support the use of public synonyms with the Oracle adapter; required ruby-oci8 v0.1.14 #4390 [Michael Schoen]
-
-* Change periods (.) in table aliases to _'s. Closes #4251 [jeff@ministrycentered.com]
-
-* Changed has_and_belongs_to_many join to INNER JOIN for Mysql 3.23.x. Closes #4348 [Rick Olson]
-
-* Fixed issue that kept :select options from being scoped [Rick Olson]
-
-* Fixed db_schema_import when binary types are present #3101 [David Heinemeier Hansson]
-
-* Fixed that MySQL enums should always be returned as strings #3501 [David Heinemeier Hansson]
-
-* Change has_many :through to use the :source option to specify the source association. :class_name is now ignored. [Rick Olson]
-
- class Connection < ActiveRecord::Base
- belongs_to :user
- belongs_to :channel
- end
-
- class Channel < ActiveRecord::Base
- has_many :connections
- has_many :contacts, :through => :connections, :class_name => 'User' # OLD
- has_many :contacts, :through => :connections, :source => :user # NEW
- end
-
-* Fixed DB2 adapter so nullable columns will be determines correctly now and quotes from column default values will be removed #4350 [contact@maik-schmidt.de]
-
-* Allow overriding of find parameters in scoped has_many :through calls [Rick Olson]
-
- In this example, :include => false disables the default eager association from loading. :select changes the standard
- select clause. :joins specifies a join that is added to the end of the has_many :through query.
-
- class Post < ActiveRecord::Base
- has_many :tags, :through => :taggings, :include => :tagging do
- def add_joins_and_select
- find :all, :select => 'tags.*, authors.id as author_id', :include => false,
- :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
- end
- end
- end
-
-* Fixed that schema changes while the database was open would break any connections to an SQLite database (now we reconnect if that error is throw) [David Heinemeier Hansson]
-
-* Don't classify the has_one class when eager loading, it is already singular. Add tests. (closes #4117) [Jonathan Viney]
-
-* Quit ignoring default :include options in has_many :through calls [Mark James]
-
-* Allow has_many :through associations to find the source association by setting a custom class (closes #4307) [Jonathan Viney]
-
-* Eager Loading support added for has_many :through => :has_many associations (see below). [Rick Olson]
-
-* Allow has_many :through to work on has_many associations (closes #3864) [sco@scottraymond.net] Example:
-
- class Firm < ActiveRecord::Base
- has_many :clients
- has_many :invoices, :through => :clients
- end
-
- class Client < ActiveRecord::Base
- belongs_to :firm
- has_many :invoices
- end
-
- class Invoice < ActiveRecord::Base
- belongs_to :client
- end
-
-* Raise error when trying to select many polymorphic objects with has_many :through or :include (closes #4226) [Josh Susser]
-
-* Fixed has_many :through to include :conditions set on the :through association. closes #4020 [Jonathan Viney]
-
-* Fix that has_many :through honors the foreign key set by the belongs_to association in the join model (closes #4259) [andylien@gmail.com / Rick Olson]
-
-* SQL Server adapter gets some love #4298 [Ryan Tomayko]
-
-* Added OpenBase database adapter that builds on top of the http://www.spice-of-life.net/ruby-openbase/ driver. All functionality except LIMIT/OFFSET is supported #3528 [derrickspell@cdmplus.com]
-
-* Rework table aliasing to account for truncated table aliases. Add smarter table aliasing when doing eager loading of STI associations. This allows you to use the association name in the order/where clause. [Jonathan Viney / Rick Olson] #4108 Example (SpecialComment is using STI):
-
- Author.find(:all, :include => { :posts => :special_comments }, :order => 'special_comments.body')
-
-* Add AbstractAdapter#table_alias_for to create table aliases according to the rules of the current adapter. [Rick Olson]
-
-* Provide access to the underlying database connection through Adapter#raw_connection. Enables the use of db-specific methods without complicating the adapters. #2090 [Michael Koziarski]
-
-* Remove broken attempts at handling columns with a default of 'now()' in the postgresql adapter. #2257 [Michael Koziarski]
-
-* Added connection#current_database that'll return of the current database (only works in MySQL, SQL Server, and Oracle so far -- please help implement for the rest of the adapters) #3663 [Tom Ward]
-
-* Fixed that Migration#execute would have the table name prefix appended to its query #4110 [mark.imbriaco@pobox.com]
-
-* Make all tinyint(1) variants act like boolean in mysql (tinyint(1) unsigned, etc.) [Jamis Buck]
-
-* Use association's :conditions when eager loading. [Jeremy Evans] #4144
-
-* Alias the has_and_belongs_to_many join table on eager includes. #4106 [Jeremy Evans]
-
- This statement would normally error because the projects_developers table is joined twice, and therefore joined_on would be ambiguous.
-
- Developer.find(:all, :include => {:projects => :developers}, :conditions => 'join_project_developers.joined_on IS NOT NULL')
-
-* Oracle adapter gets some love #4230 [Michael Schoen]
-
- * Changes :text to CLOB rather than BLOB [Moses Hohman]
- * Fixes an issue with nil numeric length/scales (several)
- * Implements support for XMLTYPE columns [wilig / Kubo Takehiro]
- * Tweaks a unit test to get it all green again
- * Adds support for #current_database
-
-* Added Base.abstract_class? that marks which classes are not part of the Active Record hierarchy #3704 [Rick Olson]
-
- class CachedModel < ActiveRecord::Base
- self.abstract_class = true
- end
-
- class Post < CachedModel
- end
-
- CachedModel.abstract_class?
- => true
-
- Post.abstract_class?
- => false
-
- Post.base_class
- => Post
-
- Post.table_name
- => 'posts'
-
-* Allow :dependent options to be used with polymorphic joins. #3820 [Rick Olson]
-
- class Foo < ActiveRecord::Base
- has_many :attachments, :as => :attachable, :dependent => :delete_all
- end
-
-* Nicer error message on has_many :through when :through reflection can not be found. #4042 [court3nay]
-
-* Upgrade to Transaction::Simple 1.3 [Jamis Buck]
-
-* Catch FixtureClassNotFound when using instantiated fixtures on a fixture that has no ActiveRecord model [Rick Olson]
-
-* Allow ordering of calculated results and/or grouped fields in calculations [solo@gatelys.com]
-
-* Make ActiveRecord::Base#save! return true instead of nil on success. #4173 [johan@johansorensen.com]
-
-* Dynamically set allow_concurrency. #4044 [Stefan Kaes]
-
-* Added Base#to_xml that'll turn the current record into a XML representation [David Heinemeier Hansson]. Example:
-
- topic.to_xml
-
- ...returns:
-
- <?xml version="1.0" encoding="UTF-8"?>
- <topic>
- <title>The First Topic</title>
- <author-name>David</author-name>
- <id type="integer">1</id>
- <approved type="boolean">false</approved>
- <replies-count type="integer">0</replies-count>
- <bonus-time type="datetime">2000-01-01 08:28:00</bonus-time>
- <written-on type="datetime">2003-07-16 09:28:00</written-on>
- <content>Have a nice day</content>
- <author-email-address>david@loudthinking.com</author-email-address>
- <parent-id></parent-id>
- <last-read type="date">2004-04-15</last-read>
- </topic>
-
- ...and you can configure with:
-
- topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
-
- ...that'll return:
-
- <topic>
- <title>The First Topic</title>
- <author-name>David</author-name>
- <approved type="boolean">false</approved>
- <content>Have a nice day</content>
- <author-email-address>david@loudthinking.com</author-email-address>
- <parent-id></parent-id>
- <last-read type="date">2004-04-15</last-read>
- </topic>
-
- You can even do load first-level associations as part of the document:
-
- firm.to_xml :include => [ :account, :clients ]
-
- ...that'll return something like:
-
- <?xml version="1.0" encoding="UTF-8"?>
- <firm>
- <id type="integer">1</id>
- <rating type="integer">1</rating>
- <name>37signals</name>
- <clients>
- <client>
- <rating type="integer">1</rating>
- <name>Summit</name>
- </client>
- <client>
- <rating type="integer">1</rating>
- <name>Microsoft</name>
- </client>
- </clients>
- <account>
- <id type="integer">1</id>
- <credit-limit type="integer">50</credit-limit>
- </account>
- </firm>
-
-* Allow :counter_cache to take a column name for custom counter cache columns [Jamis Buck]
-
-* Documentation fixes for :dependent [robby@planetargon.com]
-
-* Stop the MySQL adapter crashing when views are present. #3782 [Jonathan Viney]
-
-* Don't classify the belongs_to class, it is already singular #4117 [keithm@infused.org]
-
-* Allow set_fixture_class to take Classes instead of strings for a class in a module. Raise FixtureClassNotFound if a fixture can't load. [Rick Olson]
-
-* Fix quoting of inheritance column for STI eager loading #4098 [Jonathan Viney <jonathan@bluewire.net.nz>]
-
-* Added smarter table aliasing for eager associations for multiple self joins #3580 [Rick Olson]
-
- * The first time a table is referenced in a join, no alias is used.
- * After that, the parent class name and the reflection name are used.
-
- Tree.find(:all, :include => :children) # LEFT OUTER JOIN trees AS tree_children ...
-
- * Any additional join references get a numerical suffix like '_2', '_3', etc.
-
-* Fixed eager loading problems with single-table inheritance #3580 [Rick Olson]. Post.find(:all, :include => :special_comments) now returns all posts, and any special comments that the posts may have. And made STI work with has_many :through and polymorphic belongs_to.
-
-* Added cascading eager loading that allows for queries like Author.find(:all, :include=> { :posts=> :comments }), which will fetch all authors, their posts, and the comments belonging to those posts in a single query (using LEFT OUTER JOIN) #3913 [anna@wota.jp]. Examples:
-
- # cascaded in two levels
- >> Author.find(:all, :include=>{:posts=>:comments})
- => authors
- +- posts
- +- comments
-
- # cascaded in two levels and normal association
- >> Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
- => authors
- +- posts
- +- comments
- +- categorizations
-
- # cascaded in two levels with two has_many associations
- >> Author.find(:all, :include=>{:posts=>[:comments, :categorizations]})
- => authors
- +- posts
- +- comments
- +- categorizations
-
- # cascaded in three levels
- >> Company.find(:all, :include=>{:groups=>{:members=>{:favorites}}})
- => companies
- +- groups
- +- members
- +- favorites
-
-* Make counter cache work when replacing an association #3245 [eugenol@gmail.com]
-
-* Make migrations verbose [Jamis Buck]
-
-* Make counter_cache work with polymorphic belongs_to [Jamis Buck]
-
-* Fixed that calling HasOneProxy#build_model repeatedly would cause saving to happen #4058 [anna@wota.jp]
-
-* Added Sybase database adapter that relies on the Sybase Open Client bindings (see http://raa.ruby-lang.org/project/sybase-ctlib) #3765 [John Sheets]. It's almost completely Active Record compliant (including migrations), but has the following caveats:
-
- * Does not support DATE SQL column types; use DATETIME instead.
- * Date columns on HABTM join tables are returned as String, not Time.
- * Insertions are potentially broken for :polymorphic join tables
- * BLOB column access not yet fully supported
-
-* Clear stale, cached connections left behind by defunct threads. [Jeremy Kemper]
-
-* CHANGED DEFAULT: set ActiveRecord::Base.allow_concurrency to false. Most AR usage is in single-threaded applications. [Jeremy Kemper]
-
-* Renamed the "oci" adapter to "oracle", but kept the old name as an alias #4017 [Michael Schoen]
-
-* Fixed that Base.save should always return false if the save didn't succeed, including if it has halted by before_save's #1861, #2477 [David Heinemeier Hansson]
-
-* Speed up class -> connection caching and stale connection verification. #3979 [Stefan Kaes]
-
-* Add set_fixture_class to allow the use of table name accessors with models which use set_table_name. [Kevin Clark]
-
-* Added that fixtures to placed in subdirectories of the main fixture files are also loaded #3937 [dblack@wobblini.net]
-
-* Define attribute query methods to avoid method_missing calls. #3677 [Jonathan Viney]
-
-* ActiveRecord::Base.remove_connection explicitly closes database connections and doesn't corrupt the connection cache. Introducing the disconnect! instance method for the PostgreSQL, MySQL, and SQL Server adapters; implementations for the others are welcome. #3591 [Simon Stapleton, Tom Ward]
-
-* Added support for nested scopes #3407 [anna@wota.jp]. Examples:
-
- Developer.with_scope(:find => { :conditions => "salary > 10000", :limit => 10 }) do
- Developer.find(:all) # => SELECT * FROM developers WHERE (salary > 10000) LIMIT 10
-
- # inner rule is used. (all previous parameters are ignored)
- Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
- Developer.find(:all) # => SELECT * FROM developers WHERE (name = 'Jamis')
- end
-
- # parameters are merged
- Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
- Developer.find(:all) # => SELECT * FROM developers WHERE (( salary > 10000 ) AND ( name = 'Jamis' )) LIMIT 10
- end
- end
-
-* Fixed db2 connection with empty user_name and auth options #3622 [phurley@gmail.com]
-
-* Fixed validates_length_of to work on UTF-8 strings by using characters instead of bytes #3699 [Masao Mutoh]
-
-* Fixed that reflections would bleed across class boundaries in single-table inheritance setups #3796 [Lars Pind]
-
-* Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Base.maxmium, and the generic Base.calculate. All can be used with :group and :having. Calculations and statitics need no longer require custom SQL. #3958 [Rick Olson]. Examples:
-
- Person.average :age
- Person.minimum :age
- Person.maximum :age
- Person.sum :salary, :group => :last_name
-
-* Renamed Errors#count to Errors#size but kept an alias for the old name (and included an alias for length too) #3920 [Luke Redpath]
-
-* Reflections don't attempt to resolve module nesting of association classes. Simplify type computation. [Jeremy Kemper]
-
-* Improved the Oracle OCI Adapter with better performance for column reflection (from #3210), fixes to migrations (from #3476 and #3742), tweaks to unit tests (from #3610), and improved documentation (from #2446) #3879 [Aggregated by schoenm@earthlink.net]
-
-* Fixed that the schema_info table used by ActiveRecord::Schema.define should respect table pre- and suffixes #3834 [rubyonrails@atyp.de]
-
-* Added :select option to Base.count that'll allow you to select something else than * to be counted on. Especially important for count queries using DISTINCT #3839 [Stefan Kaes]
-
-* Correct syntax error in mysql DDL, and make AAACreateTablesTest run first [Bob Silva]
-
-* Allow :include to be used with has_many :through associations #3611 [Michael Schoen]
-
-* PostgreSQL: smarter schema dumps using pk_and_sequence_for(table). #2920 [Blair Zajac]
-
-* SQLServer: more compatible limit/offset emulation. #3779 [Tom Ward]
-
-* Polymorphic join support for has_one associations (has_one :foo, :as => :bar) #3785 [Rick Olson]
-
-* PostgreSQL: correctly parse negative integer column defaults. #3776 [bellis@deepthought.org]
-
-* Fix problems with count when used with :include [Jeremy Hopple and Kevin Clark]
-
-* ActiveRecord::RecordInvalid now states which validations failed in its default error message [Tobias Lütke]
-
-* Using AssociationCollection#build with arrays of hashes should call build, not create [David Heinemeier Hansson]
-
-* Remove definition of reloadable? from ActiveRecord::Base to make way for new Reloadable code. [Nicholas Seckar]
-
-* Fixed schema handling for DB2 adapter that didn't work: an initial schema could be set, but it wasn't used when getting tables and indexes #3678 [Maik Schmidt]
-
-* Support the :column option for remove_index with the PostgreSQL adapter. #3661 [Shugo Maeda]
-
-* Add documentation for add_index and remove_index. #3600 [Manfred Stienstra <m.stienstra@fngtps.com>]
-
-* If the OCI library is not available, raise an exception indicating as much. #3593 [Michael Schoen]
-
-* Add explicit :order in finder tests as postgresql orders results differently by default. #3577. [Rick Olson]
-
-* Make dynamic finders honor additional passed in :conditions. #3569 [Oleg Pudeyev <pudeyo@rpi.edu>, Marcel Molina Jr.]
-
-* Show a meaningful error when the DB2 adapter cannot be loaded due to missing dependencies. [Nicholas Seckar]
-
-* Make .count work for has_many associations with multi line finder sql [Michael Schoen]
-
-* Add AR::Base.base_class for querying the ancestor AR::Base subclass [Jamis Buck]
-
-* Allow configuration of the column used for optimistic locking [wilsonb@gmail.com]
-
-* Don't hardcode 'id' in acts as list. [ror@philippeapril.com]
-
-* Fix date errors for SQLServer in association tests. #3406 [Kevin Clark]
-
-* Escape database name in MySQL adapter when creating and dropping databases. #3409 [anna@wota.jp]
-
-* Disambiguate table names for columns in validates_uniqueness_of's WHERE clause. #3423 [alex.borovsky@gmail.com]
-
-* .with_scope imposed create parameters now bypass attr_protected [Tobias Lütke]
-
-* Don't raise an exception when there are more keys than there are named bind variables when sanitizing conditions. [Marcel Molina Jr.]
-
-* Multiple enhancements and adjustments to DB2 adaptor. #3377 [contact@maik-schmidt.de]
-
-* Sanitize scoped conditions. [Marcel Molina Jr.]
-
-* Added option to Base.reflection_of_all_associations to specify a specific association to scope the call. For example Base.reflection_of_all_associations(:has_many) [David Heinemeier Hansson]
-
-* Added ActiveRecord::SchemaDumper.ignore_tables which tells SchemaDumper which tables to ignore. Useful for tables with funky column like the ones required for tsearch2. [Tobias Lütke]
-
-* SchemaDumper now doesn't fail anymore when there are unknown column types in the schema. Instead the table is ignored and a Comment is left in the schema.rb. [Tobias Lütke]
-
-* Fixed that saving a model with multiple habtm associations would only save the first one. #3244 [yanowitz-rubyonrails@quantumfoam.org, Florian Weber]
-
-* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 [wejn@box.cz, Rick Olson, Scott Barron]
-
-* removed :piggyback in favor of just allowing :select on :through associations. [Tobias Lütke]
-
-* made method missing delegation to class methods on relation target work on :through associations. [Tobias Lütke]
-
-* made .find() work on :through relations. [Tobias Lütke]
-
-* Fix typo in association docs. #3296. [Blair Zajac]
-
-* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model [Tobias Lütke]
-
-*1.13.2* (December 13th, 2005)
-
-* Become part of Rails 1.0
-
-* MySQL: allow encoding option for mysql.rb driver. [Jeremy Kemper]
-
-* Added option inheritance for find calls on has_and_belongs_to_many and has_many assosociations [David Heinemeier Hansson]. Example:
-
- class Post
- has_many :recent_comments, :class_name => "Comment", :limit => 10, :include => :author
- end
-
- post.recent_comments.find(:all) # Uses LIMIT 10 and includes authors
- post.recent_comments.find(:all, :limit => nil) # Uses no limit but include authors
- post.recent_comments.find(:all, :limit => nil, :include => nil) # Uses no limit and doesn't include authors
-
-* Added option to specify :group, :limit, :offset, and :select options from find on has_and_belongs_to_many and has_many assosociations [David Heinemeier Hansson]
-
-* MySQL: fixes for the bundled mysql.rb driver. #3160 [Justin Forder]
-
-* SQLServer: fix obscure optimistic locking bug. #3068 [kajism@yahoo.com]
-
-* SQLServer: support uniqueidentifier columns. #2930 [keithm@infused.org]
-
-* SQLServer: cope with tables names qualified by owner. #3067 [jeff@ministrycentered.com]
-
-* SQLServer: cope with columns with "desc" in the name. #1950 [Ron Lusk, Ryan Tomayko]
-
-* SQLServer: cope with primary keys with "select" in the name. #3057 [rdifrango@captechventures.com]
-
-* Oracle: active? performs a select instead of a commit. #3133 [Michael Schoen]
-
-* MySQL: more robust test for nullified result hashes. #3124 [Stefan Kaes]
-
-* Reloading an instance refreshes its aggregations as well as its associations. #3024 [François Beausoleil]
-
-* Fixed that using :include together with :conditions array in Base.find would cause NoMethodError #2887 [Paul Hammmond]
-
-* PostgreSQL: more robust sequence name discovery. #3087 [Rick Olson]
-
-* Oracle: use syntax compatible with Oracle 8. #3131 [Michael Schoen]
-
-* MySQL: work around ruby-mysql/mysql-ruby inconsistency with mysql.stat. Eliminate usage of mysql.ping because it doesn't guarantee reconnect. Explicitly close and reopen the connection instead. [Jeremy Kemper]
-
-* Added preliminary support for polymorphic associations [David Heinemeier Hansson]
-
-* Added preliminary support for join models [David Heinemeier Hansson]
-
-* Allow validate_uniqueness_of to be scoped by more than just one column. #1559. [jeremy@jthopple.com, Marcel Molina Jr.]
-
-* Firebird: active? and reconnect! methods for handling stale connections. #428 [Ken Kunz <kennethkunz@gmail.com>]
-
-* Firebird: updated for FireRuby 0.4.0. #3009 [Ken Kunz <kennethkunz@gmail.com>]
-
-* MySQL and PostgreSQL: active? compatibility with the pure-Ruby driver. #428 [Jeremy Kemper]
-
-* Oracle: active? check pings the database rather than testing the last command status. #428 [Michael Schoen]
-
-* SQLServer: resolve column aliasing/quoting collision when using limit or offset in an eager find. #2974 [kajism@yahoo.com]
-
-* Reloading a model doesn't lose track of its connection. #2996 [junk@miriamtech.com, Jeremy Kemper]
-
-* Fixed bug where using update_attribute after pushing a record to a habtm association of the object caused duplicate rows in the join table. #2888 [colman@rominato.com, Florian Weber, Michael Schoen]
-
-* MySQL, PostgreSQL: reconnect! also reconfigures the connection. Otherwise, the connection 'loses' its settings if it times out and is reconnected. #2978 [Shugo Maeda]
-
-* has_and_belongs_to_many: use JOIN instead of LEFT JOIN. [Jeremy Kemper]
-
-* MySQL: introduce :encoding option to specify the character set for client, connection, and results. Only available for MySQL 4.1 and later with the mysql-ruby driver. Do SHOW CHARACTER SET in mysql client to see available encodings. #2975 [Shugo Maeda]
-
-* Add tasks to create, drop and rebuild the MySQL and PostgreSQL test databases. [Marcel Molina Jr.]
-
-* Correct boolean handling in generated reader methods. #2945 [Don Park, Stefan Kaes]
-
-* Don't generate read methods for columns whose names are not valid ruby method names. #2946 [Stefan Kaes]
-
-* Document :force option to create_table. #2921 [Blair Zajac <blair@orcaware.com>]
-
-* Don't add the same conditions twice in has_one finder sql. #2916 [Jeremy Evans]
-
-* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
-
-* Introducing the Firebird adapter. Quote columns and use attribute_condition more consistently. Setup guide: http://wiki.rubyonrails.com/rails/pages/Firebird+Adapter #1874 [Ken Kunz <kennethkunz@gmail.com>]
-
-* SQLServer: active? and reconnect! methods for handling stale connections. #428 [kajism@yahoo.com, Tom Ward <tom@popdog.net>]
-
-* Associations handle case-equality more consistently: item.parts.is_a?(Array) and item.parts === Array. #1345 [MarkusQ@reality.com]
-
-* SQLServer: insert uses given primary key value if not nil rather than SELECT @@IDENTITY. #2866 [kajism@yahoo.com, Tom Ward <tom@popdog.net>]
-
-* Oracle: active? and reconnect! methods for handling stale connections. Optionally retry queries after reconnect. #428 [Michael Schoen <schoenm@earthlink.net>]
-
-* Correct documentation for Base.delete_all. #1568 [Newhydra]
-
-* Oracle: test case for column default parsing. #2788 [Michael Schoen <schoenm@earthlink.net>]
-
-* Update documentation for Migrations. #2861 [Tom Werner <tom@cube6media.com>]
-
-* When AbstractAdapter#log rescues an exception, attempt to detect and reconnect to an inactive database connection. Connection adapter must respond to the active? and reconnect! instance methods. Initial support for PostgreSQL, MySQL, and SQLite. Make certain that all statements which may need reconnection are performed within a logged block: for example, this means no avoiding log(sql, name) { } if @logger.nil? #428 [Jeremy Kemper]
-
-* Oracle: Much faster column reflection. #2848 [Michael Schoen <schoenm@earthlink.net>]
-
-* Base.reset_sequence_name analogous to reset_table_name (mostly useful for testing). Base.define_attr_method allows nil values. [Jeremy Kemper]
-
-* PostgreSQL: smarter sequence name defaults, stricter last_insert_id, warn on pk without sequence. [Jeremy Kemper]
-
-* PostgreSQL: correctly discover custom primary key sequences. #2594 [Blair Zajac <blair@orcaware.com>, meadow.nnick@gmail.com, Jeremy Kemper]
-
-* SQLServer: don't report limits for unsupported field types. #2835 [Ryan Tomayko]
-
-* Include the Enumerable module in ActiveRecord::Errors. [Rick Bradley <rick@rickbradley.com>]
-
-* Add :group option, correspond to GROUP BY, to the find method and to the has_many association. #2818 [rubyonrails@atyp.de]
-
-* Don't cast nil or empty strings to a dummy date. #2789 [Rick Bradley <rick@rickbradley.com>]
-
-* acts_as_list plays nicely with inheritance by remembering the class which declared it. #2811 [rephorm@rephorm.com]
-
-* Fix sqlite adaptor's detection of missing dbfile or database declaration. [Nicholas Seckar]
-
-* Fixed acts_as_list for definitions without an explicit :order #2803 [Jonathan Viney]
-
-* Upgrade bundled ruby-mysql 0.2.4 with mysql411 shim (see #440) to ruby-mysql 0.2.6 with a patchset for 4.1 protocol support. Local change [301] is now a part of the main driver; reapplied local change [2182]. Removed GC.start from Result.free. [tommy@tmtm.org, akuroda@gmail.com, Doug Fales <doug.fales@gmail.com>, Jeremy Kemper]
-
-* Correct handling of complex order clauses with SQL Server limit emulation. #2770 [Tom Ward <tom@popdog.net>, Matt B.]
-
-* Correct whitespace problem in Oracle default column value parsing. #2788 [rick@rickbradley.com]
-
-* Destroy associated has_and_belongs_to_many records after all before_destroy callbacks but before destroy. This allows you to act on the habtm association as you please while preserving referential integrity. #2065 [larrywilliams1@gmail.com, sam.kirchmeier@gmail.com, elliot@townx.org, Jeremy Kemper]
-
-* Deprecate the old, confusing :exclusively_dependent option in favor of :dependent => :delete_all. [Jeremy Kemper]
-
-* More compatible Oracle column reflection. #2771 [Ryan Davis <ryand-ruby@zenspider.com>, Michael Schoen <schoenm@earthlink.net>]
-
-
-*1.13.0* (November 7th, 2005)
-
-* Fixed faulty regex in get_table_name method (SQLServerAdapter) #2639 [Ryan Tomayko]
-
-* Added :include as an option for association declarations [David Heinemeier Hansson]. Example:
-
- has_many :posts, :include => [ :author, :comments ]
-
-* Rename Base.constrain to Base.with_scope so it doesn't conflict with existing concept of database constraints. Make scoping more robust: uniform method => parameters, validated method names and supported finder parameters, raise exception on nested scopes. [Jeremy Kemper] Example:
-
- Comment.with_scope(:find => { :conditions => 'active=true' }, :create => { :post_id => 5 }) do
- # Find where name = ? and active=true
- Comment.find :all, :conditions => ['name = ?', name]
- # Create comment associated with :post_id
- Comment.create :body => "Hello world"
- end
-
-* Fixed that SQL Server should ignore :size declarations on anything but integer and string in the agnostic schema representation #2756 [Ryan Tomayko]
-
-* Added constrain scoping for creates using a hash of attributes bound to the :creation key [David Heinemeier Hansson]. Example:
-
- Comment.constrain(:creation => { :post_id => 5 }) do
- # Associated with :post_id
- Comment.create :body => "Hello world"
- end
-
- This is rarely used directly, but allows for find_or_create on associations. So you can do:
-
- # If the tag doesn't exist, a new one is created that's associated with the person
- person.tags.find_or_create_by_name("Summer")
-
-* Added find_or_create_by_X as a second type of dynamic finder that'll create the record if it doesn't already exist [David Heinemeier Hansson]. Example:
-
- # No 'Summer' tag exists
- Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
-
- # Now the 'Summer' tag does exist
- Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
-
-* Added extension capabilities to has_many and has_and_belongs_to_many proxies [David Heinemeier Hansson]. Example:
-
- class Account < ActiveRecord::Base
- has_many :people do
- def find_or_create_by_name(name)
- first_name, *last_name = name.split
- last_name = last_name.join " "
-
- find_or_create_by_first_name_and_last_name(first_name, last_name)
- end
- end
- end
-
- person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
- person.first_name # => "David"
- person.last_name # => "Heinemeier Hansson"
-
- Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation).
-
-* Omit internal dtproperties table from SQLServer table list. #2729 [Ryan Tomayko]
-
-* Quote column names in generated SQL. #2728 [Ryan Tomayko]
-
-* Correct the pure-Ruby MySQL 4.1.1 shim's version test. #2718 [Jeremy Kemper]
-
-* Add Model.create! to match existing model.save! method. When save! raises RecordInvalid, you can catch the exception, retrieve the invalid record (invalid_exception.record), and see its errors (invalid_exception.record.errors). [Jeremy Kemper]
-
-* Correct fixture behavior when table name pluralization is off. #2719 [Rick Bradley <rick@rickbradley.com>]
-
-* Changed :dbfile to :database for SQLite adapter for consistency (old key still works as an alias) #2644 [Dan Peterson]
-
-* Added migration support for Oracle #2647 [Michael Schoen]
-
-* Worked around that connection can't be reset if allow_concurrency is off. #2648 [Michael Schoen <schoenm@earthlink.net>]
-
-* Fixed SQL Server adapter to pass even more tests and do even better #2634 [Ryan Tomayko]
-
-* Fixed SQL Server adapter so it honors options[:conditions] when applying :limits #1978 [Tom Ward]
-
-* Added migration support to SQL Server adapter (please someone do the same for Oracle and DB2) #2625 [Tom Ward]
-
-* Use AR::Base.silence rather than AR::Base.logger.silence in fixtures to preserve Log4r compatibility. #2618 [dansketcher@gmail.com]
-
-* Constraints are cloned so they can't be inadvertently modified while they're
-in effect. Added :readonly finder constraint. Calling an association collection's class method (Part.foobar via item.parts.foobar) constrains :readonly => false since the collection's :joins constraint would otherwise force it to true. [Jeremy Kemper <rails@bitsweat.net>]
-
-* Added :offset and :limit to the kinds of options that Base.constrain can use #2466 [duane.johnson@gmail.com]
-
-* Fixed handling of nil number columns on Oracle and cleaned up tests for Oracle in general #2555 [Michael Schoen]
-
-* Added quoted_true and quoted_false methods and tables to db2_adapter and cleaned up tests for DB2 #2493, #2624 [maik schmidt]
-
-
-*1.12.2* (October 26th, 2005)
-
-* Allow symbols to rename columns when using SQLite adapter. #2531 [Kevin Clark]
-
-* Map Active Record time to SQL TIME. #2575, #2576 [Robby Russell <robby@planetargon.com>]
-
-* Clarify semantics of ActiveRecord::Base#respond_to? #2560 [Stefan Kaes]
-
-* Fixed Association#clear for associations which have not yet been accessed. #2524 [Patrick Lenz <patrick@lenz.sh>]
-
-* HABTM finders shouldn't return readonly records. #2525 [Patrick Lenz <patrick@lenz.sh>]
-
-* Make all tests runnable on their own. #2521. [Blair Zajac <blair@orcaware.com>]
-
-
-*1.12.1* (October 19th, 2005)
-
-* Always parenthesize :conditions options so they may be safely combined with STI and constraints.
-
-* Correct PostgreSQL primary key sequence detection. #2507 [tmornini@infomania.com]
-
-* Added support for using limits in eager loads that involve has_many and has_and_belongs_to_many associations
-
-
-*1.12.0* (October 16th, 2005)
-
-* Update/clean up documentation (rdoc)
-
-* PostgreSQL sequence support. Use set_sequence_name in your model class to specify its primary key sequence. #2292 [Rick Olson <technoweenie@gmail.com>, Robby Russell <robby@planetargon.com>]
-
-* Change default logging colors to work on both white and black backgrounds. [Sam Stephenson]
-
-* YAML fixtures support ordered hashes for fixtures with foreign key dependencies in the same table. #1896 [purestorm@ggnore.net]
-
-* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 [Robby Russell <robby@planetargon.com>]
-
-* Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around.
-
-* Avoid memleak in dev mode when using fcgi
-
-* Simplified .clear on active record associations by using the existing delete_records method. #1906 [Caleb <me@cpb.ca>]
-
-* Delegate access to a customized primary key to the conventional id method. #2444. [Blair Zajac <blair@orcaware.com>]
-
-* Fix errors caused by assigning a has-one or belongs-to property to itself
-
-* Add ActiveRecord::Base.schema_format setting which specifies how databases should be dumped [Sam Stephenson]
-
-* Update DB2 adapter. #2206. [contact@maik-schmidt.de]
-
-* Corrections to SQLServer native data types. #2267. [rails.20.clarry@spamgourmet.com]
-
-* Deprecated ActiveRecord::Base.threaded_connection in favor of ActiveRecord::Base.allow_concurrency.
-
-* Protect id attribute from mass assigment even when the primary key is set to something else. #2438. [Blair Zajac <blair@orcaware.com>]
-
-* Misc doc fixes (typos/grammar/etc.). #2430. [coffee2code]
-
-* Add test coverage for content_columns. #2432. [coffee2code]
-
-* Speed up for unthreaded environments. #2431. [Stefan Kaes]
-
-* Optimization for Mysql selects using mysql-ruby extension greater than 2.6.3. #2426. [Stefan Kaes]
-
-* Speed up the setting of table_name. #2428. [Stefan Kaes]
-
-* Optimize instantiation of STI subclass records. In partial fullfilment of #1236. [Stefan Kaes]
-
-* Fix typo of 'constrains' to 'contraints'. #2069. [Michael Schuerig <michael@schuerig.de>]
-
-* Optimization refactoring for add_limit_offset!. In partial fullfilment of #1236. [Stefan Kaes]
-
-* Add ability to get all siblings, including the current child, with acts_as_tree. Recloses #2140. [Michael Schuerig <michael@schuerig.de>]
-
-* Add geometric type for postgresql adapter. #2233 [Andrew Kaspick]
-
-* Add option (true by default) to generate reader methods for each attribute of a record to avoid the overhead of calling method missing. In partial fullfilment of #1236. [Stefan Kaes]
-
-* Add convenience predicate methods on Column class. In partial fullfilment of #1236. [Stefan Kaes]
-
-* Raise errors when invalid hash keys are passed to ActiveRecord::Base.find. #2363 [Chad Fowler <chad@chadfowler.com>, Nicholas Seckar]
-
-* Added :force option to create_table that'll try to drop the table if it already exists before creating
-
-* Fix transactions so that calling return while inside a transaction will not leave an open transaction on the connection. [Nicholas Seckar]
-
-* Use foreign_key inflection uniformly. #2156 [Blair Zajac <blair@orcaware.com>]
-
-* model.association.clear should destroy associated objects if :dependent => true instead of nullifying their foreign keys. #2221 [joergd@pobox.com, ObieFernandez <obiefernandez@gmail.com>]
-
-* Returning false from before_destroy should cancel the action. #1829 [Jeremy Huffman]
-
-* Recognize PostgreSQL NOW() default as equivalent to CURRENT_TIMESTAMP or CURRENT_DATE, depending on the column's type. #2256 [mat <mat@absolight.fr>]
-
-* Extensive documentation for the abstract database adapter. #2250 [François Beausoleil <fbeausoleil@ftml.net>]
-
-* Clean up Fixtures.reset_sequences for PostgreSQL. Handle tables with no rows and models with custom primary keys. #2174, #2183 [jay@jay.fm, Blair Zajac <blair@orcaware.com>]
-
-* Improve error message when nil is assigned to an attr which validates_size_of within a range. #2022 [Manuel Holtgrewe <purestorm@ggnore.net>]
-
-* Make update_attribute use the same writer method that update_attributes uses.
- #2237 [trevor@protocool.com]
-
-* Make migrations honor table name prefixes and suffixes. #2298 [Jakob Skjerning, Marcel Molina Jr.]
-
-* Correct and optimize PostgreSQL bytea escaping. #1745, #1837 [dave@cherryville.org, ken@miriamtech.com, bellis@deepthought.org]
-
-* Fixtures should only reset a PostgreSQL sequence if it corresponds to an integer primary key named id. #1749 [chris@chrisbrinker.com]
-
-* Standardize the interpretation of boolean columns in the Mysql and Sqlite adapters. (Use MysqlAdapter.emulate_booleans = false to disable this behavior)
-
-* Added new symbol-driven approach to activating observers with Base#observers= [David Heinemeier Hansson]. Example:
-
- ActiveRecord::Base.observers = :cacher, :garbage_collector
-
-* Added AbstractAdapter#select_value and AbstractAdapter#select_values as convenience methods for selecting single values, instead of hashes, of the first column in a SELECT #2283 [solo@gatelys.com]
-
-* Wrap :conditions in parentheses to prevent problems with OR's #1871 [Jamis Buck]
-
-* Allow the postgresql adapter to work with the SchemaDumper. [Jamis Buck]
-
-* Add ActiveRecord::SchemaDumper for dumping a DB schema to a pure-ruby file, making it easier to consolidate large migration lists and port database schemas between databases. [Jamis Buck]
-
-* Fixed migrations for Windows when using more than 10 [David Naseby]
-
-* Fixed that the create_x method from belongs_to wouldn't save the association properly #2042 [Florian Weber]
-
-* Fixed saving a record with two unsaved belongs_to associations pointing to the same object #2023 [Tobias Lütke]
-
-* Improved migrations' behavior when the schema_info table is empty. [Nicholas Seckar]
-
-* Fixed that Observers didn't observe sub-classes #627 [Florian Weber]
-
-* Fix eager loading error messages, allow :include to specify tables using strings or symbols. Closes #2222 [Marcel Molina Jr.]
-
-* Added check for RAILS_CONNECTION_ADAPTERS on startup and only load the connection adapters specified within if its present (available in Rails through config.connection_adapters using the new config) #1958 [skae]
-
-* Fixed various problems with has_and_belongs_to_many when using customer finder_sql #2094 [Florian Weber]
-
-* Added better exception error when unknown column types are used with migrations #1814 [François Beausoleil]
-
-* Fixed "connection lost" issue with the bundled Ruby/MySQL driver (would kill the app after 8 hours of inactivity) #2163, #428 [kajism@yahoo.com]
-
-* Fixed comparison of Active Record objects so two new objects are not equal #2099 [deberg]
-
-* Fixed that the SQL Server adapter would sometimes return DBI::Timestamp objects instead of Time #2127 [Tom Ward]
-
-* Added the instance methods #root and #ancestors on acts_as_tree and fixed siblings to not include the current node #2142, #2140 [coffee2code]
-
-* Fixed that Active Record would call SHOW FIELDS twice (or more) for the same model when the cached results were available #1947 [sd@notso.net]
-
-* Added log_level and use_silence parameter to ActiveRecord::Base.benchmark. The first controls at what level the benchmark statement will be logged (now as debug, instead of info) and the second that can be passed false to include all logging statements during the benchmark block/
-
-* Make sure the schema_info table is created before querying the current version #1903
-
-* Fixtures ignore table name prefix and suffix #1987 [Jakob Skjerning]
-
-* Add documentation for index_type argument to add_index method for migrations #2005 [Blaine]
-
-* Modify read_attribute to allow a symbol argument #2024 [Ken Kunz]
-
-* Make destroy return self #1913 [Sebastian Kanthak]
-
-* Fix typo in validations documentation #1938 [court3nay]
-
-* Make acts_as_list work for insert_at(1) #1966 [hensleyl@papermountain.org]
-
-* Fix typo in count_by_sql documentation #1969 [Alexey Verkhovsky]
-
-* Allow add_column and create_table to specify NOT NULL #1712 [emptysands@gmail.com]
-
-* Fix create_table so that id column is implicitly added [Rick Olson]
-
-* Default sequence names for Oracle changed to #{table_name}_seq, which is the most commonly used standard. In addition, a new method ActiveRecord::Base#set_sequence_name allows the developer to set the sequence name per model. This is a non-backwards-compatible change -- anyone using the old-style "rails_sequence" will need to either create new sequences, or set: ActiveRecord::Base.set_sequence_name = "rails_sequence" #1798
-
-* OCIAdapter now properly handles synonyms, which are commonly used to separate out the schema owner from the application user #1798
-
-* Fixed the handling of camelCase columns names in Oracle #1798
-
-* Implemented for OCI the Rakefile tasks of :clone_structure_to_test, :db_structure_dump, and :purge_test_database, which enable Oracle folks to enjoy all the agile goodness of Rails for testing. Note that the current implementation is fairly limited -- only tables and sequences are cloned, not constraints or indexes. A full clone in Oracle generally requires some manual effort, and is version-specific. Post 9i, Oracle recommends the use of the DBMS_METADATA package, though that approach requires editing of the physical characteristics generated #1798
-
-* Fixed the handling of multiple blob columns in Oracle if one or more of them are null #1798
-
-* Added support for calling constrained class methods on has_many and has_and_belongs_to_many collections #1764 [Tobias Lütke]
-
- class Comment < AR:B
- def self.search(q)
- find(:all, :conditions => ["body = ?", q])
- end
- end
-
- class Post < AR:B
- has_many :comments
- end
-
- Post.find(1).comments.search('hi') # => SELECT * from comments WHERE post_id = 1 AND body = 'hi'
-
- NOTICE: This patch changes the underlying SQL generated by has_and_belongs_to_many queries. If your relying on that, such as
- by explicitly referencing the old t and j aliases, you'll need to update your code. Of course, you _shouldn't_ be relying on
- details like that no less than you should be diving in to touch private variables. But just in case you do, consider yourself
- noticed :)
-
-* Added migration support for SQLite (using temporary tables to simulate ALTER TABLE) #1771 [Sam Stephenson]
-
-* Remove extra definition of supports_migrations? from abstract_adaptor.rb [Nicholas Seckar]
-
-* Fix acts_as_list so that moving next-to-last item to the bottom does not result in duplicate item positions
-
-* Fixed incompatibility in DB2 adapter with the new limit/offset approach #1718 [Maik Schmidt]
-
-* Added :select option to find which can specify a different value than the default *, like find(:all, :select => "first_name, last_name"), if you either only want to select part of the columns or exclude columns otherwise included from a join #1338 [Stefan Kaes]
-
-
-*1.11.1* (11 July, 2005)
-
-* Added support for limit and offset with eager loading of has_one and belongs_to associations. Using the options with has_many and has_and_belongs_to_many associations will now raise an ActiveRecord::ConfigurationError #1692 [Rick Olson]
-
-* Fixed that assume_bottom_position (in acts_as_list) could be called on items already last in the list and they would move one position away from the list #1648 [tyler@kianta.com]
-
-* Added ActiveRecord::Base.threaded_connections flag to turn off 1-connection per thread (required for thread safety). By default it's on, but WEBrick in Rails need it off #1685 [Sam Stephenson]
-
-* Correct reflected table name for singular associations. #1688 [court3nay]
-
-* Fixed optimistic locking with SQL Server #1660 [tom@popdog.net]
-
-* Added ActiveRecord::Migrator.migrate that can figure out whether to go up or down based on the target version and the current
-
-* Added better error message for "packets out of order" #1630 [court3nay]
-
-* Fixed first run of "rake migrate" on PostgreSQL by not expecting a return value on the id #1640
-
-
-*1.11.0* (6 July, 2005)
-
-* Fixed that Yaml error message in fixtures hid the real error #1623 [Nicholas Seckar]
-
-* Changed logging of SQL statements to use the DEBUG level instead of INFO
-
-* Added new Migrations framework for describing schema transformations in a way that can be easily applied across multiple databases #1604 [Tobias Lütke] See documentation under ActiveRecord::Migration and the additional support in the Rails rakefile/generator.
-
-* Added callback hooks to association collections #1549 [Florian Weber]. Example:
-
- class Project
- has_and_belongs_to_many :developers, :before_add => :evaluate_velocity
-
- def evaluate_velocity(developer)
- ...
- end
- end
-
- ..raising an exception will cause the object not to be added (or removed, with before_remove).
-
-
-* Fixed Base.content_columns call for SQL Server adapter #1450 [DeLynn Berry]
-
-* Fixed Base#write_attribute to work with both symbols and strings #1190 [Paul Legato]
-
-* Fixed that has_and_belongs_to_many didn't respect single table inheritance types #1081 [Florian Weber]
-
-* Speed up ActiveRecord#method_missing for the common case (read_attribute).
-
-* Only notify observers on after_find and after_initialize if these methods are defined on the model. #1235 [Stefan Kaes]
-
-* Fixed that single-table inheritance sub-classes couldn't be used to limit the result set with eager loading #1215 [Chris McGrath]
-
-* Fixed validates_numericality_of to work with overrided getter-method when :allow_nil is on #1316 [raidel@onemail.at]
-
-* Added roots, root, and siblings to the batch of methods added by acts_as_tree #1541 [Michael Schuerig]
-
-* Added support for limit/offset with the MS SQL Server driver so that pagination will now work #1569 [DeLynn Berry]
-
-* Added support for ODBC connections to MS SQL Server so you can connect from a non-Windows machine #1569 [Mark Imbriaco/DeLynn Berry]
-
-* Fixed that multiparameter posts ignored attr_protected #1532 [alec+rails@veryclever.net]
-
-* Fixed problem with eager loading when using a has_and_belongs_to_many association using :association_foreign_key #1504 [flash@vanklinkenbergsoftware.nl]
-
-* Fixed Base#find to honor the documentation on how :joins work and make them consistent with Base#count #1405 [pritchie@gmail.com]. What used to be:
-
- Developer.find :all, :joins => 'developers_projects', :conditions => 'id=developer_id AND project_id=1'
-
- ...should instead be:
-
- Developer.find(
- :all,
- :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
- :conditions => 'project_id=1'
- )
-
-* Fixed that validations didn't respecting custom setting for too_short, too_long messages #1437 [Marcel Molina Jr.]
-
-* Fixed that clear_association_cache doesn't delete new associations on new records (so you can safely place new records in the session with Action Pack without having new associations wiped) #1494 [cluon]
-
-* Fixed that calling Model.find([]) returns [] and doesn't throw an exception #1379
-
-* Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 [Alisdair McDiarmid]
-
-* Fixed saving of in-memory association structures to happen as a after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice
-
-* Allow any Enumerable, not just Array, to work as bind variables #1344 [Jeremy Kemper]
-
-* Added actual database-changing behavior to collection assigment for has_many and has_and_belongs_to_many #1425 [Sebastian Kanthak].
- Example:
-
- david.projects = [Project.find(1), Project.new("name" => "ActionWebSearch")]
- david.save
-
- If david.projects already contain the project with ID 1, this is left unchanged. Any other projects are dropped. And the new
- project is saved when david.save is called.
-
- Also included is a way to do assignments through IDs, which is perfect for checkbox updating, so you get to do:
-
- david.project_ids = [1, 5, 7]
-
-* Corrected typo in find SQL for has_and_belongs_to_many. #1312 [ben@bensinclair.com]
-
-* Fixed sanitized conditions for has_many finder method. #1281 [jackc@hylesanderson.com, pragdave, Tobias Lütke]
-
-* Comprehensive PostgreSQL schema support. Use the optional schema_search_path directive in database.yml to give a comma-separated list of schemas to search for your tables. This allows you, for example, to have tables in a shared schema without having to use a custom table name. See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html to learn more. #827 [dave@cherryville.org]
-
-* Corrected @@configurations typo #1410 [david@ruppconsulting.com]
-
-* Return PostgreSQL columns in the order they were declared #1374 [perlguy@gmail.com]
-
-* Allow before/after update hooks to work on models using optimistic locking
-
-* Eager loading of dependent has_one associations won't delete the association #1212
-
-* Added a second parameter to the build and create method for has_one that controls whether the existing association should be replaced (which means nullifying its foreign key as well). By default this is true, but false can be passed to prevent it.
-
-* Using transactional fixtures now causes the data to be loaded only once.
-
-* Added fixture accessor methods that can be used when instantiated fixtures are disabled.
-
- fixtures :web_sites
-
- def test_something
- assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
- end
-
-* Added DoubleRenderError exception that'll be raised if render* is called twice #518 [Nicholas Seckar]
-
-* Fixed exceptions occuring after render has been called #1096 [Nicholas Seckar]
-
-* CHANGED: validates_presence_of now uses Errors#add_on_blank, which will make " " fail the validation where it didn't before #1309
-
-* Added Errors#add_on_blank which works like Errors#add_on_empty, but uses Object#blank? instead
-
-* Added the :if option to all validations that can either use a block or a method pointer to determine whether the validation should be run or not. #1324 [Duane Johnson/jhosteny]. Examples:
-
- Conditional validations such as the following are made possible:
- validates_numericality_of :income, :if => :employed?
-
- Conditional validations can also solve the salted login generator problem:
- validates_confirmation_of :password, :if => :new_password?
-
- Using blocks:
- validates_presence_of :username, :if => Proc.new { |user| user.signup_step > 1 }
-
-* Fixed use of construct_finder_sql when using :join #1288 [dwlt@dwlt.net]
-
-* Fixed that :delete_sql in has_and_belongs_to_many associations couldn't access record properties #1299 [Rick Olson]
-
-* Fixed that clone would break when an aggregate had the same name as one of its attributes #1307 [Jeremy Kemper]
-
-* Changed that destroying an object will only freeze the attributes hash, which keeps the object from having attributes changed (as that wouldn't make sense), but allows for the querying of associations after it has been destroyed.
-
-* Changed the callbacks such that observers are notified before the in-object callbacks are triggered. Without this change, it wasn't possible to act on the whole object in something like a before_destroy observer without having the objects own callbacks (like deleting associations) called first.
-
-* Added option for passing an array to the find_all version of the dynamic finders and have it evaluated as an IN fragment. Example:
-
- # SELECT * FROM topics WHERE title IN ('First', 'Second')
- Topic.find_all_by_title(["First", "Second"])
-
-* Added compatibility with camelCase column names for dynamic finders #533 [Dee Zsombor]
-
-* Fixed extraneous comma in count() function that made it not work with joins #1156 [Jarkko Laine/Dee Zsombor]
-
-* Fixed incompatibility with Base#find with an array of ids that would fail when using eager loading #1186 [Alisdair McDiarmid]
-
-* Fixed that validate_length_of lost :on option when :within was specified #1195 [jhosteny@mac.com]
-
-* Added encoding and min_messages options for PostgreSQL #1205 [Shugo Maeda]. Configuration example:
-
- development:
- adapter: postgresql
- database: rails_development
- host: localhost
- username: postgres
- password:
- encoding: UTF8
- min_messages: ERROR
-
-* Fixed acts_as_list where deleting an item that was removed from the list would ruin the positioning of other list items #1197 [Jamis Buck]
-
-* Added validates_exclusion_of as a negative of validates_inclusion_of
-
-* Optimized counting of has_many associations by setting the association to empty if the count is 0 so repeated calls doesn't trigger database calls
-
-
-*1.10.1* (20th April, 2005)
-
-* Fixed frivilous database queries being triggered with eager loading on empty associations and other things
-
-* Fixed order of loading in eager associations
-
-* Fixed stray comma when using eager loading and ordering together from has_many associations #1143
-
-
-*1.10.0* (19th April, 2005)
-
-* Added eager loading of associations as a way to solve the N+1 problem more gracefully without piggy-back queries. Example:
-
- for post in Post.find(:all, :limit => 100)
- puts "Post: " + post.title
- puts "Written by: " + post.author.name
- puts "Last comment on: " + post.comments.first.created_on
- end
-
- This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as:
-
- for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
-
- ...and the number of database queries needed is now 1.
-
-* Added new unified Base.find API and deprecated the use of find_first and find_all. See the documentation for Base.find. Examples:
-
- Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
- Person.find(1, 5, 6, :conditions => "administrator = 1", :order => "created_on DESC")
- Person.find(:first, :order => "created_on DESC", :offset => 5)
- Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
- Person.find(:all, :offset => 10, :limit => 10)
-
-* Added acts_as_nested_set #1000 [wschenk]. Introduction:
-
- This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with
- the added feature that you can select the children and all of it's descendants with
- a single query. A good use case for this is a threaded post system, where you want
- to display every reply to a comment without multiple selects.
-
-* Added Base.save! that attempts to save the record just like Base.save but will raise a RecordInvalid exception instead of returning false if the record is not valid [Dave Thomas]
-
-* Fixed PostgreSQL usage of fixtures with regards to public schemas and table names with dots #962 [gnuman1@gmail.com]
-
-* Fixed that fixtures were being deleted in the same order as inserts causing FK errors #890 [andrew.john.peters@gmail.com]
-
-* Fixed loading of fixtures in to be in the right order (or PostgreSQL would bark) #1047 [stephenh@chase3000.com]
-
-* Fixed page caching for non-vhost applications living underneath the root #1004 [Ben Schumacher]
-
-* Fixes a problem with the SQL Adapter which was resulting in IDENTITY_INSERT not being set to ON when it should be #1104 [adelle]
-
-* Added the option to specify the acceptance string in validates_acceptance_of #1106 [caleb@aei-tech.com]
-
-* Added insert_at(position) to acts_as_list #1083 [DeLynnB]
-
-* Removed the default order by id on has_and_belongs_to_many queries as it could kill performance on large sets (you can still specify by hand with :order)
-
-* Fixed that Base.silence should restore the old logger level when done, not just set it to DEBUG #1084 [yon@milliped.com]
-
-* Fixed boolean saving on Oracle #1093 [mparrish@pearware.org]
-
-* Moved build_association and create_association for has_one and belongs_to out of deprecation as they work when the association is nil unlike association.build and association.create, which require the association to be already in place #864
-
-* Added rollbacks of transactions if they're active as the dispatcher is killed gracefully (TERM signal) #1054 [Leon Bredt]
-
-* Added quoting of column names for fixtures #997 [jcfischer@gmail.com]
-
-* Fixed counter_sql when no records exist in database for PostgreSQL (would give error, not 0) #1039 [Caleb Tennis]
-
-* Fixed that benchmarking times for rendering included db runtimes #987 [Stefan Kaes]
-
-* Fixed boolean queries for t/f fields in PostgreSQL #995 [dave@cherryville.org]
-
-* Added that model.items.delete(child) will delete the child, not just set the foreign key to nil, if the child is dependent on the model #978 [Jeremy Kemper]
-
-* Fixed auto-stamping of dates (created_on/updated_on) for PostgreSQL #985 [dave@cherryville.org]
-
-* Fixed Base.silence/benchmark to only log if a logger has been configured #986 [Stefan Kaes]
-
-* Added a join parameter as the third argument to Base.find_first and as the second to Base.count #426, #988 [Stefan Kaes]
-
-* Fixed bug in Base#hash method that would treat records with the same string-based id as different [Dave Thomas]
-
-* Renamed DateHelper#distance_of_time_in_words_to_now to DateHelper#time_ago_in_words (old method name is still available as a deprecated alias)
-
-
-*1.9.1* (27th March, 2005)
-
-* Fixed that Active Record objects with float attribute could not be cloned #808
-
-* Fixed that MissingSourceFile's wasn't properly detected in production mode #925 [Nicholas Seckar]
-
-* Fixed that :counter_cache option would look for a line_items_count column for a LineItem object instead of lineitems_count
-
-* Fixed that AR exists?() would explode on postgresql if the passed id did not match the PK type #900 [Scott Barron]
-
-* Fixed the MS SQL adapter to work with the new limit/offset approach and with binary data (still suffering from 7KB limit, though) #901 [delynnb]
-
-
-*1.9.0* (22th March, 2005)
-
-* Added adapter independent limit clause as a two-element array with the first being the limit, the second being the offset #795 [Sam Stephenson]. Example:
-
- Developer.find_all nil, 'id ASC', 5 # return the first five developers
- Developer.find_all nil, 'id ASC', [3, 8] # return three developers, starting from #8 and forward
-
- This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
-
-* Added alias_method :to_param, :id to Base, such that Active Record objects to be used as URL parameters in Action Pack automatically #812 [Nicholas Seckar/Sam Stephenson]
-
-* Improved the performance of the OCI8 adapter for Oracle #723 [pilx/gjenkins]
-
-* Added type conversion before saving a record, so string-based values like "10.0" aren't left for the database to convert #820 [dave@cherryville.org]
-
-* Added with additional settings for working with transactional fixtures and pre-loaded test databases #865 [mindel]
-
-* Fixed acts_as_list to trigger remove_from_list on destroy after the fact, not before, so a unique position can be maintained #871 [Alisdair McDiarmid]
-
-* Added the possibility of specifying fixtures in multiple calls #816 [kim@tinker.com]
-
-* Added Base.exists?(id) that'll return true if an object of the class with the given id exists #854 [stian@grytoyr.net]
-
-* Added optionally allow for nil or empty strings with validates_numericality_of #801 [Sebastian Kanthak]
-
-* Fixed problem with using slashes in validates_format_of regular expressions #801 [Sebastian Kanthak]
-
-* Fixed that SQLite3 exceptions are caught and reported properly #823 [yerejm]
-
-* Added that all types of after_find/after_initialized callbacks are triggered if the explicit implementation is present, not only the explicit implementation itself
-
-* Fixed that symbols can be used on attribute assignment, like page.emails.create(:subject => data.subject, :body => data.body)
-
-
-*1.8.0* (7th March, 2005)
-
-* Added ActiveRecord::Base.colorize_logging to control whether to use colors in logs or not (on by default)
-
-* Added support for timestamp with time zone in PostgreSQL #560 [Scott Barron]
-
-* Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius]. Documentation:
-
- * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
- +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
- objects that should be inspected to determine which attributes triggered the errors.
- * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
- You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
-
-* Fixed that postgresql adapter would fails when reading bytea fields with null value #771 [rodrigo k]
-
-* Added transactional fixtures that uses rollback to undo changes to fixtures instead of DELETE/INSERT -- it's much faster. See documentation under Fixtures #760 [Jeremy Kemper]
-
-* Added destruction of dependent objects in has_one associations when a new assignment happens #742 [mindel]. Example:
-
- class Account < ActiveRecord::Base
- has_one :credit_card, :dependent => true
- end
- class CreditCard < ActiveRecord::Base
- belongs_to :account
- end
-
- account.credit_card # => returns existing credit card, lets say id = 12
- account.credit_card = CreditCard.create("number" => "123")
- account.save # => CC with id = 12 is destroyed
-
-
-* Added validates_numericality_of #716 [Sebastian Kanthak/Chris McGrath]. Docuemntation:
-
- Validates whether the value of the specified attribute is numeric by trying to convert it to
- a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
- <tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true).
-
- class Person < ActiveRecord::Base
- validates_numericality_of :value, :on => :create
- end
-
- Configuration options:
- * <tt>message</tt> - A custom error message (default is: "is not a number")
- * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
- * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
-
-
-* Fixed that HasManyAssociation#count was using :finder_sql rather than :counter_sql if it was available #445 [Scott Barron]
-
-* Added better defaults for composed_of, so statements like composed_of :time_zone, :mapping => %w( time_zone time_zone ) can be written without the mapping part (it's now assumed)
-
-* Added MacroReflection#macro which will return a symbol describing the macro used (like :composed_of or :has_many) #718, #248 [james@slashetc.com]
-
-
-*1.7.0* (24th February, 2005)
-
-* Changed the auto-timestamping feature to use ActiveRecord::Base.default_timezone instead of entertaining the parallel ActiveRecord::Base.timestamps_gmt method. The latter is now deprecated and will throw a warning on use (but still work) #710 [Jamis Buck]
-
-* Added a OCI8-based Oracle adapter that has been verified to work with Oracle 8 and 9 #629 [Graham Jenkins]. Usage notes:
-
- 1. Key generation uses a sequence "rails_sequence" for all tables. (I couldn't find a simple
- and safe way of passing table-specific sequence information to the adapter.)
- 2. Oracle uses DATE or TIMESTAMP datatypes for both dates and times. Consequently I have had to
- resort to some hacks to get data converted to Date or Time in Ruby.
- If the column_name ends in _at (like created_at, updated_at) it's created as a Ruby Time. Else if the
- hours/minutes/seconds are 0, I make it a Ruby Date. Else it's a Ruby Time.
- This is nasty - but if you use Duck Typing you'll probably not care very much.
- In 9i it's tempting to map DATE to Date and TIMESTAMP to Time but I don't think that is
- valid - too many databases use DATE for both.
- Timezones and sub-second precision on timestamps are not supported.
- 3. Default values that are functions (such as "SYSDATE") are not supported. This is a
- restriction of the way active record supports default values.
- 4. Referential integrity constraints are not fully supported. Under at least
- some circumstances, active record appears to delete parent and child records out of
- sequence and out of transaction scope. (Or this may just be a problem of test setup.)
-
- The OCI8 driver can be retrieved from http://rubyforge.org/projects/ruby-oci8/
-
-* Added option :schema_order to the PostgreSQL adapter to support the use of multiple schemas per database #697 [YuriSchimke]
-
-* Optimized the SQL used to generate has_and_belongs_to_many queries by listing the join table first #693 [yerejm]
-
-* Fixed that when using validation macros with a custom message, if you happened to use single quotes in the message string you would get a parsing error #657 [tonka]
-
-* Fixed that Active Record would throw Broken Pipe errors with FCGI when the MySQL connection timed out instead of reconnecting #428 [Nicholas Seckar]
-
-* Added options to specify an SSL connection for MySQL. Define the following attributes in the connection config (config/database.yml in Rails) to use it: sslkey, sslcert, sslca, sslcapath, sslcipher. To use SSL with no client certs, just set :sslca = '/dev/null'. http://dev.mysql.com/doc/mysql/en/secure-connections.html #604 [daniel@nightrunner.com]
-
-* Added automatic dropping/creating of test tables for running the unit tests on all databases #587 [adelle@bullet.net.au]
-
-* Fixed that find_by_* would fail when column names had numbers #670 [demetrius]
-
-* Fixed the SQL Server adapter on a bunch of issues #667 [DeLynn]
-
- 1. Created a new columns method that is much cleaner.
- 2. Corrected a problem with the select and select_all methods
- that didn't account for the LIMIT clause being passed into raw SQL statements.
- 3. Implemented the string_to_time method in order to create proper instances of the time class.
- 4. Added logic to the simplified_type method that allows the database to specify the scale of float data.
- 5. Adjusted the quote_column_name to account for the fact that MS SQL is bothered by a forward slash in the data string.
-
-* Fixed that the dynamic finder like find_all_by_something_boolean(false) didn't work #649 [lmarlow]
-
-* Added validates_each that validates each specified attribute against a block #610 [Jeremy Kemper]. Example:
-
- class Person < ActiveRecord::Base
- validates_each :first_name, :last_name do |record, attr|
- record.errors.add attr, 'starts with z.' if attr[0] == ?z
- end
- end
-
-* Added :allow_nil as an explicit option for validates_length_of, so unless that's set to true having the attribute as nil will also return an error if a range is specified as :within #610 [Jeremy Kemper]
-
-* Added that validates_* now accept blocks to perform validations #618 [Tim Bates]. Example:
-
- class Person < ActiveRecord::Base
- validate { |person| person.errors.add("title", "will never be valid") if SHOULD_NEVER_BE_VALID }
- end
-
-* Addded validation for validate all the associated objects before declaring failure with validates_associated #618 [Tim Bates]
-
-* Added keyword-style approach to defining the custom relational bindings #545 [Jamis Buck]. Example:
-
- class Project < ActiveRecord::Base
- primary_key "sysid"
- table_name "XYZ_PROJECT"
- inheritance_column { original_inheritance_column + "_id" }
- end
-
-* Fixed Base#clone for use with PostgreSQL #565 [hanson@surgery.wisc.edu]
-
-
-*1.6.0* (January 25th, 2005)
-
-* Added that has_many association build and create methods can take arrays of record data like Base#create and Base#build to build/create multiple records at once.
-
-* Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336
-
-* Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once.
-
-* Added the option of supplying an array of ids and attributes to Base#update, so that multiple records can be updated at once (inspired by #526/Duane Johnson). Example
-
- people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
- Person.update(people.keys, people.values)
-
-* Added ActiveRecord::Base.timestamps_gmt that can be set to true to make the automated timestamping use GMT instead of local time #520 [Scott Baron]
-
-* Added that update_all calls sanitize_sql on its updates argument, so stuff like MyRecord.update_all(['time = ?', Time.now]) works #519 [notahat]
-
-* Fixed that the dynamic finders didn't treat nil as a "IS NULL" but rather "= NULL" case #515 [Demetrius]
-
-* Added bind-named arrays for interpolating a group of ids or strings in conditions #528 [Jeremy Kemper]
-
-* Added that has_and_belongs_to_many associations with additional attributes also can be created between unsaved objects and only committed to the database when Base#save is called on the associator #524 [Eric Anderson]
-
-* Fixed that records fetched with piggy-back attributes or through rich has_and_belongs_to_many associations couldn't be saved due to the extra attributes not part of the table #522 [Eric Anderson]
-
-* Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not
-
-* Fixed that association proxies would fail === tests like PremiumSubscription === @account.subscription
-
-* Fixed that column aliases didn't work as expected with the new MySql411 driver #507 [Demetrius]
-
-* Fixed that find_all would produce invalid sql when called sequentialy #490 [Scott Baron]
-
-
-*1.5.1* (January 18th, 2005)
-
-* Fixed that the belongs_to and has_one proxy would fail a test like 'if project.manager' -- this unfortunately also means that you can't call methods like project.manager.build unless there already is a manager on the project #492 [Tim Bates]
-
-* Fixed that the Ruby/MySQL adapter wouldn't connect if the password was empty #503 [Pelle]
-
-
-*1.5.0* (January 17th, 2005)
-
-* Fixed that unit tests for MySQL are now run as the "rails" user instead of root #455 [Eric Hodel]
-
-* Added validates_associated that enables validation of objects in an unsaved association #398 [Tim Bates]. Example:
-
- class Book < ActiveRecord::Base
- has_many :pages
- belongs_to :library
-
- validates_associated :pages, :library
- end
-
-* Added support for associating unsaved objects #402 [Tim Bates]. Rules that govern this addition:
-
- == Unsaved objects and associations
-
- You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
- aware of, mostly involving the saving of associated objects.
-
- === One-to-one associations
-
- * Assigning an object to a has_one association automatically saves that object, and the object being replaced (if there is one), in
- order to update their primary keys - except if the parent object is unsaved (new_record? == true).
- * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
- is cancelled.
- * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
- * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
- not save the parent either.
-
- === Collections
-
- * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
- (the owner of the collection) is not yet stored in the database.
- * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
- * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
- * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
-
-* Added replace to associations, so you can do project.manager.replace(new_manager) or project.milestones.replace(new_milestones) #402 [Tim Bates]
-
-* Added build and create methods to has_one and belongs_to associations, so you can now do project.manager.build(attributes) #402 [Tim Bates]
-
-* Added that if a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last. #402 [Tim Bates]
-
-* Fixed that Base#== wouldn't work for multiple references to the same unsaved object #402 [Tim Bates]
-
-* Fixed binary support for PostgreSQL #444 [alex@byzantine.no]
-
-* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
- collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
- it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
- it'll take one less SELECT query if you use length.
-
-* Added Base#attributes that returns a hash of all the attributes with their names as keys and clones of their objects as values #433 [atyp.de]
-
-* Fixed that foreign keys named the same as the association would cause stack overflow #437 [Eric Anderson]
-
-* Fixed default scope of acts_as_list from "1" to "1 = 1", so it'll work in PostgreSQL (among other places) #427 [Alexey]
-
-* Added Base#reload that reloads the attributes of an object from the database #422 [Andreas Schwarz]
-
-* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 [Jeremy Kemper]
-
-* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower]
-
-* Added that Observers can use the observes class method instead of overwriting self.observed_class().
-
- Before:
- class ListSweeper < ActiveRecord::Base
- def self.observed_class() [ List, Item ]
- end
-
- After:
- class ListSweeper < ActiveRecord::Base
- observes List, Item
- end
-
-* Fixed that conditions in has_many and has_and_belongs_to_many should be interpolated just like the finder_sql is
-
-* Fixed Base#update_attribute to be indifferent to whether a string or symbol is used to describe the name
-
-* Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it easier to flip a switch or flag.
-
- Before: topic.update_attribute(:approved, !approved?)
- After : topic.toggle!(:approved)
-
-* Added Base#increment!(attribute) and Base#decrement!(attribute) that also saves the records. Example:
-
- page.views # => 1
- page.increment!(:views) # executes an UPDATE statement
- page.views # => 2
-
- page.increment(:views).increment!(:views)
- page.views # => 4
-
-* Added Base#increment(attribute) and Base#decrement(attribute) that encapsulates the += 1 and -= 1 patterns.
-
-
-*1.4.0* (January 4th, 2005)
-
-* Added automated optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
- record increments the lock_version column and the locking facilities ensure that records instantiated twice
- will let the last one saved raise a StaleObjectError if the first was also updated. Example:
-
- p1 = Person.find(1)
- p2 = Person.find(1)
-
- p1.first_name = "Michael"
- p1.save
-
- p2.first_name = "should fail"
- p2.save # Raises a ActiveRecord::StaleObjectError
-
- You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
- or otherwise apply the business logic needed to resolve the conflict.
-
- #384 [Michael Koziarski]
-
-* Added dynamic attribute-based finders as a cleaner way of getting objects by simple queries without turning to SQL.
- They work by appending the name of an attribute to <tt>find_by_</tt>, so you get finders like <tt>Person.find_by_user_name,
- Payment.find_by_transaction_id</tt>. So instead of writing <tt>Person.find_first(["user_name = ?", user_name])</tt>, you just do
- <tt>Person.find_by_user_name(user_name)</tt>.
-
- It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
- <tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
- <tt>Person.find_first(["user_name = ? AND password = ?", user_name, password])</tt>, you just do
- <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
-
- While primarily a construct for easier find_firsts, it can also be used as a construct for find_all by using calls like
- <tt>Payment.find_all_by_amount(50)</tt> that is turned into <tt>Payment.find_all(["amount = ?", 50])</tt>. This is something not as equally useful,
- though, as it's not possible to specify the order in which the objects are returned.
-
-* Added block-style for callbacks #332 [Jeremy Kemper].
-
- Before:
- before_destroy(Proc.new{ |record| Person.destroy_all "firm_id = #{record.id}" })
-
- After:
- before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
-
-* Added :counter_cache option to acts_as_tree that works just like the one you can define on belongs_to #371 [Josh Peek]
-
-* Added Base.default_timezone accessor that determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates
- and times from the database. This is set to :local by default.
-
-* Added the possibility for adapters to overwrite add_limit! to implement a different limiting scheme than "LIMIT X" used by MySQL, PostgreSQL, and SQLite.
-
-* Added the possibility of having objects with acts_as_list created before their scope is available or...
-
-* Added a db2 adapter that only depends on the Ruby/DB2 bindings (http://raa.ruby-lang.org/project/ruby-db2/) #386 [Maik Schmidt]
-
-* Added the final touches to the Microsoft SQL Server adapter by Joey Gibson that makes it suitable for actual use #394 [DeLynn Barry]
-
-* Added that Base#find takes an optional options hash, including :conditions. Base#find_on_conditions deprecated in favor of #find with :conditions #407 [Jeremy Kemper]
-
-* Added HasManyAssociation#count that works like Base#count #413 [intinig]
-
-* Fixed handling of binary content in blobs and similar fields for Ruby/MySQL and SQLite #409 [xal]
-
-* Fixed a bug in the Ruby/MySQL that caused binary content to be escaped badly and come back mangled #405 [Tobias Lütke]
-
-* Fixed that the const_missing autoload assumes the requested constant is set by require_association and calls const_get to retrieve it.
- If require_association did not set the constant then const_get will call const_missing, resulting in an infinite loop #380 [Jeremy Kemper]
-
-* Fixed broken transactions that were actually only running object-level and not db level transactions [andreas]
-
-* Fixed that validates_uniqueness_of used 'id' instead of defined primary key #406
-
-* Fixed that the overwritten respond_to? method didn't take two parameters like the original #391
-
-* Fixed quoting in validates_format_of that would allow some rules to pass regardless of input #390 [Dmitry V. Sabanin]
-
-
-*1.3.0* (December 23, 2004)
-
-* Added a require_association hook on const_missing that makes it possible to use any model class without requiring it first. This makes STI look like:
-
- before:
- require_association 'person'
- class Employee < Person
- end
-
- after:
- class Employee < Person
- end
-
- This also reduces the usefulness of Controller.model in Action Pack to currently only being for documentation purposes.
-
-* Added that Base.update_all and Base.delete_all return an integer of the number of affected rows #341
-
-* Added scope option to validation_uniqueness #349 [Kent Sibilev]
-
-* Added respondence to *_before_type_cast for all attributes to return their string-state before they were type casted by the column type.
- This is helpful for getting "100,000" back on a integer-based validation where the value would normally be "100".
-
-* Added allow_nil options to validates_inclusion_of so that validation is only triggered if the attribute is not nil [what-a-day]
-
-* Added work-around for PostgreSQL and the problem of getting fixtures to be created from id 1 on each test case.
- This only works for auto-incrementing primary keys called "id" for now #359 [Scott Baron]
-
-* Added Base#clear_association_cache to empty all the cached associations #347 [Tobias Lütke]
-
-* Added more informative exceptions in establish_connection #356 [Jeremy Kemper]
-
-* Added Base#update_attributes that'll accept a hash of attributes and save the record (returning true if it passed validation, false otherwise).
-
- Before:
- person.attributes = @params["person"]
- person.save
-
- Now:
- person.update_attributes(@params["person"])
-
-* Added Base.destroy and Base.delete to remove records without holding a reference to them first.
-
-* Added that query benchmarking will only happen if its going to be logged anyway #344
-
-* Added higher_item and lower_item as public methods for acts_as_list #342 [Tobias Lütke]
-
-* Fixed that options[:counter_sql] was overwritten with interpolated sql rather than original sql #355 [Jeremy Kemper]
-
-* Fixed that overriding an attribute's accessor would be disregarded by add_on_empty and add_on_boundary_breaking because they simply used
- the attributes[] hash instead of checking for @base.respond_to?(attr.to_s). [Marten]
-
-* Fixed that Base.table_name would expect a parameter when used in has_and_belongs_to_many joins [Anna Lissa Cruz]
-
-* Fixed that nested transactions now work by letting the outer most transaction have the responsibilty of starting and rolling back the transaction.
- If any of the inner transactions swallow the exception raised, though, the transaction will not be rolled back. So always let the transaction
- bubble up even when you've dealt with local issues. Closes #231 and #340.
-
-* Fixed validates_{confirmation,acceptance}_of to only happen when the virtual attributes are not nil #348 [dpiddy@gmail.com]
-
-* Changed the interface on AbstractAdapter to require that adapters return the number of affected rows on delete and update operations.
-
-* Fixed the automated timestamping feature when running under Rails' development environment that resets the inheritable attributes on each request.
-
-
-
-*1.2.0*
-
-* Added Base.validates_inclusion_of that validates whether the value of the specified attribute is available in a particular enumerable
- object. [what-a-day]
-
- class Person < ActiveRecord::Base
- validates_inclusion_of :gender, :in=>%w( m f ), :message=>"woah! what are you then!??!!"
- validates_inclusion_of :age, :in=>0..99
- end
-
-* Added acts_as_list that can decorates an existing class with methods like move_higher/lower, move_to_top/bottom. [Tobias Lütke] Example:
-
- class TodoItem < ActiveRecord::Base
- acts_as_list :scope => :todo_list_id
- belongs_to :todo_list
- end
-
-* Added acts_as_tree that can decorates an existing class with a many to many relationship with itself. Perfect for categories in
- categories and the likes. [Tobias Lütke]
-
-* Added that Active Records will automatically record creation and/or update timestamps of database objects if fields of the names
- created_at/created_on or updated_at/updated_on are present. [Tobias Lütke]
-
-* Added Base.default_error_messages as a hash of all the error messages used in the validates_*_of so they can be changed in one place [Tobias Lütke]
-
-* Added automatic transaction block around AssociationCollection.<<, AssociationCollection.delete, and AssociationCollection.destroy_all
-
-* Fixed that Base#find will return an array if given an array -- regardless of the number of elements #270 [Marten]
-
-* Fixed that has_and_belongs_to_many would generate bad sql when naming conventions differed from using vanilla "id" everywhere [RedTerror]
-
-* Added a better exception for when a type column is used in a table without the intention of triggering single-table inheritance. Example:
-
- ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'bad_class!'.
- This error is raised because the column 'type' is reserved for storing the class in case of inheritance.
- Please rename this column if you didn't intend it to be used for storing the inheritance class or
- overwrite Company.inheritance_column to use another column for that information.
-
-* Added that single-table inheritance will only kick in if the inheritance_column (by default "type") is present. Otherwise, inheritance won't
- have any magic side effects.
-
-* Added the possibility of marking fields as being in error without adding a message (using nil) to it that'll get displayed wth full_messages #208 [mjobin]
-
-* Fixed Base.errors to be indifferent as to whether strings or symbols are used. Examples:
-
- Before:
- errors.add(:name, "must be shorter") if name.size > 10
- errors.on(:name) # => "must be shorter"
- errors.on("name") # => nil
-
- After:
- errors.add(:name, "must be shorter") if name.size > 10
- errors.on(:name) # => "must be shorter"
- errors.on("name") # => "must be shorter"
-
-* Added Base.validates_format_of that Validates whether the value of the specified attribute is of the correct form by matching
- it against the regular expression provided. [Marcel Molina Jr.]
-
- class Person < ActiveRecord::Base
- validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/, :on => :create
- end
-
-* Added Base.validates_length_of that delegates to add_on_boundary_breaking #312 [Tobias Lütke]. Example:
-
- Validates that the specified attribute matches the length restrictions supplied in either:
-
- - configuration[:minimum]
- - configuration[:maximum]
- - configuration[:is]
- - configuration[:within] (aka. configuration[:in])
-
- Only one option can be used at a time.
-
- class Person < ActiveRecord::Base
- validates_length_of :first_name, :maximum=>30
- validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
- validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
- validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
- validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
- end
-
-* Added Base.validate_presence as an alternative to implementing validate and doing errors.add_on_empty yourself.
-
-* Added Base.validates_uniqueness_of that alidates whether the value of the specified attributes are unique across the system.
- Useful for making sure that only one user can be named "davidhh".
-
- class Person < ActiveRecord::Base
- validates_uniqueness_of :user_name
- end
-
- When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified
- attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
-
-
-* Added Base.validates_confirmation_of that encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
-
- Model:
- class Person < ActiveRecord::Base
- validates_confirmation_of :password
- end
-
- View:
- <%= password_field "person", "password" %>
- <%= password_field "person", "password_confirmation" %>
-
- The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
- It exists only as an in-memory variable for validating the password. This check is performed both on create and update.
-
-
-* Added Base.validates_acceptance_of that encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
-
- class Person < ActiveRecord::Base
- validates_acceptance_of :terms_of_service
- end
-
- The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed both on create and update.
-
- NOTE: The agreement is considered valid if it's set to the string "1". This makes it easy to relate it to an HTML checkbox.
-
-
-* Added validation macros to make the stackable just like the life cycle callbacks. Examples:
-
- class Person < ActiveRecord::Base
- validate { |record| record.errors.add("name", "too short") unless name.size > 10 }
- validate { |record| record.errors.add("name", "too long") unless name.size < 20 }
- validate_on_create :validate_password
-
- private
- def validate_password
- errors.add("password", "too short") unless password.size > 6
- end
- end
-
-* Added the option for sanitizing find_by_sql and the offset parts in regular finds [Sam Stephenson]. Examples:
-
- Project.find_all ["category = ?", category_name], "created ASC", ["? OFFSET ?", 15, 20]
- Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
-
-* Fixed value quoting in all generated SQL statements, so that integers are not surrounded in quotes and that all sanitation are happening
- through the database's own quoting routine. This should hopefully make it lots easier for new adapters that doesn't accept '1' for integer
- columns.
-
-* Fixed has_and_belongs_to_many guessing of foreign key so that keys are generated correctly for models like SomeVerySpecialClient
- [Florian Weber]
-
-* Added counter_sql option for has_many associations [Jeremy Kemper]. Documentation:
-
- <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
- specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
-
-* Fixed that methods wrapped in callbacks still return their original result #260 [Jeremy Kemper]
-
-* Fixed the Inflector to handle the movie/movies pair correctly #261 [Scott Baron]
-
-* Added named bind-style variable interpolation #281 [Michael Koziarski]. Example:
-
- Person.find(["id = :id and first_name = :first_name", { :id => 5, :first_name = "bob' or 1=1" }])
-
-* Added bind-style variable interpolation for the condition arrays that uses the adapter's quote method [Michael Koziarski]
-
- Before:
- find_first([ "user_name = '%s' AND password = '%s'", user_name, password ])]
- find_first([ "firm_id = %s", firm_id ])] # unsafe!
-
- After:
- find_first([ "user_name = ? AND password = ?", user_name, password ])]
- find_first([ "firm_id = ?", firm_id ])]
-
-* Added CSV format for fixtures #272 [what-a-day]. (See the new and expanded documentation on fixtures for more information)
-
-* Fixed fixtures using primary key fields called something else than "id" [dave]
-
-* Added proper handling of time fields that are turned into Time objects with the dummy date of 2000/1/1 [HariSeldon]
-
-* Added reverse order of deleting fixtures, so referential keys can be maintained #247 [Tim Bates]
-
-* Added relative path search for sqlite dbfiles in database.yml (if RAILS_ROOT is defined) #233 [Jeremy Kemper]
-
-* Added option to establish_connection where you'll be able to leave out the parameter to have it use the RAILS_ENV environment variable
-
-* Fixed problems with primary keys and postgresql sequences (#230) [Tim Bates]
-
-* Added reloading for associations under cached environments like FastCGI and mod_ruby. This makes it possible to use those environments for development.
- This is turned on by default, but can be turned off with ActiveRecord::Base.reload_dependencies = false in production environments.
-
- NOTE: This will only have an effect if you let the associations manage the requiring of model classes. All libraries loaded through
- require will be "forever" cached. You can, however, use ActiveRecord::Base.load_or_require("library") to get this behavior outside of the
- auto-loading associations.
-
-* Added ERB capabilities to the fixture files for dynamic fixture generation. You don't need to do anything, just include ERB blocks like:
-
- david:
- id: 1
- name: David
-
- jamis:
- id: 2
- name: Jamis
-
- <% for digit in 3..10 %>
- dev_<%= digit %>:
- id: <%= digit %>
- name: fixture_<%= digit %>
- <% end %>
-
-* Changed the yaml fixture searcher to look in the root of the fixtures directory, so when you before could have something like:
-
- fixtures/developers/fixtures.yaml
- fixtures/accounts/fixtures.yaml
-
- ...you now need to do:
-
- fixtures/developers.yaml
- fixtures/accounts.yaml
-
-* Changed the fixture format from:
-
- name: david
- data:
- id: 1
- name: David Heinemeier Hansson
- birthday: 1979-10-15
- profession: Systems development
- ---
- name: steve
- data:
- id: 2
- name: Steve Ross Kellock
- birthday: 1974-09-27
- profession: guy with keyboard
-
- ...to:
-
- david:
- id: 1
- name: David Heinemeier Hansson
- birthday: 1979-10-15
- profession: Systems development
-
- steve:
- id: 2
- name: Steve Ross Kellock
- birthday: 1974-09-27
- profession: guy with keyboard
-
- The change is NOT backwards compatible. Fixtures written in the old YAML style needs to be rewritten!
-
-* All associations will now attempt to require the classes that they associate to. Relieving the need for most explicit 'require' statements.
-
-
-*1.1.0* (34)
-
-* Added automatic fixture setup and instance variable availability. Fixtures can also be automatically
- instantiated in instance variables relating to their names using the following style:
-
- class FixturesTest < Test::Unit::TestCase
- fixtures :developers # you can add more with comma separation
-
- def test_developers
- assert_equal 3, @developers.size # the container for all the fixtures is automatically set
- assert_kind_of Developer, @david # works like @developers["david"].find
- assert_equal "David Heinemeier Hansson", @david.name
- end
- end
-
-* Added HasAndBelongsToManyAssociation#push_with_attributes(object, join_attributes) that can create associations in the join table with additional
- attributes. This is really useful when you have information that's only relevant to the join itself, such as a "added_on" column for an association
- between post and category. The added attributes will automatically be injected into objects retrieved through the association similar to the piggy-back
- approach:
-
- post.categories.push_with_attributes(category, :added_on => Date.today)
- post.categories.first.added_on # => Date.today
-
- NOTE: The categories table doesn't have a added_on column, it's the categories_post join table that does!
-
-* Fixed that :exclusively_dependent and :dependent can't be activated at the same time on has_many associations [Jeremy Kemper]
-
-* Fixed that database passwords couldn't be all numeric [Jeremy Kemper]
-
-* Fixed that calling id would create the instance variable for new_records preventing them from being saved correctly [Jeremy Kemper]
-
-* Added sanitization feature to HasManyAssociation#find_all so it works just like Base.find_all [Sam Stephenson/Jeremy Kemper]
-
-* Added that you can pass overlapping ids to find without getting duplicated records back [Jeremy Kemper]
-
-* Added that Base.benchmark returns the result of the block [Jeremy Kemper]
-
-* Fixed problem with unit tests on Windows with SQLite [paterno]
-
-* Fixed that quotes would break regular non-yaml fixtures [Dmitry Sabanin/daft]
-
-* Fixed fixtures on windows with line endings cause problems under unix / mac [Tobias Lütke]
-
-* Added HasAndBelongsToManyAssociation#find(id) that'll search inside the collection and find the object or record with that id
-
-* Added :conditions option to has_and_belongs_to_many that works just like the one on all the other associations
-
-* Added AssociationCollection#clear to remove all associations from has_many and has_and_belongs_to_many associations without destroying the records [geech]
-
-* Added type-checking and remove in 1-instead-of-N sql statements to AssociationCollection#delete [geech]
-
-* Added a return of self to AssociationCollection#<< so appending can be chained, like project << Milestone.create << Milestone.create [geech]
-
-* Added Base#hash and Base#eql? which means that all of the equality using features of array and other containers now works:
-
- [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
-
-* Added :uniq as an option to has_and_belongs_to_many which will automatically ensure that AssociateCollection#uniq is called
- before pulling records out of the association. This is especially useful for three-way (and above) has_and_belongs_to_many associations.
-
-* Added AssociateCollection#uniq which is especially useful for has_and_belongs_to_many associations that can include duplicates,
- which is common on associations that also use metadata. Usage: post.categories.uniq
-
-* Fixed respond_to? to use a subclass specific hash instead of an Active Record-wide one
-
-* Fixed has_and_belongs_to_many to treat associations between classes in modules properly [Florian Weber]
-
-* Added a NoMethod exception to be raised when query and writer methods are called for attributes that doesn't exist [geech]
-
-* Added a more robust version of Fixtures that throws meaningful errors when on formatting issues [geech]
-
-* Added Base#transaction as a compliment to Base.transaction for prettier use in instance methods [geech]
-
-* Improved the speed of respond_to? by placing the dynamic methods lookup table in a hash [geech]
-
-* Added that any additional fields added to the join table in a has_and_belongs_to_many association
- will be placed as attributes when pulling records out through has_and_belongs_to_many associations.
- This is helpful when have information about the association itself that you want available on retrival.
-
-* Added better loading exception catching and RubyGems retries to the database adapters [alexeyv]
-
-* Fixed bug with per-model transactions [daniel]
-
-* Fixed Base#transaction so that it returns the result of the last expression in the transaction block [alexeyv]
-
-* Added Fixture#find to find the record corresponding to the fixture id. The record
- class name is guessed by using Inflector#classify (also new) on the fixture directory name.
-
- Before: Document.find(@documents["first"]["id"])
- After : @documents["first"].find
-
-* Fixed that the table name part of column names ("TABLE.COLUMN") wasn't removed properly [Andreas Schwarz]
-
-* Fixed a bug with Base#size when a finder_sql was used that didn't capitalize SELECT and FROM [geech]
-
-* Fixed quoting problems on SQLite by adding quote_string to the AbstractAdapter that can be overwritten by the concrete
- adapters for a call to the dbm. [Andreas Schwarz]
-
-* Removed RubyGems backup strategy for requiring SQLite-adapter -- if people want to use gems, they're already doing it with AR.
-
-
-*1.0.0 (35)*
-
-* Added OO-style associations methods [Florian Weber]. Examples:
-
- Project#milestones_count => Project#milestones.size
- Project#build_to_milestones => Project#milestones.build
- Project#create_for_milestones => Project#milestones.create
- Project#find_in_milestones => Project#milestones.find
- Project#find_all_in_milestones => Project#milestones.find_all
-
-* Added serialize as a new class method to control when text attributes should be YAMLized or not. This means that automated
- serialization of hashes, arrays, and so on WILL NO LONGER HAPPEN (#10). You need to do something like this:
-
- class User < ActiveRecord::Base
- serialize :settings
- end
-
- This will assume that settings is a text column and will now YAMLize any object put in that attribute. You can also specify
- an optional :class_name option that'll raise an exception if a serialized object is retrieved as a descendant of a class not in
- the hierarchy. Example:
-
- class User < ActiveRecord::Base
- serialize :settings, :class_name => "Hash"
- end
-
- user = User.create("settings" => %w( one two three ))
- User.find(user.id).settings # => raises SerializationTypeMismatch
-
-* Added the option to connect to a different database for one model at a time. Just call establish_connection on the class
- you want to have connected to another database than Base. This will automatically also connect decendents of that class
- to the different database [Renald Buter].
-
-* Added transactional protection for Base#save. Validations can now check for values knowing that it happens in a transaction and callbacks
- can raise exceptions knowing that the save will be rolled back. [Suggested by Alexey Verkhovsky]
-
-* Added column name quoting so reserved words, such as "references", can be used as column names [Ryan Platte]
-
-* Added the possibility to chain the return of what happened inside a logged block [geech]:
-
- This now works:
- log { ... }.map { ... }
-
- Instead of doing:
- result = []
- log { result = ... }
- result.map { ... }
-
-* Added "socket" option for the MySQL adapter, so you can change it to something else than "/tmp/mysql.sock" [Anna Lissa Cruz]
-
-* Added respond_to? answers for all the attribute methods. So if Person has a name attribute retrieved from the table schema,
- person.respond_to? "name" will return true.
-
-* Added Base.benchmark which can be used to aggregate logging and benchmark, so you can measure and represent multiple statements in a single block.
- Usage (hides all the SQL calls for the individual actions and calculates total runtime for them all):
-
- Project.benchmark("Creating project") do
- project = Project.create("name" => "stuff")
- project.create_manager("name" => "David")
- project.milestones << Milestone.find_all
- end
-
-* Added logging of invalid SQL statements [Daniel Von Fange]
-
-* Added alias Errors#[] for Errors#on, so you can now say person.errors["name"] to retrieve the errors for name [Andreas Schwarz]
-
-* Added RubyGems require attempt if sqlite-ruby is not available through regular methods.
-
-* Added compatibility with 2.x series of sqlite-ruby drivers. [Jamis Buck]
-
-* Added type safety for association assignments, so a ActiveRecord::AssociationTypeMismatch will be raised if you attempt to
- assign an object that's not of the associated class. This cures the problem with nil giving id = 4 and fixnums giving id = 1 on
- mistaken association assignments. [Reported by Andreas Schwarz]
-
-* Added the option to keep many fixtures in one single YAML document [what-a-day]
-
-* Added the class method "inheritance_column" that can be overwritten to return the name of an alternative column than "type" for storing
- the type for inheritance hierarchies. [Dave Steinberg]
-
-* Added [] and []= as an alternative way to access attributes when the regular methods have been overwritten [Dave Steinberg]
-
-* Added the option to observer more than one class at the time by specifying observed_class as an array
-
-* Added auto-id propagation support for tables with arbitrary primary keys that have autogenerated sequences associated with them
- on PostgreSQL. [Dave Steinberg]
-
-* Changed that integer and floats set to "" through attributes= remain as NULL. This was especially a problem for scaffolding and postgresql. (#49)
-
-* Changed the MySQL Adapter to rely on MySQL for its defaults for socket, host, and port [Andreas Schwarz]
-
-* Changed ActionControllerError to decent from StandardError instead of Exception. It can now be caught by a generic rescue.
-
-* Changed class inheritable attributes to not use eval [Caio Chassot]
-
-* Changed Errors#add to now use "invalid" as the default message instead of true, which means full_messages work with those [Marcel Molina Jr.]
-
-* Fixed spelling on Base#add_on_boundry_breaking to Base#add_on_boundary_breaking (old naming still works) [Marcel Molina Jr.]
-
-* Fixed that entries in the has_and_belongs_to_many join table didn't get removed when an associated object was destroyed.
-
-* Fixed unnecessary calls to SET AUTOCOMMIT=0/1 for MySQL adapter [Andreas Schwarz]
-
-* Fixed PostgreSQL defaults are now handled gracefully [Dave Steinberg]
-
-* Fixed increment/decrement_counter are now atomic updates [Andreas Schwarz]
-
-* Fixed the problems the Inflector had turning Attachment into attuchments and Cases into Casis [radsaq/Florian Gross]
-
-* Fixed that cloned records would point attribute references on the parent object [Andreas Schwarz]
-
-* Fixed SQL for type call on inheritance hierarchies [Caio Chassot]
-
-* Fixed bug with typed inheritance [Florian Weber]
-
-* Fixed a bug where has_many collection_count wouldn't use the conditions specified for that association
-
-
-*0.9.5*
-
-* Expanded the table_name guessing rules immensely [Florian Green]. Documentation:
-
- Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
- directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
- to guess the table name from even when called on Reply. The guessing rules are as follows:
- * Class name ends in "x", "ch" or "ss": "es" is appended, so a Search class becomes a searches table.
- * Class name ends in "y" preceded by a consonant or "qu": The "y" is replaced with "ies",
- so a Category class becomes a categories table.
- * Class name ends in "fe": The "fe" is replaced with "ves", so a Wife class becomes a wives table.
- * Class name ends in "lf" or "rf": The "f" is replaced with "ves", so a Half class becomes a halves table.
- * Class name ends in "person": The "person" is replaced with "people", so a Salesperson class becomes a salespeople table.
- * Class name ends in "man": The "man" is replaced with "men", so a Spokesman class becomes a spokesmen table.
- * Class name ends in "sis": The "i" is replaced with an "e", so a Basis class becomes a bases table.
- * Class name ends in "tum" or "ium": The "um" is replaced with an "a", so a Datum class becomes a data table.
- * Class name ends in "child": The "child" is replaced with "children", so a NodeChild class becomes a node_children table.
- * Class name ends in an "s": No additional characters are added or removed.
- * Class name doesn't end in "s": An "s" is appended, so a Comment class becomes a comments table.
- * Class name with word compositions: Compositions are underscored, so CreditCard class becomes a credit_cards table.
- Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
- So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
-
- You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
- "mice" table. Example:
-
- class Mouse < ActiveRecord::Base
- def self.table_name() "mice" end
- end
-
- This conversion is now done through an external class called Inflector residing in lib/active_record/support/inflector.rb.
-
-* Added find_all_in_collection to has_many defined collections. Works like this:
-
- class Firm < ActiveRecord::Base
- has_many :clients
- end
-
- firm.id # => 1
- firm.find_all_in_clients "revenue > 1000" # SELECT * FROM clients WHERE firm_id = 1 AND revenue > 1000
-
- [Requested by Dave Thomas]
-
-* Fixed finders for inheritance hierarchies deeper than one level [Florian Weber]
-
-* Added add_on_boundry_breaking to errors to accompany add_on_empty as a default validation method. It's used like this:
-
- class Person < ActiveRecord::Base
- protected
- def validation
- errors.add_on_boundry_breaking "password", 3..20
- end
- end
-
- This will add an error to the tune of "is too short (minimum is 3 characters)" or "is too long (minimum is 20 characters)" if
- the password is outside the boundry. The messages can be changed by passing a third and forth parameter as message strings.
-
-* Implemented a clone method that works properly with AR. It returns a clone of the record that
- hasn't been assigned an id yet and is treated as a new record.
-
-* Allow for domain sockets in PostgreSQL by not assuming localhost when no host is specified [Scott Barron]
-
-* Fixed that bignums are saved properly instead of attempted to be YAMLized [Andreas Schwartz]
-
-* Fixed a bug in the GEM where the rdoc options weren't being passed according to spec [Chad Fowler]
-
-* Fixed a bug with the exclusively_dependent option for has_many
-
-
-*0.9.4*
-
-* Correctly guesses the primary key when the class is inside a module [Dave Steinberg].
-
-* Added [] and []= as alternatives to read_attribute and write_attribute [Dave Steinberg]
-
-* has_and_belongs_to_many now accepts an :order key to determine in which order the collection is returned [radsaq].
-
-* The ids passed to find and find_on_conditions are now automatically sanitized.
-
-* Added escaping of plings in YAML content.
-
-* Multi-parameter assigns where all the parameters are empty will now be set to nil instead of a new instance of their class.
-
-* Proper type within an inheritance hierarchy is now ensured already at object initialization (instead of first at create)
-
-
-*0.9.3*
-
-* Fixed bug with using a different primary key name together with has_and_belongs_to_many [Investigation by Scott]
-
-* Added :exclusively_dependent option to the has_many association macro. The doc reads:
-
- If set to true all the associated object are deleted in one SQL statement without having their
- before_destroy callback run. This should only be used on associations that depend solely on
- this class and don't need to do any clean-up in before_destroy. The upside is that it's much
- faster, especially if there's a counter_cache involved.
-
-* Added :port key to connection options, so the PostgreSQL and MySQL adapters can connect to a database server
- running on another port than the default.
-
-* Converted the new natural singleton methods that prevented AR objects from being saved by PStore
- (and hence be placed in a Rails session) to a module. [Florian Weber]
-
-* Fixed the use of floats (was broken since 0.9.0+)
-
-* Fixed PostgreSQL adapter so default values are displayed properly when used in conjunction with
- Action Pack scaffolding.
-
-* Fixed booleans support for PostgreSQL (use real true/false on boolean fields instead of 0/1 on tinyints) [radsaq]
-
-
-*0.9.2*
-
-* Added static method for instantly updating a record
-
-* Treat decimal and numeric as Ruby floats [Andreas Schwartz]
-
-* Treat chars as Ruby strings (fixes problem for Action Pack form helpers too)
-
-* Removed debugging output accidently left in (which would screw web applications)
-
-
-*0.9.1*
-
-* Added MIT license
-
-* Added natural object-style assignment for has_and_belongs_to_many associations. Consider the following model:
-
- class Event < ActiveRecord::Base
- has_one_and_belongs_to_many :sponsors
- end
-
- class Sponsor < ActiveRecord::Base
- has_one_and_belongs_to_many :sponsors
- end
-
- Earlier, you'd have to use synthetic methods for creating associations between two objects of the above class:
-
- roskilde_festival.add_to_sponsors(carlsberg)
- roskilde_festival.remove_from_sponsors(carlsberg)
-
- nike.add_to_events(world_cup)
- nike.remove_from_events(world_cup)
-
- Now you can use regular array-styled methods:
-
- roskilde_festival.sponsors << carlsberg
- roskilde_festival.sponsors.delete(carlsberg)
-
- nike.events << world_cup
- nike.events.delete(world_cup)
-
-* Added delete method for has_many associations. Using this will nullify an association between the has_many and the belonging
- object by setting the foreign key to null. Consider this model:
-
- class Post < ActiveRecord::Base
- has_many :comments
- end
-
- class Comment < ActiveRecord::Base
- belongs_to :post
- end
-
- You could do something like:
-
- funny_comment.has_post? # => true
- announcement.comments.delete(funny_comment)
- funny_comment.has_post? # => false
-
-
-*0.9.0*
-
-* Active Record is now thread safe! (So you can use it with Cerise and WEBrick applications)
- [Implementation idea by Michael Neumann, debugging assistance by Jamis Buck]
-
-* Improved performance by roughly 400% on a basic test case of pulling 100 records and querying one attribute.
- This brings the tax for using Active Record instead of "riding on the metal" (using MySQL-ruby C-driver directly) down to ~50%.
- Done by doing lazy type conversions and caching column information on the class-level.
-
-* Added callback objects and procs as options for implementing the target for callback macros.
-
-* Added "counter_cache" option to belongs_to that automates the usage of increment_counter and decrement_counter. Consider:
-
- class Post < ActiveRecord::Base
- has_many :comments
- end
-
- class Comment < ActiveRecord::Base
- belongs_to :post
- end
-
- Iterating over 100 posts like this:
-
- <% for post in @posts %>
- <%= post.title %> has <%= post.comments_count %> comments
- <% end %>
-
- Will generate 100 SQL count queries -- one for each call to post.comments_count. If you instead add a "comments_count" int column
- to the posts table and rewrite the comments association macro with:
-
- class Comment < ActiveRecord::Base
- belongs_to :post, :counter_cache => true
- end
-
- Those 100 SQL count queries will be reduced to zero. Beware that counter caching is only appropriate for objects that begin life
- with the object it's specified to belong with and is destroyed like that as well. Typically objects where you would also specify
- :dependent => true. If your objects switch from one belonging to another (like a post that can be move from one category to another),
- you'll have to manage the counter yourself.
-
-* Added natural object-style assignment for has_one and belongs_to associations. Consider the following model:
-
- class Project < ActiveRecord::Base
- has_one :manager
- end
-
- class Manager < ActiveRecord::Base
- belongs_to :project
- end
-
- Earlier, assignments would work like following regardless of which way the assignment told the best story:
-
- active_record.manager_id = david.id
-
- Now you can do it either from the belonging side:
-
- david.project = active_record
-
- ...or from the having side:
-
- active_record.manager = david
-
- If the assignment happens from the having side, the assigned object is automatically saved. So in the example above, the
- project_id attribute on david would be set to the id of active_record, then david would be saved.
-
-* Added natural object-style assignment for has_many associations [Florian Weber]. Consider the following model:
-
- class Project < ActiveRecord::Base
- has_many :milestones
- end
-
- class Milestone < ActiveRecord::Base
- belongs_to :project
- end
-
- Earlier, assignments would work like following regardless of which way the assignment told the best story:
-
- deadline.project_id = active_record.id
-
- Now you can do it either from the belonging side:
-
- deadline.project = active_record
-
- ...or from the having side:
-
- active_record.milestones << deadline
-
- The milestone is automatically saved with the new foreign key.
-
-* API CHANGE: Attributes for text (or blob or similar) columns will now have unknown classes stored using YAML instead of using
- to_s. (Known classes that won't be yamelized are: String, NilClass, TrueClass, FalseClass, Fixnum, Date, and Time).
- Likewise, data pulled out of text-based attributes will be attempted converged using Yaml if they have the "--- " header.
- This was primarily done to be enable the storage of hashes and arrays without wrapping them in aggregations, so now you can do:
-
- user = User.find(1)
- user.preferences = { "background" => "black", "display" => large }
- user.save
-
- User.find(1).preferences # => { "background" => "black", "display" => large }
-
- Please note that this method should only be used when you don't care about representing the object in proper columns in
- the database. A money object consisting of an amount and a currency is still a much better fit for a value object done through
- aggregations than this new option.
-
-* POSSIBLE CODE BREAKAGE: As a consequence of the lazy type conversions, it's a bad idea to reference the @attributes hash
- directly (it always was, but now it's paramount that you don't). If you do, you won't get the type conversion. So to implement
- new accessors for existing attributes, use read_attribute(attr_name) and write_attribute(attr_name, value) instead. Like this:
-
- class Song < ActiveRecord::Base
- # Uses an integer of seconds to hold the length of the song
-
- def length=(minutes)
- write_attribute("length", minutes * 60)
- end
-
- def length
- read_attribute("length") / 60
- end
- end
-
- The clever kid will notice that this opens a door to sidestep the automated type conversion by using @attributes directly.
- This is not recommended as read/write_attribute may be granted additional responsibilities in the future, but if you think
- you know what you're doing and aren't afraid of future consequences, this is an option.
-
-* Applied a few minor bug fixes reported by Daniel Von Fange.
-
-
-*0.8.4*
-
-_Reflection_
-
-* Added ActiveRecord::Reflection with a bunch of methods and classes for reflecting in aggregations and associations.
-
-* Added Base.columns and Base.content_columns which returns arrays of column description (type, default, etc) objects.
-
-* Added Base#attribute_names which returns an array of names for the attributes available on the object.
-
-* Added Base#column_for_attribute(name) which returns the column description object for the named attribute.
-
-
-_Misc_
-
-* Added multi-parameter assignment:
-
- # Instantiate objects for all attribute classes that needs more than one constructor parameter. This is done
- # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
- # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
- # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
- # parenteses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
- # s for String, and a for Array.
-
- This is incredibly useful for assigning dates from HTML drop-downs of month, year, and day.
-
-* Fixed bug with custom primary key column name and Base.find on multiple parameters.
-
-* Fixed bug with dependent option on has_one associations if there was no associated object.
-
-
-*0.8.3*
-
-_Transactions_
-
-* Added transactional protection for destroy (important for the new :dependent option) [Suggested by Carl Youngblood]
-
-* Fixed so transactions are ignored on MyISAM tables for MySQL (use InnoDB to get transactions)
-
-* Changed transactions so only exceptions will cause a rollback, not returned false.
-
-
-_Mapping_
-
-* Added support for non-integer primary keys [Aredridel/earlier work by Michael Neumann]
-
- User.find "jdoe"
- Product.find "PDKEY-INT-12"
-
-* Added option to specify naming method for primary key column. ActiveRecord::Base.primary_key_prefix_type can either
- be set to nil, :table_name, or :table_name_with_underscore. :table_name will assume that Product class has a primary key
- of "productid" and :table_name_with_underscore will assume "product_id". The default nil will just give "id".
-
-* Added an overwriteable primary_key method that'll instruct AR to the name of the
- id column [Aredridele/earlier work by Guan Yang]
-
- class Project < ActiveRecord::Base
- def self.primary_key() "project_id" end
- end
-
-* Fixed that Active Records can safely associate inside and out of modules.
-
- class MyApplication::Account < ActiveRecord::Base
- has_many :clients # will look for MyApplication::Client
- has_many :interests, :class_name => "Business::Interest" # will look for Business::Interest
- end
-
-* Fixed that Active Records can safely live inside modules [Aredridel]
-
- class MyApplication::Account < ActiveRecord::Base
- end
-
-
-_Misc_
-
-* Added freeze call to value object assignments to ensure they remain immutable [Spotted by Gavin Sinclair]
-
-* Changed interface for specifying observed class in observers. Was OBSERVED_CLASS constant, now is
- observed_class() class method. This is more consistant with things like self.table_name(). Works like this:
-
- class AuditObserver < ActiveRecord::Observer
- def self.observed_class() Account end
- def after_update(account)
- AuditTrail.new(account, "UPDATED")
- end
- end
-
- [Suggested by Gavin Sinclair]
-
-* Create new Active Record objects by setting the attributes through a block. Like this:
-
- person = Person.new do |p|
- p.name = 'Freddy'
- p.age = 19
- end
-
- [Suggested by Gavin Sinclair]
-
-
-*0.8.2*
-
-* Added inheritable callback queues that can ensure that certain callback methods or inline fragments are
- run throughout the entire inheritance hierarchy. Regardless of whether a descendant overwrites the callback
- method:
-
- class Topic < ActiveRecord::Base
- before_destroy :destroy_author, 'puts "I'm an inline fragment"'
- end
-
- Learn more in link:classes/ActiveRecord/Callbacks.html
-
-* Added :dependent option to has_many and has_one, which will automatically destroy associated objects when
- the holder is destroyed:
-
- class Album < ActiveRecord::Base
- has_many :tracks, :dependent => true
- end
-
- All the associated tracks are destroyed when the album is.
-
-* Added Base.create as a factory that'll create, save, and return a new object in one step.
-
-* Automatically convert strings in config hashes to symbols for the _connection methods. This allows you
- to pass the argument hashes directly from yaml. (Luke)
-
-* Fixed the install.rb to include simple.rb [Spotted by Kevin Bullock]
-
-* Modified block syntax to better follow our code standards outlined in
- http://www.rubyonrails.org/CodingStandards
-
-
-*0.8.1*
-
-* Added object-level transactions [Austin Ziegler]
-
-* Changed adapter-specific connection methods to use centralized ActiveRecord::Base.establish_connection,
- which is parametized through a config hash with symbol keys instead of a regular parameter list.
- This will allow for database connections to be opened in a more generic fashion. (Luke)
-
- NOTE: This requires all *_connections to be updated! Read more in:
- http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000081
-
-* Fixed SQLite adapter so objects fetched from has_and_belongs_to_many have proper attributes
- (t.name is now name). [Spotted by Garrett Rooney]
-
-* Fixed SQLite adapter so dates are returned as Date objects, not Time objects [Spotted by Gavin Sinclair]
-
-* Fixed requirement of date class, so date conversions are succesful regardless of whether you
- manually require date or not.
-
-
-*0.8.0*
-
-* Added transactions
-
-* Changed Base.find to also accept either a list (1, 5, 6) or an array of ids ([5, 7])
- as parameter and then return an array of objects instead of just an object
-
-* Fixed method has_collection? for has_and_belongs_to_many macro to behave as a
- collection, not an association
-
-* Fixed SQLite adapter so empty or nil values in columns of datetime, date, or time type
- aren't treated as current time [Spotted by Gavin Sinclair]
-
-
-*0.7.6*
-
-* Fixed the install.rb to create the lib/active_record/support directory [Spotted by Gavin Sinclair]
-* Fixed that has_association? would always return true [Daniel Von Fange]
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
new file mode 100644
index 0000000000..8f1f315e42
--- /dev/null
+++ b/activerecord/CHANGELOG.md
@@ -0,0 +1,7163 @@
+## Rails 4.0.0 (unreleased) ##
+
+* Deprecated most of the 'dynamic finder' methods. All dynamic methods
+ except for `find_by_...` and `find_by_...!` are deprecated. Here's
+ how you can rewrite the code:
+
+ * `find_all_by_...` can be rewritten using `where(...)`
+ * `find_last_by_...` can be rewritten using `where(...).last`
+ * `scoped_by_...` can be rewritten using `where(...)`
+ * `find_or_initialize_by_...` can be rewritten using
+ `where(...).first_or_initialize`
+ * `find_or_create_by_...` can be rewritten using
+ `where(...).first_or_create`
+ * `find_or_create_by_...!` can be rewritten using
+ `where(...).first_or_create!`
+
+ The implementation of the deprecated dynamic finders has been moved
+ to the `active_record_deprecated_finders` gem. See below for details.
+
+ *Jon Leighton*
+
+* Deprecated the old-style hash based finder API. This means that
+ methods which previously accepted "finder options" no longer do. For
+ example this:
+
+ Post.find(:all, :conditions => { :comments_count => 10 }, :limit => 5)
+
+ Should be rewritten in the new style which has existed since Rails 3:
+
+ Post.where(comments_count: 10).limit(5)
+
+ Note that as an interim step, it is possible to rewrite the above as:
+
+ Post.scoped(:where => { :comments_count => 10 }, :limit => 5)
+
+ This could save you a lot of work if there is a lot of old-style
+ finder usage in your application.
+
+ Calling `Post.scoped(options)` is a shortcut for
+ `Post.scoped.merge(options)`. `Relation#merge` now accepts a hash of
+ options, but they must be identical to the names of the equivalent
+ finder method. These are mostly identical to the old-style finder
+ option names, except in the following cases:
+
+ * `:conditions` becomes `:where`
+ * `:include` becomes `:includes`
+ * `:extend` becomes `:extending`
+
+ The code to implement the deprecated features has been moved out to
+ the `active_record_deprecated_finders` gem. This gem is a dependency
+ of Active Record in Rails 4.0. It will no longer be a dependency
+ from Rails 4.1, but if your app relies on the deprecated features
+ then you can add it to your own Gemfile. It will be maintained by
+ the Rails core team until Rails 5.0 is released.
+
+ *Jon Leighton*
+
+* It's not possible anymore to destroy a model marked as read only.
+
+ *Johannes Barre*
+
+* Added ability to ActiveRecord::Relation#from to accept other ActiveRecord::Relation objects
+
+ Record.from(subquery)
+ Record.from(subquery, :a)
+
+ *Radoslav Stankov*
+
+* Added custom coders support for ActiveRecord::Store. Now you can set
+ your custom coder like this:
+
+ store :settings, accessors: [ :color, :homepage ], coder: JSON
+
+ *Andrey Voronkov*
+
+* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by
+ default to avoid silent data loss. This can be disabled by specifying
+ `strict: false` in your `database.yml`.
+
+ *Michael Pearson*
+
+* Added default order to `first` to assure consistent results among
+ diferent database engines. Introduced `take` as a replacement to
+ the old behavior of `first`.
+
+ *Marcelo Silveira*
+
+* Added an :index option to automatically create indexes for references
+ and belongs_to statements in migrations.
+
+ The `references` and `belongs_to` methods now support an `index`
+ option that receives either a boolean value or an options hash
+ that is identical to options available to the add_index method:
+
+ create_table :messages do |t|
+ t.references :person, :index => true
+ end
+
+ Is the same as:
+
+ create_table :messages do |t|
+ t.references :person
+ end
+ add_index :messages, :person_id
+
+ Generators have also been updated to use the new syntax.
+
+ [Joshua Wood]
+
+* Added bang methods for mutating `ActiveRecord::Relation` objects.
+ For example, while `foo.where(:bar)` will return a new object
+ leaving `foo` unchanged, `foo.where!(:bar)` will mutate the foo
+ object
+
+ *Jon Leighton*
+
+* Added `#find_by` and `#find_by!` to mirror the functionality
+ provided by dynamic finders in a way that allows dynamic input more
+ easily:
+
+ Post.find_by name: 'Spartacus', rating: 4
+ Post.find_by "published_at < ?", 2.weeks.ago
+ Post.find_by! name: 'Spartacus'
+
+ *Jon Leighton*
+
+* Added ActiveRecord::Base#slice to return a hash of the given methods with
+ their names as keys and returned values as values.
+
+ *Guillermo Iguaran*
+
+* Deprecate eager-evaluated scopes.
+
+ Don't use this:
+
+ scope :red, where(color: 'red')
+ default_scope where(color: 'red')
+
+ Use this:
+
+ scope :red, -> { where(color: 'red') }
+ default_scope { where(color: 'red') }
+
+ The former has numerous issues. It is a common newbie gotcha to do
+ the following:
+
+ scope :recent, where(published_at: Time.now - 2.weeks)
+
+ Or a more subtle variant:
+
+ scope :recent, -> { where(published_at: Time.now - 2.weeks) }
+ scope :recent_red, recent.where(color: 'red')
+
+ Eager scopes are also very complex to implement within Active
+ Record, and there are still bugs. For example, the following does
+ not do what you expect:
+
+ scope :remove_conditions, except(:where)
+ where(...).remove_conditions # => still has conditions
+
+ *Jon Leighton*
+
+* Remove IdentityMap
+
+ IdentityMap has never graduated to be an "enabled-by-default" feature, due
+ to some inconsistencies with associations, as described in this commit:
+
+ https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6
+
+ Hence the removal from the codebase, until such issues are fixed.
+
+ *Carlos Antonio da Silva*
+
+* Added the schema cache dump feature.
+
+ `Schema cache dump` feature was implemetend. This feature can dump/load internal state of `SchemaCache` instance
+ because we want to boot rails more quickly when we have many models.
+
+ Usage notes:
+
+ 1) execute rake task.
+ RAILS_ENV=production bundle exec rake db:schema:cache:dump
+ => generate db/schema_cache.dump
+
+ 2) add config.use_schema_cache_dump = true in config/production.rb. BTW, true is default.
+
+ 3) boot rails.
+ RAILS_ENV=production bundle exec rails server
+ => use db/schema_cache.dump
+
+ 4) If you remove clear dumped cache, execute rake task.
+ RAILS_ENV=production bundle exec rake db:schema:cache:clear
+ => remove db/schema_cache.dump
+
+ *kennyj*
+
+* Added support for partial indices to PostgreSQL adapter
+
+ The `add_index` method now supports a `where` option that receives a
+ string with the partial index criteria.
+
+ add_index(:accounts, :code, :where => "active")
+
+ Generates
+
+ CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active
+
+ *Marcelo Silveira*
+
+* Implemented ActiveRecord::Relation#none method
+
+ The `none` method returns a chainable relation with zero records
+ (an instance of the NullRelation class).
+
+ Any subsequent condition chained to the returned relation will continue
+ generating an empty relation and will not fire any query to the database.
+
+ *Juanjo Bazán*
+
+* Added the `ActiveRecord::NullRelation` class implementing the null
+ object pattern for the Relation class. *Juanjo Bazán*
+
+* Added deprecation for the `:dependent => :restrict` association option.
+
+ Please note:
+
+ * Up until now `has_many` and `has_one`, `:dependent => :restrict`
+ option raised a `DeleteRestrictionError` at the time of destroying
+ the object. Instead, it will add an error on the model.
+
+ * To fix this warning, make sure your code isn't relying on a
+ `DeleteRestrictionError` and then add
+ `config.active_record.dependent_restrict_raises = false` to your
+ application config.
+
+ * New rails application would be generated with the
+ `config.active_record.dependent_restrict_raises = false` in the
+ application config.
+
+ *Manoj Kumar*
+
+* Added `create_join_table` migration helper to create HABTM join tables
+
+ create_join_table :products, :categories
+ # =>
+ # create_table :categories_products, :id => false do |td|
+ # td.integer :product_id, :null => false
+ # td.integer :category_id, :null => false
+ # end
+
+ *Rafael Mendonça França*
+
+* The primary key is always initialized in the @attributes hash to nil (unless
+ another value has been specified).
+
+* In previous releases, the following would generate a single query with
+ an `OUTER JOIN comments`, rather than two separate queries:
+
+ Post.includes(:comments)
+ .where("comments.name = 'foo'")
+
+ This behaviour relies on matching SQL string, which is an inherently
+ flawed idea unless we write an SQL parser, which we do not wish to
+ do.
+
+ Therefore, it is now deprecated.
+
+ To avoid deprecation warnings and for future compatibility, you must
+ explicitly state which tables you reference, when using SQL snippets:
+
+ Post.includes(:comments)
+ .where("comments.name = 'foo'")
+ .references(:comments)
+
+ Note that you do not need to explicitly specify references in the
+ following cases, as they can be automatically inferred:
+
+ Post.where(comments: { name: 'foo' })
+ Post.where('comments.name' => 'foo')
+ Post.order('comments.name')
+
+ You also do not need to worry about this unless you are doing eager
+ loading. Basically, don't worry unless you see a deprecation warning
+ or (in future releases) an SQL error due to a missing JOIN.
+
+ [Jon Leighton]
+
+* Support for the `schema_info` table has been dropped. Please
+ switch to `schema_migrations`.
+
+* Connections *must* be closed at the end of a thread. If not, your
+ connection pool can fill and an exception will be raised.
+
+* Added the `ActiveRecord::Model` module which can be included in a
+ class as an alternative to inheriting from `ActiveRecord::Base`:
+
+ class Post
+ include ActiveRecord::Model
+ end
+
+ Please note:
+
+ * Up until now it has been safe to assume that all AR models are
+ descendants of `ActiveRecord::Base`. This is no longer a safe
+ assumption, but it may transpire that there are areas of the
+ code which still make this assumption. So there may be
+ 'teething difficulties' with this feature. (But please do try it
+ and report bugs.)
+
+ * Plugins & libraries etc that add methods to `ActiveRecord::Base`
+ will not be compatible with `ActiveRecord::Model`. Those libraries
+ should add to `ActiveRecord::Model` instead (which is included in
+ `Base`), or better still, avoid monkey-patching AR and instead
+ provide a module that users can include where they need it.
+
+ * To minimise the risk of conflicts with other code, it is
+ advisable to include `ActiveRecord::Model` early in your class
+ definition.
+
+ *Jon Leighton*
+
+* PostgreSQL hstore records can be created.
+
+* PostgreSQL hstore types are automatically deserialized from the database.
+
+
+## Rails 3.2.3 (March 30, 2012) ##
+
+* Added find_or_create_by_{attribute}! dynamic method. *Andrew White*
+
+* Whitelist all attribute assignment by default. Change the default for newly generated applications to whitelist all attribute assignment. Also update the generated model classes so users are reminded of the importance of attr_accessible. *NZKoz*
+
+* Update ActiveRecord::AttributeMethods#attribute_present? to return false for empty strings. *Jacobkg*
+
+* Fix associations when using per class databases. *larskanis*
+
+* Revert setting NOT NULL constraints in add_timestamps *fxn*
+
+* Fix mysql to use proper text types. Fixes #3931. *kennyj*
+
+* Fix #5069 - Protect foreign key from mass assignment through association builder. *byroot*
+
+
+## Rails 3.2.2 (March 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* The threshold for auto EXPLAIN is ignored if there's no logger. *fxn*
+
+* Call `to_s` on the value passed to `table_name=`, in particular symbols
+ are supported (regression). *Sergey Nartimov*
+
+* Fix possible race condition when two threads try to define attribute
+ methods for the same class. *Jon Leighton*
+
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* Added a `with_lock` method to ActiveRecord objects, which starts
+ a transaction, locks the object (pessimistically) and yields to the block.
+ The method takes one (optional) parameter and passes it to `lock!`.
+
+ Before:
+
+ class Order < ActiveRecord::Base
+ def cancel!
+ transaction do
+ lock!
+ # ... cancelling logic
+ end
+ end
+ end
+
+ After:
+
+ class Order < ActiveRecord::Base
+ def cancel!
+ with_lock do
+ # ... cancelling logic
+ end
+ end
+ end
+
+ *Olek Janiszewski*
+
+* 'on' and 'ON' boolean columns values are type casted to true
+ *Santiago Pastorino*
+
+* Added ability to run migrations only for given scope, which allows
+ to run migrations only from one engine (for example to revert changes
+ from engine that you want to remove).
+
+ Example:
+ rake db:migrate SCOPE=blog
+
+ *Piotr Sarnacki*
+
+* Migrations copied from engines are now scoped with engine's name,
+ for example 01_create_posts.blog.rb. *Piotr Sarnacki*
+
+* Implements `AR::Base.silence_auto_explain`. This method allows the user to
+ selectively disable automatic EXPLAINs within a block. *fxn*
+
+* Implements automatic EXPLAIN logging for slow queries.
+
+ A new configuration parameter `config.active_record.auto_explain_threshold_in_seconds`
+ determines what's to be considered a slow query. Setting that to `nil` disables
+ this feature. Defaults are 0.5 in development mode, and `nil` in test and production
+ modes.
+
+ As of this writing there's support for SQLite, MySQL (mysql2 adapter), and
+ PostgreSQL.
+
+ *fxn*
+
+* Implemented ActiveRecord::Relation#pluck method
+
+ Method returns Array of column value from table under ActiveRecord model
+
+ Client.pluck(:id)
+
+ *Bogdan Gusiev*
+
+* Automatic closure of connections in threads is deprecated. For example
+ the following code is deprecated:
+
+ Thread.new { Post.find(1) }.join
+
+ It should be changed to close the database connection at the end of
+ the thread:
+
+ Thread.new {
+ Post.find(1)
+ Post.connection.close
+ }.join
+
+ Only people who spawn threads in their application code need to worry
+ about this change.
+
+* Deprecated:
+
+ * `set_table_name`
+ * `set_inheritance_column`
+ * `set_sequence_name`
+ * `set_primary_key`
+ * `set_locking_column`
+
+ Use an assignment method instead. For example, instead of `set_table_name`, use `self.table_name=`:
+
+ class Project < ActiveRecord::Base
+ self.table_name = "project"
+ end
+
+ Or define your own `self.table_name` method:
+
+ class Post < ActiveRecord::Base
+ def self.table_name
+ "special_" + super
+ end
+ end
+ Post.table_name # => "special_posts"
+
+ *Jon Leighton*
+
+* Generated association methods are created within a separate module to allow overriding and
+ composition using `super`. For a class named `MyModel`, the module is named
+ `MyModel::GeneratedFeatureMethods`. It is included into the model class immediately after
+ the `generated_attributes_methods` module defined in ActiveModel, so association methods
+ override attribute methods of the same name. *Josh Susser*
+
+* Implemented ActiveRecord::Relation#explain. *fxn*
+
+* Add ActiveRecord::Relation#uniq for generating unique queries.
+
+ Before:
+
+ Client.select('DISTINCT name')
+
+ After:
+
+ Client.select(:name).uniq
+
+ This also allows you to revert the uniqueness in a relation:
+
+ Client.select(:name).uniq.uniq(false)
+
+ *Jon Leighton*
+
+* Support index sort order in sqlite, mysql and postgres adapters. *Vlad Jebelev*
+
+* Allow the :class_name option for associations to take a symbol (:Client) in addition to
+ a string ('Client').
+
+ This is to avoid confusing newbies, and to be consistent with the fact that other options
+ like :foreign_key already allow a symbol or a string.
+
+ *Jon Leighton*
+
+* In development mode the db:drop task also drops the test database. For symmetry with
+ the db:create task. *Dmitriy Kiriyenko*
+
+* Added ActiveRecord::Base.store for declaring simple single-column key/value stores *DHH*
+
+ class User < ActiveRecord::Base
+ store :settings, accessors: [ :color, :homepage ]
+ end
+
+ u = User.new(color: 'black', homepage: '37signals.com')
+ u.color # Accessor stored attribute
+ u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
+
+
+* MySQL: case-insensitive uniqueness validation avoids calling LOWER when
+ the column already uses a case-insensitive collation. Fixes #561.
+
+ *Joseph Palermo*
+
+* Transactional fixtures enlist all active database connections. You can test
+ models on different connections without disabling transactional fixtures.
+
+ *Jeremy Kemper*
+
+* Add first_or_create, first_or_create!, first_or_initialize methods to Active Record. This is a
+ better approach over the old find_or_create_by dynamic methods because it's clearer which
+ arguments are used to find the record and which are used to create it:
+
+ User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson")
+
+ *Andrés Mejía*
+
+* Fix nested attributes bug where _destroy parameter is taken into account
+ during :reject_if => :all_blank (fixes #2937)
+
+ *Aaron Christy*
+
+
+## Rails 3.1.4 (March 1, 2012) ##
+
+ * Fix a custom primary key regression *GH 3987*
+
+ *Jon Leighton*
+
+ * Perf fix (second try): don't load records for `has many :dependent =>
+ :delete_all` *GH 3672*
+
+ *Jon Leighton*
+
+ * Fix accessing `proxy_association` method from an association extension
+ where the calls are chained. *GH #3890*
+
+ (E.g. `post.comments.where(bla).my_proxy_method`)
+
+ *Jon Leighton*
+
+ * Perf fix: MySQL primary key lookup was still slow for very large
+ tables. *GH 3678*
+
+ *Kenny J*
+
+ * Perf fix: If a table has no primary key, don't repeatedly ask the database for it.
+
+ *Julius de Bruijn*
+
+
+### Rails 3.1.3 (November 20, 2011) ##
+
+* Perf fix: If we're deleting all records in an association, don't add a IN(..) clause
+ to the query. *GH 3672*
+
+ *Jon Leighton*
+
+* Fix bug with referencing other mysql databases in set_table_name. *GH 3690*
+
+* Fix performance bug with mysql databases on a server with lots of other databses. *GH 3678*
+
+ *Christos Zisopoulos and Kenny J*
+
+
+### Rails 3.1.2 (November 18, 2011) ##
+
+* Fix bug with PostgreSQLAdapter#indexes. When the search path has multiple schemas, spaces
+ were not being stripped from the schema names after the first.
+
+ *Sean Kirby*
+
+* Preserve SELECT columns on the COUNT for finder_sql when possible. *GH 3503*
+
+ *Justin Mazzi*
+
+* Reset prepared statement cache when schema changes impact statement results. *GH 3335*
+
+ *Aaron Patterson*
+
+* Postgres: Do not attempt to deallocate a statement if the connection is no longer active.
+
+ *Ian Leitch*
+
+* Prevent QueryCache leaking database connections. *GH 3243*
+
+ *Mark J. Titorenko*
+
+* Fix bug where building the conditions of a nested through association could potentially
+ modify the conditions of the through and/or source association. If you have experienced
+ bugs with conditions appearing in the wrong queries when using nested through associations,
+ this probably solves your problems. *GH #3271*
+
+ *Jon Leighton*
+
+* If a record is removed from a has_many :through, all of the join records relating to that
+ record should also be removed from the through association's target.
+
+ *Jon Leighton*
+
+* Fix adding multiple instances of the same record to a has_many :through. *GH #3425*
+
+ *Jon Leighton*
+
+* Fix creating records in a through association with a polymorphic source type. *GH #3247*
+
+ *Jon Leighton*
+
+* MySQL: use the information_schema than the describe command when we look for a primary key. *GH #3440*
+
+ *Kenny J*
+
+
+## Rails 3.1.1 (October 7, 2011) ##
+
+* Add deprecation for the preload_associations method. Fixes #3022.
+
+ *Jon Leighton*
+
+* Don't require a DB connection when loading a model that uses set_primary_key. GH #2807.
+
+ *Jon Leighton*
+
+* Fix using select() with a habtm association, e.g. Person.friends.select(:name). GH #3030 and
+ \#2923.
+
+ *Hendy Tanata*
+
+* Fix belongs_to polymorphic with custom primary key on target. GH #3104.
+
+ *Jon Leighton*
+
+* CollectionProxy#replace should change the DB records rather than just mutating the array.
+ Fixes #3020.
+
+ *Jon Leighton*
+
+* LRU cache in mysql and sqlite are now per-process caches.
+
+ * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache keys are per process id.
+ * lib/active_record/connection_adapters/sqlite_adapter.rb: ditto
+ *Aaron Patterson*
+
+* Support bulk change_table in mysql2 adapter, as well as the mysql one. *Jon Leighton*
+
+* If multiple parameters are sent representing a date, and some are blank, the
+ resulting object is nil. In previous releases those values defaulted to 1. This only affects existing but blank parameters, missing ones still raise an error. [Akira Matsuda]
+* ActiveRecord::Base.establish_connection now takes a string that contains
+ a URI that specifies the connection configuration. For example:
+ ActiveRecord::Base.establish_connection 'postgres://localhost/foo'
+
+## Rails 3.1.0 (August 30, 2011) ##
+
+* Add a proxy_association method to association proxies, which can be called by association
+ extensions to access information about the association. This replaces proxy_owner etc with
+ proxy_association.owner.
+
+ *Jon Leighton*
+
+* ActiveRecord::MacroReflection::AssociationReflection#build_record has a new method signature.
+
+ Before: def build_association(*options)
+ After: def build_association(*options, &block)
+
+ Users who are redefining this method to extend functionality should ensure that the block is
+ passed through to ActiveRecord::Base#new.
+
+ This change is necessary to fix https://github.com/rails/rails/issues/1842.
+
+ *Jon Leighton*
+
+* AR#pluralize_table_names can be used to singularize/pluralize table name of an individual model:
+
+ class User < ActiveRecord::Base
+ self.pluralize_table_names = false
+ end
+
+ Previously this could only be set globally for all models through ActiveRecord::Base.pluralize_table_names. *Guillermo Iguaran*
+
+* Add block setting of attributes to singular associations:
+
+ class User < ActiveRecord::Base
+ has_one :account
+ end
+
+ user.build_account{ |a| a.credit_limit = 100.0 }
+
+ The block is called after the instance has been initialized. *Andrew White*
+
+* Add ActiveRecord::Base.attribute_names to return a list of attribute names. This will return an empty array if the model is abstract or table does not exists. *Prem Sichanugrist*
+
+* CSV Fixtures are deprecated and support will be removed in Rails 3.2.0
+
+* AR#new, AR#create, AR#create!, AR#update_attributes and AR#update_attributes! all accept a second hash as option that allows you
+ to specify which role to consider when assigning attributes. This is built on top of ActiveModel's
+ new mass assignment capabilities:
+
+ class Post < ActiveRecord::Base
+ attr_accessible :title
+ attr_accessible :title, :published_at, :as => :admin
+ end
+
+ Post.new(params[:post], :as => :admin)
+
+ assign_attributes() with similar API was also added and attributes=(params, guard) was deprecated.
+
+ Please note that this changes the method signatures for AR#new, AR#create, AR#create!, AR#update_attributes and AR#update_attributes!. If you have overwritten these methods you should update them accordingly.
+
+ *Josh Kalderimis*
+
+* default_scope can take a block, lambda, or any other object which responds to `call` for lazy
+ evaluation:
+
+ default_scope { ... }
+ default_scope lambda { ... }
+ default_scope method(:foo)
+
+ This feature was originally implemented by Tim Morgan, but was then removed in favour of
+ defining a 'default_scope' class method, but has now been added back in by Jon Leighton.
+ The relevant lighthouse ticket is #1812.
+
+* 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*
+
+* If you wish to merge default scopes in special ways, it is recommended to define your default
+ scope as a class method and use the standard techniques for sharing code (inheritance, mixins,
+ etc.):
+
+ class Post < ActiveRecord::Base
+ def self.default_scope
+ where(:published => true).where(:hidden => false)
+ end
+ end
+
+ *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.
+
+* Added an update_column method on ActiveRecord. This new method updates a given attribute on an object, skipping validations and callbacks.
+ It is recommended to use #update_attribute unless you are sure you do not want to execute any callback, including the modification of
+ the updated_at column. It should not be called on new records.
+ Example:
+
+ User.first.update_column(:name, "sebastian") # => true
+
+ *Sebastian Martinez*
+
+* Associations with a :through option can now use *any* association as the
+ through or source association, including other associations which have a
+ :through option and has_and_belongs_to_many associations
+
+ *Jon Leighton*
+
+* The configuration for the current database connection is now accessible via
+ ActiveRecord::Base.connection_config. *fxn*
+
+* limits and offsets are removed from COUNT queries unless both are supplied.
+ For example:
+
+ People.limit(1).count # => 'SELECT COUNT(*) FROM people'
+ People.offset(1).count # => 'SELECT COUNT(*) FROM people'
+ People.limit(1).offset(1).count # => 'SELECT COUNT(*) FROM people LIMIT 1 OFFSET 1'
+
+ *lighthouse #6262*
+
+* ActiveRecord::Associations::AssociationProxy has been split. There is now an Association class
+ (and subclasses) which are responsible for operating on associations, and then a separate,
+ thin wrapper called CollectionProxy, which proxies collection associations.
+
+ This prevents namespace pollution, separates concerns, and will allow further refactorings.
+
+ Singular associations (has_one, belongs_to) no longer have a proxy at all. They simply return
+ the associated record or nil. This means that you should not use undocumented methods such
+ as bob.mother.create - use bob.create_mother instead.
+
+ *Jon Leighton*
+
+* Make has_many :through associations work correctly when you build a record and then save it. This
+ requires you to set the :inverse_of option on the source reflection on the join model, like so:
+
+ class Post < ActiveRecord::Base
+ has_many :taggings
+ has_many :tags, :through => :taggings
+ end
+
+ class Tagging < ActiveRecord::Base
+ belongs_to :post
+ belongs_to :tag, :inverse_of => :tagging # :inverse_of must be set!
+ end
+
+ class Tag < ActiveRecord::Base
+ has_many :taggings
+ has_many :posts, :through => :taggings
+ end
+
+ post = Post.first
+ tag = post.tags.build :name => "ruby"
+ tag.save # will save a Taggable linking to the post
+
+ *Jon Leighton*
+
+* Support the :dependent option on has_many :through associations. For historical and practical
+ reasons, :delete_all is the default deletion strategy employed by association.delete(*records),
+ despite the fact that the default strategy is :nullify for regular has_many. Also, this only
+ works at all if the source reflection is a belongs_to. For other situations, you should directly
+ modify the through association.
+
+ *Jon Leighton*
+
+* Changed the behaviour of association.destroy for has_and_belongs_to_many and has_many :through.
+ From now on, 'destroy' or 'delete' on an association will be taken to mean 'get rid of the link',
+ not (necessarily) 'get rid of the associated records'.
+
+ Previously, has_and_belongs_to_many.destroy(*records) would destroy the records themselves. It
+ would not delete any records in the join table. Now, it deletes the records in the join table.
+
+ Previously, has_many_through.destroy(*records) would destroy the records themselves, and the
+ records in the join table. [Note: This has not always been the case; previous version of Rails
+ only deleted the records themselves.] Now, it destroys only the records in the join table.
+
+ Note that this change is backwards-incompatible to an extent, but there is unfortunately no
+ way to 'deprecate' it before changing it. The change is being made in order to have
+ consistency as to the meaning of 'destroy' or 'delete' across the different types of associations.
+
+ If you wish to destroy the records themselves, you can do records.association.each(&:destroy)
+
+ *Jon Leighton*
+
+* Add :bulk => true option to change_table to make all the schema changes defined in change_table block using a single ALTER statement. *Pratik Naik*
+
+ Example:
+
+ change_table(:users, :bulk => true) do |t|
+ t.string :company_name
+ t.change :birthdate, :datetime
+ end
+
+ This will now result in:
+
+ ALTER TABLE `users` ADD COLUMN `company_name` varchar(255), CHANGE `updated_at` `updated_at` datetime DEFAULT NULL
+
+* Removed support for accessing attributes on a has_and_belongs_to_many join table. This has been
+ documented as deprecated behaviour since April 2006. Please use has_many :through instead.
+ *Jon Leighton*
+
+* Added a create_association! method for has_one and belongs_to associations. *Jon Leighton*
+
+* Migration files generated from model and constructive migration generators
+ (for example, add_name_to_users) use the reversible migration's `change`
+ method instead of the ordinary `up` and `down` methods. *Prem Sichanugrist*
+
+* Removed support for interpolating string SQL conditions on associations. Instead, you should
+ use a proc, like so:
+
+ Before:
+
+ has_many :things, :conditions => 'foo = #{bar}'
+
+ After:
+
+ has_many :things, :conditions => proc { "foo = #{bar}" }
+
+ Inside the proc, 'self' is the object which is the owner of the association, unless you are
+ eager loading the association, in which case 'self' is the class which the association is within.
+
+ You can have any "normal" conditions inside the proc, so the following will work too:
+
+ has_many :things, :conditions => proc { ["foo = ?", bar] }
+
+ Previously :insert_sql and :delete_sql on has_and_belongs_to_many association allowed you to call
+ 'record' to get the record being inserted or deleted. This is now passed as an argument to
+ the proc.
+
+* Added ActiveRecord::Base#has_secure_password (via ActiveModel::SecurePassword) to encapsulate dead-simple password usage with BCrypt encryption and salting [DHH]. Example:
+
+ # Schema: User(name:string, password_digest:string, password_salt:string)
+ class User < ActiveRecord::Base
+ has_secure_password
+ end
+
+ user = User.new(:name => "david", :password => "", :password_confirmation => "nomatch")
+ user.save # => false, password required
+ user.password = "mUc3m00RsqyRe"
+ user.save # => false, confirmation doesn't match
+ user.password_confirmation = "mUc3m00RsqyRe"
+ user.save # => true
+ user.authenticate("notright") # => false
+ user.authenticate("mUc3m00RsqyRe") # => user
+ User.find_by_name("david").try(:authenticate, "notright") # => nil
+ User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
+
+
+* When a model is generated add_index is added by default for belongs_to or references columns
+
+ rails g model post user:belongs_to will generate the following:
+
+ class CreatePosts < ActiveRecord::Migration
+ def up
+ create_table :posts do |t|
+ t.belongs_to :user
+ t.timestamps
+ end
+
+ add_index :posts, :user_id
+ end
+
+ def down
+ drop_table :posts
+ end
+ end
+
+ *Santiago Pastorino*
+
+* Setting the id of a belongs_to object will update the reference to the
+ object. [#2989 state:resolved]
+* ActiveRecord::Base#dup and ActiveRecord::Base#clone semantics have changed
+ to closer match normal Ruby dup and clone semantics.
+* Calling ActiveRecord::Base#clone will result in a shallow copy of the record,
+ including copying the frozen state. No callbacks will be called.
+* Calling ActiveRecord::Base#dup will duplicate the record, including calling
+ after initialize hooks. Frozen state will not be copied, and all associations will be cleared. A duped record will return true for new_record?, have a nil id field, and is saveable.
+* Migrations can be defined as reversible, meaning that the migration system
+ will figure out how to reverse your migration. To use reversible migrations, just define the "change" method. For example:
+ class MyMigration < ActiveRecord::Migration
+ def change
+ create_table(:horses) do
+ t.column :content, :text
+ t.column :remind_at, :datetime
+ end
+ end
+ end
+
+ Some things cannot be automatically reversed for you. If you know how to reverse those things, you should define 'up' and 'down' in your migration. If you define something in `change` that cannot be reversed, an IrreversibleMigration exception will be raised when going down.
+* Migrations should use instance methods rather than class methods:
+ class FooMigration < ActiveRecord::Migration
+ def up
+ ...
+ end
+ end
+
+ *Aaron Patterson*
+
+* has_one maintains the association with separate after_create/after_update instead
+ of a single after_save. *fxn*
+
+* The following code:
+
+ Model.limit(10).scoping { Model.count }
+
+ now generates the following SQL:
+
+ SELECT COUNT(*) FROM models LIMIT 10
+
+ This may not return what you want. Instead, you may with to do something
+ like this:
+
+ Model.limit(10).scoping { Model.all.size }
+
+ *Aaron Patterson*
+
+
+## Rails 3.0.12 (March 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* Exceptions from database adapters should not lose their backtrace.
+
+* Backport "ActiveRecord::Persistence#touch should not use default_scope" (GH #1519)
+
+* Psych errors with poor yaml formatting are proxied. Fixes GH #2645 and
+ GH #2731
+
+* Fix ActiveRecord#exists? when passsed a nil value
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* Magic encoding comment added to schema.rb files
+
+* schema.rb is written as UTF-8 by default.
+
+* Ensuring an established connection when running `rake db:schema:dump`
+
+* Association conditions will not clobber join conditions.
+
+* Destroying a record will destroy the HABTM record before destroying itself.
+ GH #402.
+
+* Make `ActiveRecord::Batches#find_each` to not return `self`.
+
+* Update `table_exists?` in PG to to always use current search_path or schema if explictly set.
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* Fix various problems with using :primary_key and :foreign_key options in conjunction with
+ :through associations. [Jon Leighton]
+
+* Correctly handle inner joins on polymorphic relationships.
+
+* Fixed infinity and negative infinity cases in PG date columns.
+
+* Creating records with invalid associations via `create` or `save` will no longer raise exceptions.
+
+
+## Rails 3.0.7 (April 18, 2011) ##
+
+* Destroying records via nested attributes works independent of reject_if LH #6006 *Durran Jordan*
+
+* Delegate any? and many? to Model.scoped for consistency *Andrew White*
+
+* Quote the ORDER BY clause in batched finds - fixes #6620 *Andrew White*
+
+* Change exists? so records are not instantiated - fixes #6127. This prevents after_find
+ and after_initialize callbacks being triggered when checking for record existence.
+ *Andrew White*
+
+* Fix performance bug with attribute accessors which only occurred on Ruby 1.8.7, and ensure we
+ cache type-casted values when the column returned from the db contains non-standard chars.
+ *Jon Leighton*
+
+* Fix a performance regression introduced here 86acbf1cc050c8fa8c74a10c735e467fb6fd7df8
+ related to read_attribute method *Stian Grytøyr*
+
+
+## Rails 3.0.6 (April 5, 2011) ##
+
+* Un-deprecate reorder method *Sebastian Martinez*
+
+* Extensions are applied when calling +except+ or +only+ on relations.
+ Thanks to Iain Hecker.
+
+* Schemas set in set_table_name are respected by the mysql adapter. LH #5322
+
+* Fixed a bug when empty? was called on a grouped Relation that wasn't loaded.
+ LH #5829
+
+* Reapply extensions when using except and only. Thanks Iain Hecker.
+
+* Binary data is escaped when being inserted to SQLite3 Databases. Thanks
+ Naruse!
+
+
+## Rails 3.0.5 (February 26, 2011) ##
+
+* Model.where(:column => 1).where(:column => 2) will always produce an AND
+ query.
+ *Aaron Patterson*
+
+* Deprecated support for interpolated association conditions in the form of :conditions => 'foo = #{bar}'.
+
+ Instead, you should use a proc, like so:
+
+ Before:
+
+ has_many :things, :conditions => 'foo = #{bar}'
+
+ After:
+
+ has_many :things, :conditions => proc { "foo = #{bar}" }
+
+ Inside the proc, 'self' is the object which is the owner of the association, unless you are
+ eager loading the association, in which case 'self' is the class which the association is within.
+
+ You can have any "normal" conditions inside the proc, so the following will work too:
+
+ has_many :things, :conditions => proc { ["foo = ?", bar] }
+
+ Previously :insert_sql and :delete_sql on has_and_belongs_to_many association allowed you to call
+ 'record' to get the record being inserted or deleted. This is now passed as an argument to
+ the proc.
+
+ *Jon Leighton*
+
+
+## Rails 3.0.4 (February 8, 2011) ##
+
+* Added deprecation warning for has_and_belongs_to_many associations where the join table has
+ additional attributes other than the keys. Access to these attributes is removed in 3.1.
+ Please use has_many :through instead. *Jon Leighton*
+
+
+## Rails 3.0.3 (November 16, 2010) ##
+
+* Support find by class like this: Post.where(:name => Post)
+
+
+## Rails 3.0.2 (November 15, 2010) ##
+
+* Dramatic speed increase (see: http://engineering.attinteractive.com/2010/10/arel-two-point-ohhhhh-yaaaaaa/) *Aaron Patterson*
+
+* reorder is deprecated in favor of except(:order).order(...) *Santiago Pastorino*
+
+* except is now AR public API
+
+ Model.order('name').except(:order).order('salary')
+
+ generates:
+
+ SELECT * FROM models ORDER BY salary
+
+ *Santiago Pastorino*
+
+* The following code:
+
+ Model.limit(10).scoping { Model.count }
+
+ now generates the following SQL:
+
+ SELECT COUNT(*) FROM models LIMIT 10
+
+ This may not return what you want. Instead, you may with to do something
+ like this:
+
+ Model.limit(10).scoping { Model.all.size }
+
+ *Aaron Patterson*
+
+
+## Rails 3.0.1 (October 15, 2010) ##
+
+* Introduce a fix for CVE-2010-3993
+
+
+## Rails 3.0.0 (August 29, 2010) ##
+
+* Changed update_attribute to not run callbacks and update the record directly in the database *Neeraj Singh*
+
+* Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope *José Valim*
+
+* New rake task, db:migrate:status, displays status of migrations #4947 *Kevin Skoglund*
+
+* select and order for ActiveRecord now always concatenate nested calls. Use reorder if you want the original order to be overwritten *Santiago Pastorino*
+
+* PostgreSQL: ensure the database time zone matches Ruby's time zone #4895 *Aaron Patterson*
+
+* Fixed that ActiveRecord::Base.compute_type would swallow NoMethodError #4751 *Andrew Bloomgarden, Andrew White*
+
+* Add index length support for MySQL. #1852 *Emili Parreno, Pratik Naik*
+
+ Example:
+
+ add_index(:accounts, :name, :name => 'by_name', :length => 10)
+ => CREATE INDEX by_name ON accounts(name(10))
+
+ add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15})
+ => CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
+
+* find_or_create_by_attr(value, ...) works when attr is protected. #4457 *Santiago Pastorino, Marc-André Lafortune*
+
+* New callbacks: after_commit and after_rollback. Do expensive operations like image thumbnailing after_commit instead of after_save. #2991 *Brian Durand*
+
+* Serialized attributes are not converted to YAML if they are any of the formats that can be serialized to XML (like Hash, Array and Strings). *José Valim*
+
+* Destroy uses optimistic locking. If lock_version on the record you're destroying doesn't match lock_version in the database, a StaleObjectError is raised. #1966 *Curtis Hawthorne*
+
+* PostgreSQL: drop support for old postgres driver. Use pg 0.9.0 or later. *Jeremy Kemper*
+
+* Observers can prevent records from saving by returning false, just like before_save and friends. #4087 *Mislav Marohnić*
+
+* Add Relation extensions. *Pratik Naik*
+
+ users = User.where(:admin => true).extending(User::AdminPowers)
+
+ latest_users = User.order('created_at DESC') do
+ def posts_count
+ Post.count(:user_id => to_a.map(&:id))
+ end
+ end
+
+* To prefix the table names of all models in a module, define self.table_name_prefix on the module. #4032 *Andrew White*
+
+* Silenced "SHOW FIELDS" and "SET SQL_AUTO_IS_NULL=0" statements from the MySQL driver to improve log signal to noise ration in development *DHH*
+
+* PostgreSQLAdapter: set time_zone to UTC when Base.default_timezone == :utc so that Postgres doesn't incorrectly offset-adjust values inserted into TIMESTAMP WITH TIME ZONE columns. #3777 *Jack Christensen*
+
+* Allow relations to be used as scope.
+
+ class Item
+ scope :red, where(:colour => 'red')
+ end
+
+ Item.red.limit(10) # Ten red items
+
+* Rename named_scope to scope. *Pratik Naik*
+
+* Changed ActiveRecord::Base.store_full_sti_class to be true by default reflecting the previously announced Rails 3 default *DHH*
+
+* Add Relation#except. *Pratik Naik*
+
+ one_red_item = Item.where(:colour => 'red').limit(1)
+ all_items = one_red_item.except(:where, :limit)
+
+* Add Relation#delete_all. *Pratik Naik*
+
+ Item.where(:colour => 'red').delete_all
+
+* Add Model.having and Relation#having. *Pratik Naik*
+
+ Developer.group("salary").having("sum(salary) > 10000").select("salary")
+
+* Add Relation#count. *Pratik Naik*
+
+ legends = People.where("age > 100")
+ legends.count
+ legends.count(:age, :distinct => true)
+ legends.select('id').count
+
+* Add Model.readonly and association_collection#readonly finder method. *Pratik Naik*
+
+ Post.readonly.to_a # Load all posts in readonly mode
+ @user.items.readonly(false).to_a # Load all the user items in writable mode
+
+* Add .lock finder method *Pratik Naik*
+
+ User.lock.where(:name => 'lifo').to_a
+
+ old_items = Item.where("age > 100")
+ old_items.lock.each {|i| .. }
+
+* Add Model.from and association_collection#from finder methods *Pratik Naik*
+
+ user = User.scoped
+ user.select('*').from('users, items')
+
+* Add relation.destroy_all *Pratik Naik*
+
+ old_items = Item.where("age > 100")
+ old_items.destroy_all
+
+* Add relation.exists? *Pratik Naik*
+
+ red_items = Item.where(:colours => 'red')
+ red_items.exists?
+ red_items.exists?(1)
+
+* Add find(ids) to relations. *Pratik Naik*
+
+ old_users = User.order("age DESC")
+ old_users.find(1)
+ old_users.find(1, 2, 3)
+
+* Add new finder methods to association collection. *Pratik Naik*
+
+ class User < ActiveRecord::Base
+ has_many :items
+ end
+
+ user = User.first
+ user.items.where(:items => {:colour => 'red'})
+ user.items.select('items.id')
+
+* Add relation.reload to force reloading the records. *Pratik Naik*
+
+ topics = Topic.scoped
+ topics.to_a # force load
+ topics.first # returns a cached record
+ topics.reload
+ topics.first # Fetches a new record from the database
+
+* Rename Model.conditions and relation.conditions to .where. *Pratik Naik*
+
+ Before :
+ User.conditions(:name => 'lifo')
+ User.select('id').conditions(["age > ?", 21])
+
+ Now :
+ User.where(:name => 'lifo')
+ User.select('id').where(["age > ?", 21])
+
+* Add Model.select/group/order/limit/joins/conditions/preload/eager_load class methods returning a lazy relation. *Pratik Naik*
+
+ Examples :
+
+ posts = Post.select('id).order('name') # Returns a lazy relation
+ posts.each {|p| puts p.id } # Fires "select id from posts order by name"
+
+* Model.scoped now returns a relation if invoked without any arguments. *Pratik Naik*
+
+ Example :
+
+ posts = Post.scoped
+ posts.size # Fires "select count(*) from posts" and returns the count
+ posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
+
+* Association inverses for belongs_to, has_one, and has_many. Optimization to reduce database queries. #3533 *Murray Steele*
+
+ # post.comments sets each comment's post without needing to :include
+ class Post < ActiveRecord::Base
+ has_many :comments, :inverse_of => :post
+ end
+
+* MySQL: add_ and change_column support positioning. #3286 *Ben Marini*
+
+* Reset your Active Record counter caches with the reset_counter_cache class method. #1211 *Mike Breen, Gabe da Silveira*
+
+* Remove support for SQLite 2. Please upgrade to SQLite 3+ or install the plugin from git://github.com/rails/sqlite2_adapter.git *Pratik Naik*
+
+* PostgreSQL: XML datatype support. #1874 *Leonardo Borges*
+
+* quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 *Geoff Buesing*
+
+* SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 *Paul Hinze, Jeremy Kemper*
+
+* Added :primary_key option to belongs_to associations. #765 *Szymon Nowak, Philip Hallstrom, Noel Rocha*
+ # employees.company_name references companies.name
+ Employee.belongs_to :company, :primary_key => 'name', :foreign_key => 'company_name'
+
+* Implement #many? for NamedScope and AssociationCollection using #size. #1500 *Chris Kampmeier*
+
+* Added :touch option to belongs_to associations that will touch the parent record when the current record is saved or destroyed *DHH*
+
+* Added ActiveRecord::Base#touch to update the updated_at/on attributes (or another specified timestamp) with the current time *DHH*
+
+
+## 2.3.2 Final (March 15, 2009) ##
+
+* Added ActiveRecord::Base.find_each and ActiveRecord::Base.find_in_batches for batch processing *DHH/Jamis Buck*
+
+* Added that ActiveRecord::Base.exists? can be called with no arguments #1817 *Scott Taylor*
+
+* Add Support for updating deeply nested models from a single form. #1202 *Eloy Duran*
+
+ class Book < ActiveRecord::Base
+ has_one :author
+ has_many :pages
+
+ accepts_nested_attributes_for :author, :pages
+ end
+
+* Make after_save callbacks fire only if the record was successfully saved. #1735 *Michael Lovitt*
+
+ Previously the callbacks would fire if a before_save cancelled saving.
+
+* Support nested transactions using database savepoints. #383 *Jonathan Viney, Hongli Lai*
+
+* Added dynamic scopes ala dynamic finders #1648 *Yaroslav Markin*
+
+* Fixed that ActiveRecord::Base#new_record? should return false (not nil) for existing records #1219 *Yaroslav Markin*
+
+* I18n the word separator for error messages. Introduces the activerecord.errors.format.separator translation key. #1294 *Akira Matsuda*
+
+* Add :having as a key to find and the relevant associations. *Emilio Tagua*
+
+* Added default_scope to Base #1381 [Paweł Kondzior]. Example:
+
+ class Person < ActiveRecord::Base
+ default_scope :order => 'last_name, first_name'
+ end
+
+ class Company < ActiveRecord::Base
+ has_many :people
+ end
+
+ Person.all # => Person.find(:all, :order => 'last_name, first_name')
+ Company.find(1).people # => Person.find(:all, :order => 'last_name, first_name', :conditions => { :company_id => 1 })
+
+
+## 2.2.1 RC2 (November 14th, 2008) ##
+
+* Ensure indices don't flip order in schema.rb #1266 *Jordi Bunster*
+
+* Fixed that serialized strings should never be type-casted (i.e. turning "Yes" to a boolean) #857 *Andreas Korth*
+
+
+## 2.2.0 RC1 (October 24th, 2008) ##
+
+* Skip collection ids reader optimization if using :finder_sql *Jeremy Kemper*
+
+* Add Model#delete instance method, similar to Model.delete class method. #1086 *Hongli Lai (Phusion)*
+
+* MySQL: cope with quirky default values for not-null text columns. #1043 *Frederick Cheung*
+
+* Multiparameter attributes skip time zone conversion for time-only columns [#1030 state:resolved] *Geoff Buesing*
+
+* Base.skip_time_zone_conversion_for_attributes uses class_inheritable_accessor, so that subclasses don't overwrite Base [#346 state:resolved] *Emilio Tagua*
+
+* Added find_last_by dynamic finder #762 *Emilio Tagua*
+
+* Internal API: configurable association options and build_association method for reflections so plugins may extend and override. #985 *Hongli Lai (Phusion)*
+
+* Changed benchmarks to be reported in milliseconds *David Heinemeier Hansson*
+
+* Connection pooling. #936 *Nick Sieger*
+
+* Merge scoped :joins together instead of overwriting them. May expose scoping bugs in your code! #501 *Andrew White*
+
+* before_save, before_validation and before_destroy callbacks that return false will now ROLLBACK the transaction. Previously this would have been committed before the processing was aborted. #891 *Xavier Noria*
+
+* Transactional migrations for databases which support them. #834 *divoxx, Adam Wiggins, Tarmo Tänav*
+
+* Set config.active_record.timestamped_migrations = false to have migrations with numeric prefix instead of UTC timestamp. #446. *Andrew Stone, Nik Wakelin*
+
+* change_column_default preserves the not-null constraint. #617 *Tarmo Tänav*
+
+* Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) *#334*
+
+* Add :tokenizer option to validates_length_of to specify how to split up the attribute string. #507. [David Lowenfels] Example :
+
+ \# Ensure essay contains at least 100 words.
+ validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
+
+* Allow conditions on multiple tables to be specified using hash. [Pratik Naik]. Example:
+
+ User.all :joins => :items, :conditions => { :age => 10, :items => { :color => 'black' } }
+ Item.first :conditions => { :items => { :color => 'red' } }
+
+* Always treat integer :limit as byte length. #420 *Tarmo Tänav*
+
+* Partial updates don't update lock_version if nothing changed. #426 *Daniel Morrison*
+
+* Fix column collision with named_scope and :joins. #46 *Duncan Beevers, Mark Catley*
+
+* db:migrate:down and :up update schema_migrations. #369 *Michael Raidel, RaceCondition*
+
+* PostgreSQL: support :conditions => [':foo::integer', { :foo => 1 }] without treating the ::integer typecast as a bind variable. *Tarmo Tänav*
+
+* MySQL: rename_column preserves column defaults. #466 *Diego Algorta*
+
+* Add :from option to calculations. #397 *Ben Munat*
+
+* Add :validate option to associations to enable/disable the automatic validation of associated models. Resolves #301. *Jan De Poorter*
+
+* PostgreSQL: use 'INSERT ... RETURNING id' for 8.2 and later. *Jeremy Kemper*
+
+* Added SQL escaping for :limit and :offset in MySQL *Jonathan Wiess*
+
+
+## 2.1.0 (May 31st, 2008) ##
+
+* Add ActiveRecord::Base.sti_name that checks ActiveRecord::Base#store_full_sti_class? and returns either the full or demodulized name. *Rick Olson*
+
+* Add first/last methods to associations/named_scope. Resolved #226. *Ryan Bates*
+
+* Added SQL escaping for :limit and :offset #288 *Aaron Bedra, Steven Bristol, Jonathan Wiess*
+
+* Added first/last methods to associations/named_scope. Resolved #226. *Ryan Bates*
+
+* Ensure hm:t preloading honours reflection options. Resolves #137. *Frederick Cheung*
+
+* Added protection against duplicate migration names (Aslak Hellesøy) *#112*
+
+* Base#instantiate_time_object: eliminate check for Time.zone, since we can assume this is set if time_zone_aware_attributes is set to true *Geoff Buesing*
+
+* Time zone aware attribute methods use Time.zone.parse instead of #to_time for String arguments, so that offset information in String is respected. Resolves #105. *Scott Fleckenstein, Geoff Buesing*
+
+* Added change_table for migrations (Jeff Dean) [#71]. Example:
+
+ change_table :videos do |t|
+ t.timestamps # adds created_at, updated_at
+ t.belongs_to :goat # adds goat_id integer
+ t.string :name, :email, :limit => 20 # adds name and email both with a 20 char limit
+ t.remove :name, :email # removes the name and email columns
+ end
+
+* Fixed has_many :through .create with no parameters caused a "can't dup NilClass" error (Steven Soroka) *#85*
+
+* Added block-setting of attributes for Base.create like Base.new already has (Adam Meehan) *#39*
+
+* Fixed that pessimistic locking you reference the quoted table name (Josh Susser) *#67*
+
+* Fixed that change_column should be able to use :null => true on a field that formerly had false [Nate Wiger] *#26*
+
+* Added that the MySQL adapter should map integer to either smallint, int, or bigint depending on the :limit just like PostgreSQL *David Heinemeier Hansson*
+
+* Change validates_uniqueness_of :case_sensitive option default back to true (from [9160]). Love your database columns, don't LOWER them. *Rick Olson*
+
+* Add support for interleaving migrations by storing which migrations have run in the new schema_migrations table. Closes #11493 *Jordi Bunster*
+
+* ActiveRecord::Base#sum defaults to 0 if no rows are returned. Closes #11550 *Kamal Fariz Mahyuddin*
+
+* Ensure that respond_to? considers dynamic finder methods. Closes #11538. *James Mead*
+
+* Ensure that save on parent object fails for invalid has_one association. Closes #10518. *Pratik Naik*
+
+* Remove duplicate code from associations. *Pratik Naik*
+
+* Refactor HasManyThroughAssociation to inherit from HasManyAssociation. Association callbacks and <association>_ids= now work with hm:t. #11516 *Ruy Asan*
+
+* Ensure HABTM#create and HABTM#build do not load entire association. *Pratik Naik*
+
+* Improve documentation. *Xavier Noria, Jack Danger Canty, leethal*
+
+* Tweak ActiveRecord::Base#to_json to include a root value in the returned hash: {"post": {"title": ...}} *Rick Olson*
+
+ Post.find(1).to_json # => {"title": ...}
+ config.active_record.include_root_in_json = true
+ Post.find(1).to_json # => {"post": {"title": ...}}
+
+* Add efficient #include? to AssociationCollection (for has_many/has_many :through/habtm). *stopdropandrew*
+
+* PostgreSQL: create_ and drop_database support. #9042 *ez, pedz, Nick Sieger*
+
+* Ensure that validates_uniqueness_of works with with_scope. Closes #9235. *Nik Wakelin, cavalle*
+
+* Partial updates include only unsaved attributes. Off by default; set YourClass.partial_updates = true to enable. *Jeremy Kemper*
+
+* Removing unnecessary uses_tzinfo helper from tests, given that TZInfo is now bundled *Geoff Buesing*
+
+* Fixed that validates_size_of :within works in associations #11295, #10019 *cavalle*
+
+* Track changes to unsaved attributes. *Jeremy Kemper*
+
+* Switched to UTC-timebased version numbers for migrations and the schema. This will as good as eliminate the problem of multiple migrations getting the same version assigned in different branches. Also added rake db:migrate:up/down to apply individual migrations that may need to be run when you merge branches #11458 *John Barnette*
+
+* Fixed that has_many :through would ignore the hash conditions #11447 *Emilio Tagua*
+
+* Fix issue where the :uniq option of a has_many :through association is ignored when find(:all) is called. Closes #9407 *cavalle*
+
+* Fix duplicate table alias error when including an association with a has_many :through association on the same join table. Closes #7310 *cavalle*
+
+* More efficient association preloading code that compacts a through_records array in a central location. Closes #11427 *Jack Danger Canty*
+
+* Improve documentation. *Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert*
+
+* Fixed that ActiveRecord#Base.find_or_create/initialize would not honor attr_protected/accessible when used with a hash #11422 *Emilio Tagua*
+
+* Added ActiveRecord#Base.all/first/last as aliases for find(:all/:first/:last) #11413 *nkallen, Chris O'Sullivan*
+
+* Merge the has_finder gem, renamed as 'named_scope'. #11404 *nkallen*
+
+ class Article < ActiveRecord::Base
+ named_scope :published, :conditions => {:published => true}
+ named_scope :popular, :conditions => ...
+ end
+
+ Article.published.paginate(:page => 1)
+ Article.published.popular.count
+ Article.popular.find(:first)
+ Article.popular.find(:all, :conditions => {...})
+
+ See http://pivots.pivotallabs.com/users/nick/blog/articles/284-hasfinder-it-s-now-easier-than-ever-to-create-complex-re-usable-sql-queries
+
+* Add has_one :through support. #4756 *Chris O'Sullivan*
+
+* Migrations: create_table supports primary_key_prefix_type. #10314 *student, Chris O'Sullivan*
+
+* Added logging for dependency load errors with fixtures #11056 *stuthulhu*
+
+* Time zone aware attributes use Time#in_time_zone *Geoff Buesing*
+
+* Fixed that scoped joins would not always be respected #6821 *Theory/Jack Danger Canty*
+
+* Ensure that ActiveRecord::Calculations disambiguates field names with the table name. #11027 *cavalle*
+
+* Added add/remove_timestamps to the schema statements for adding the created_at/updated_at columns on existing tables #11129 *jramirez*
+
+* Added ActiveRecord::Base.find(:last) #11338 *Emilio Tagua*
+
+* test_native_types expects DateTime.local_offset instead of DateTime.now.offset; fixes test breakage due to dst transition *Geoff Buesing*
+
+* Add :readonly option to HasManyThrough associations. #11156 *Emilio Tagua*
+
+* Improve performance on :include/:conditions/:limit queries by selectively joining in the pre-query. #9560 *dasil003*
+
+* Perf fix: Avoid the use of named block arguments. Closes #11109 *adymo*
+
+* PostgreSQL: support server versions 7.4 through 8.0 and the ruby-pg driver. #11127 *jdavis*
+
+* Ensure association preloading doesn't break when an association returns nil. ##11145 *GMFlash*
+
+* Make dynamic finders respect the :include on HasManyThrough associations. #10998. *cpytel*
+
+* Base#instantiate_time_object only uses Time.zone when Base.time_zone_aware_attributes is true; leverages Time#time_with_datetime_fallback for readability *Geoff Buesing*
+
+* Refactor ConnectionAdapters::Column.new_time: leverage DateTime failover behavior of Time#time_with_datetime_fallback *Geoff Buesing*
+
+* Improve associations performance by using symbol callbacks instead of string callbacks. #11108 *adymo*
+
+* Optimise the BigDecimal conversion code. #11110 *adymo*
+
+* Introduce the :readonly option to all associations. Records from the association cannot be saved. #11084 *Emilio Tagua*
+
+* Multiparameter attributes for time columns fail over to DateTime when out of range of Time *Geoff Buesing*
+
+* Base#instantiate_time_object uses Time.zone.local() *Geoff Buesing*
+
+* Add timezone-aware attribute readers and writers. #10982 *Geoff Buesing*
+
+* Instantiating time objects in multiparameter attributes uses Time.zone if available. #10982 *Rick Olson*
+
+* Add note about how ActiveRecord::Observer classes are initialized in a Rails app. #10980 *Xavier Noria*
+
+* MySQL: omit text/blob defaults from the schema instead of using an empty string. #10963 *mdeiters*
+
+* belongs_to supports :dependent => :destroy and :delete. #10592 *Jonathan Viney*
+
+* Introduce preload query strategy for eager :includes. #9640 *Frederick Cheung, Aliaksey Kandratsenka, codafoo*
+
+* Support aggregations in finder conditions. #10572 *Ryan Kinderman*
+
+* Organize and clean up the Active Record test suite. #10742 *John Barnette*
+
+* Ensure that modifying has_and_belongs_to_many actions clear the query cache. Closes #10840 *john.andrews*
+
+* Fix issue where Table#references doesn't pass a :null option to a *_type attribute for polymorphic associations. Closes #10753 *railsjitsu*
+
+* Fixtures: removed support for the ancient pre-YAML file format. #10736 *John Barnette*
+
+* More thoroughly quote table names. #10698 *dimdenis, lotswholetime, Jeremy Kemper*
+
+* update_all ignores scoped :order and :limit, so post.comments.update_all doesn't try to include the comment order in the update statement. #10686 *Brendan Ribera*
+
+* Added ActiveRecord::Base.cache_key to make it easier to cache Active Records in combination with the new ActiveSupport::Cache::* libraries *David Heinemeier Hansson*
+
+* Make sure CSV fixtures are compatible with ruby 1.9's new csv implementation. *JEG2*
+
+* Added by parameter to increment, decrement, and their bang varieties so you can do player1.increment!(:points, 5) #10542 *Sam*
+
+* Optimize ActiveRecord::Base#exists? to use #select_all instead of #find. Closes #10605 *jamesh, Frederick Cheung, protocool*
+
+* Don't unnecessarily load has_many associations in after_update callbacks. Closes #6822 *stopdropandrew, canadaduane*
+
+* Eager belongs_to :include infers the foreign key from the association name rather than the class name. #10517 *Jonathan Viney*
+
+* SQLite: fix rename_ and remove_column for columns with unique indexes. #10576 *Brandon Keepers*
+
+* Ruby 1.9 compatibility. #10655 *Jeremy Kemper, Dirkjan Bussink*
+
+
+## 2.0.2 (December 16th, 2007) ##
+
+* Ensure optimistic locking handles nil #lock_version values properly. Closes #10510 *Rick Olson*
+
+* Make the Fixtures Test::Unit enhancements more supporting for double-loaded test cases. Closes #10379 *brynary*
+
+* Fix that validates_acceptance_of still works for non-existent tables (useful for bootstrapping new databases). Closes #10474 *Josh Susser*
+
+* Ensure that the :uniq option for has_many :through associations retains the order. #10463 *remvee*
+
+* Base.exists? doesn't rescue exceptions to avoid hiding SQL errors. #10458 *Michael Klishin*
+
+* Documentation: Active Record exceptions, destroy_all and delete_all. #10444, #10447 *Michael Klishin*
+
+
+## 2.0.1 (December 7th, 2007) ##
+
+* Removed query cache rescue as it could cause code to be run twice (closes #10408) *David Heinemeier Hansson*
+
+
+## 2.0.0 (December 6th, 2007) ##
+
+* Anchor DateTimeTest to fixed DateTime instead of a variable value based on Time.now#advance#to_datetime, so that this test passes on 64-bit platforms running Ruby 1.8.6+ *Geoff Buesing*
+
+* Fixed that the Query Cache should just be ignored if the database is misconfigured (so that the "About your applications environment" works even before the database has been created) *David Heinemeier Hansson*
+
+* Fixed that the truncation of strings longer than 50 chars should use inspect
+ so newlines etc are escaped #10385 [Norbert Crombach]
+* Fixed that habtm associations should be able to set :select as part of their definition and have that honored *David Heinemeier Hansson*
+
+* Document how the :include option can be used in Calculations::calculate. Closes #7446 *adamwiggins, ultimoamore*
+
+* Fix typo in documentation for polymorphic associations w/STI. Closes #7461 *johnjosephbachir*
+
+* Reveal that the type option in migrations can be any supported column type for your database but also include caveat about agnosticism. Closes #7531 *adamwiggins, mikong*
+
+* More complete documentation for find_by_sql. Closes #7912 *fearoffish*
+
+* Added ActiveRecord::Base#becomes to turn a record into one of another class (mostly relevant for STIs) [David Heinemeier Hansson]. Example:
+
+ render :partial => @client.becomes(Company) # renders companies/company instead of clients/client
+
+* Fixed that to_xml should not automatically pass :procs to associations included with :include #10162 *Cheah Chu Yeow*
+
+* Fix documentation typo introduced in [8250]. Closes #10339 *Henrik N*
+
+* Foxy fixtures: support single-table inheritance. #10234 *tom*
+
+* Foxy fixtures: allow mixed usage to make migration easier and more attractive. #10004 *lotswholetime*
+
+* Make the record_timestamps class-inheritable so it can be set per model. #10004 *tmacedo*
+
+* Allow validates_acceptance_of to use a real attribute instead of only virtual (so you can record that the acceptance occured) #7457 *ambethia*
+
+* DateTimes use Ruby's default calendar reform setting. #10201 *Geoff Buesing*
+
+* Dynamic finders on association collections respect association :order and :limit. #10211, #10227 *Patrick Joyce, Rick Olson, Jack Danger Canty*
+
+* Add 'foxy' support for fixtures of polymorphic associations. #10183 *John Barnette, David Lowenfels*
+
+* validates_inclusion_of and validates_exclusion_of allow formatted :message strings. #8132 *devrieda, Mike Naberezny*
+
+* attr_readonly behaves well with optimistic locking. #10188 *Nick Bugajski*
+
+* Base#to_xml supports the nil="true" attribute like Hash#to_xml. #8268 *Jonathan del Strother*
+
+* Change plings to the more conventional quotes in the documentation. Closes #10104 *Jack Danger Canty*
+
+* Fix HasManyThrough Association so it uses :conditions on the HasMany Association. Closes #9729 *Jack Danger Canty*
+
+* Ensure that column names are quoted. Closes #10134 *wesley.moxam*
+
+* Smattering of grammatical fixes to documentation. Closes #10083 *Bob Silva*
+
+* Enhance explanation with more examples for attr_accessible macro. Closes #8095 *fearoffish, Marcel Molina Jr.*
+
+* Update association/method mapping table to refected latest collection methods for has_many :through. Closes #8772 *Pratik Naik*
+
+* Explain semantics of having several different AR instances in a transaction block. Closes #9036 *jacobat, Marcel Molina Jr.*
+
+* Update Schema documentation to use updated sexy migration notation. Closes #10086 *Sam Granieri*
+
+* Make fixtures work with the new test subclasses. *Tarmo Tänav, Michael Koziarski*
+
+* Introduce finder :joins with associations. Same :include syntax but with inner rather than outer joins. #10012 *RubyRedRick*
+ # Find users with an avatar
+ User.find(:all, :joins => :avatar)
+
+ # Find posts with a high-rated comment.
+ Post.find(:all, :joins => :comments, :conditions => 'comments.rating > 3')
+
+* Associations: speedup duplicate record check. #10011 *Pratik Naik*
+
+* Make sure that << works on has_many associations on unsaved records. Closes #9989 *Josh Susser*
+
+* Allow association redefinition in subclasses. #9346 *wildchild*
+
+* Fix has_many :through delete with custom foreign keys. #6466 *naffis*
+
+* Foxy fixtures, from rathole (http://svn.geeksomnia.com/rathole/trunk/README)
+ - stable, autogenerated IDs
+ - specify associations (belongs_to, has_one, has_many) by label, not ID
+ - specify HABTM associations as inline lists
+ - autofill timestamp columns
+ - support YAML defaults
+ - fixture label interpolation
+ Enabled for fixtures that correspond to a model class and don't specify a primary key value. #9981 *John Barnette*
+
+* Add docs explaining how to protect all attributes using attr_accessible with no arguments. Closes #9631 *boone, rmm5t*
+
+* Update add_index documentation to use new options api. Closes #9787 *Kamal Fariz Mahyuddin*
+
+* Allow find on a has_many association defined with :finder_sql to accept id arguments as strings like regular find does. Closes #9916 *krishna*
+
+* Use VALID_FIND_OPTIONS when resolving :find scoping rather than hard coding the list of valid find options. Closes #9443 *sur*
+
+* Limited eager loading no longer ignores scoped :order. Closes #9561 *Jack Danger Canty, Josh Peek*
+
+* Assigning an instance of a foreign class to a composed_of aggregate calls an optional conversion block. Refactor and simplify composed_of implementation. #6322 *brandon, Chris Cruft*
+
+* Assigning nil to a composed_of aggregate also sets its immediate value to nil. #9843 *Chris Cruft*
+
+* Ensure that mysql quotes table names with database names correctly. Closes #9911 *crayz*
+
+ "foo.bar" => "`foo`.`bar`"
+
+* Complete the assimilation of Sexy Migrations from ErrFree *Chris Wanstrath, PJ Hyett*
+ http://errtheblog.com/post/2381
+
+* Qualified column names work in hash conditions, like :conditions => { 'comments.created_at' => ... }. #9733 *Jack Danger Canty*
+
+* Fix regression where the association would not construct new finder SQL on save causing bogus queries for "WHERE owner_id = NULL" even after owner was saved. #8713 *Bryan Helmkamp*
+
+* Refactor association create and build so before & after callbacks behave consistently. #8854 *Pratik Naik, mortent*
+
+* Quote table names. Defaults to column quoting. #4593 *Justin Lynn, gwcoffey, eadz, Dmitry V. Sabanin, Jeremy Kemper*
+
+* Alias association #build to #new so it behaves predictably. #8787 *Pratik Naik*
+
+* Add notes to documentation regarding attr_readonly behavior with counter caches and polymorphic associations. Closes #9835 *saimonmoore, Rick Olson*
+
+* Observers can observe model names as symbols properly now. Closes #9869 *queso*
+
+* find_and_(initialize|create)_by methods can now properly initialize protected attributes *Tobias Lütke*
+
+* belongs_to infers the foreign key from the association name instead of from the class name. *Jeremy Kemper*
+
+* PostgreSQL: support multiline default values. #7533 *Carl Lerche, aguynamedryan, Rein Henrichs, Tarmo Tänav*
+
+* MySQL: fix change_column on not-null columns that don't accept dfeault values of ''. #6663 *Jonathan Viney, Tarmo Tänav*
+
+* validates_uniqueness_of behaves well with abstract superclasses and
+ single-table inheritance. #3833, #9886 [Gabriel Gironda, rramdas, François Beausoleil, Josh Peek, Tarmo Tänav, pat]
+* Warn about protected attribute assigments in development and test environments when mass-assigning to an attr_protected attribute. #9802 *Henrik N*
+
+* Speedup database date/time parsing. *Jeremy Kemper, Tarmo Tänav*
+
+* Fix calling .clear on a has_many :dependent=>:delete_all association. *Tarmo Tänav*
+
+* Allow change_column to set NOT NULL in the PostgreSQL adapter *Tarmo Tänav*
+
+* Fix that ActiveRecord would create attribute methods and override custom attribute getters if the method is also defined in Kernel.methods. *Rick Olson*
+
+* Don't call attr_readonly on polymorphic belongs_to associations, in case it matches the name of some other non-ActiveRecord class/module. *Rick Olson*
+
+* Try loading activerecord-<adaptername>-adapter gem before trying a plain require so you can use custom gems for the bundled adapters. Also stops gems from requiring an adapter from an old Active Record gem. *Jeremy Kemper, Derrick Spell*
+
+
+## 2.0.0 Preview Release (September 29th, 2007) Includes duplicates of changes from 1.14.2 - 1.15.3 ##
+
+* Add attr_readonly to specify columns that are skipped during a normal ActiveRecord #save operation. Closes #6896 *Dan Manges*
+
+ class Comment < ActiveRecord::Base
+ # Automatically sets Article#comments_count as readonly.
+ belongs_to :article, :counter_cache => :comments_count
+ end
+
+ class Article < ActiveRecord::Base
+ attr_readonly :approved_comments_count
+ end
+
+* Make size for has_many :through use counter cache if it exists. Closes #9734 *Xavier Shay*
+
+* Remove DB2 adapter since IBM chooses to maintain their own adapter instead. *Jeremy Kemper*
+
+* Extract Oracle, SQLServer, and Sybase adapters into gems. *Jeremy Kemper*
+
+* Added fixture caching that'll speed up a normal fixture-powered test suite between 50% and 100% #9682 *Frederick Cheung*
+
+* Correctly quote id list for limited eager loading. #7482 *tmacedo*
+
+* Fixed that using version-targetted migrates would fail on loggers other than the default one #7430 *valeksenko*
+
+* Fixed rename_column for SQLite when using symbols for the column names #8616 *drodriguez*
+
+* Added the possibility of using symbols in addition to concrete classes with ActiveRecord::Observer#observe. #3998 *Robby Russell, Tarmo Tänav*
+
+* Added ActiveRecord::Base#to_json/from_json *David Heinemeier Hansson, Cheah Chu Yeow*
+
+* Added ActiveRecord::Base#from_xml [David Heinemeier Hansson]. Example:
+
+ xml = "<person><name>David</name></person>"
+ Person.new.from_xml(xml).name # => "David"
+
+* Define dynamic finders as real methods after first usage. *bscofield*
+
+* Deprecation: remove deprecated threaded_connections methods. Use allow_concurrency instead. *Jeremy Kemper*
+
+* Associations macros accept extension blocks alongside modules. #9346 *Josh Peek*
+
+* Speed up and simplify query caching. *Jeremy Kemper*
+
+* connection.select_rows 'sql' returns an array (rows) of arrays (field values). #2329 *Michael Schuerig*
+
+* Eager loading respects explicit :joins. #9496 *dasil003*
+
+* Extract Firebird, FrontBase, and OpenBase adapters into gems. #9508, #9509, #9510 *Jeremy Kemper*
+
+* RubyGem database adapters: expects a gem named activerecord-<database>-adapter with active_record/connection_adapters/<database>_adapter.rb in its load path. *Jeremy Kemper*
+
+* Fixed that altering join tables in migrations would fail w/ sqlite3 #7453 *TimoMihaljov/brandon*
+
+* Fix association writer with :dependent => :nullify. #7314 *Jonathan Viney*
+
+* OpenBase: update for new lib and latest Rails. Support migrations. #8748 *dcsesq*
+
+* Moved acts_as_tree into a plugin of the same name on the official Rails svn. #9514 *Pratik Naik*
+
+* Moved acts_as_nested_set into a plugin of the same name on the official Rails svn. #9516 *Josh Peek*
+
+* Moved acts_as_list into a plugin of the same name on the official Rails svn. *Josh Peek*
+
+* Explicitly require active_record/query_cache before using it. *Jeremy Kemper*
+
+* Fix bug where unserializing an attribute attempts to modify a frozen @attributes hash for a deleted record. *Rick Olson, marclove*
+
+* Performance: absorb instantiate and initialize_with_callbacks into the Base methods. *Jeremy Kemper*
+
+* Fixed that eager loading queries and with_scope should respect the :group option *David Heinemeier Hansson*
+
+* Improve performance and functionality of the postgresql adapter. Closes #8049 *roderickvd*
+
+ For more information see: http://dev.rubyonrails.org/ticket/8049
+
+* Don't clobber includes passed to has_many.count *Jack Danger Canty*
+
+* Make sure has_many uses :include when counting *Jack Danger Canty*
+
+* Change the implementation of ActiveRecord's attribute reader and writer methods *Michael Koziarski*
+ - Generate Reader and Writer methods which cache attribute values in hashes. This is to avoid repeatedly parsing the same date or integer columns. - Change exception raised when users use find with :select then try to access a skipped column. Plugins could override missing_attribute() to lazily load the columns. - Move method definition to the class, instead of the instance - Always generate the readers, writers and predicate methods.
+* Perform a deep #dup on query cache results so that modifying activerecord attributes does not modify the cached attributes. *Rick Olson*
+
+ \# Ensure that has_many :through associations use a count query instead of loading the target when #size is called. Closes #8800 [Pratik Naik]
+* Added :unless clause to validations #8003 [monki]. Example:
+
+ def using_open_id?
+ !identity_url.blank?
+ end
+
+ validates_presence_of :identity_url, :if => using_open_id?
+ validates_presence_of :username, :unless => using_open_id?
+ validates_presence_of :password, :unless => using_open_id?
+
+* Fix #count on a has_many :through association so that it recognizes the :uniq option. Closes #8801 *Pratik Naik*
+
+* Fix and properly document/test count(column_name) usage. Closes #8999 *Pratik Naik*
+
+* Remove deprecated count(conditions=nil, joins=nil) usage. Closes #8993 *Pratik Naik*
+
+* Change belongs_to so that the foreign_key assumption is taken from the association name, not the class name. Closes #8992 *Josh Susser*
+
+ OLD
+ belongs_to :visitor, :class_name => 'User' # => inferred foreign_key is user_id
+
+ NEW
+ belongs_to :visitor, :class_name => 'User' # => inferred foreign_key is visitor_id
+
+* Remove spurious tests from deprecated_associations_test, most of these aren't deprecated, and are duplicated in associations_test. Closes #8987 *Pratik Naik*
+
+* Make create! on a has_many :through association return the association object. Not the collection. Closes #8786 *Pratik Naik*
+
+* Move from select * to select tablename.* to avoid clobbering IDs. Closes #8889 *dasil003*
+
+* Don't call unsupported methods on associated objects when using :include, :method with to_xml #7307, *Manfred Stienstra, jwilger*
+
+* Define collection singular ids method for has_many :through associations. #8763 *Pratik Naik*
+
+* Array attribute conditions work with proxied association collections. #8318 *Kamal Fariz Mahyuddin, theamazingrando*
+
+* Fix polymorphic has_one associations declared in an abstract class. #8638 *Pratik Naik, Dax Huiberts*
+
+* Fixed validates_associated should not stop on the first error. #4276 *mrj, Manfred Stienstra, Josh Peek*
+
+* Rollback if commit raises an exception. #8642 *kik, Jeremy Kemper*
+
+* Update tests' use of fixtures for the new collections api. #8726 *Kamal Fariz Mahyuddin*
+
+* Save associated records only if the association is already loaded. #8713 *Blaine*
+
+* MySQL: fix show_variable. #8448 *matt, Jeremy Kemper*
+
+* Fixtures: correctly delete and insert fixtures in a single transaction. #8553 *Michael Schuerig*
+
+* Fixtures: people(:technomancy, :josh) returns both fixtures. #7880 *technomancy, Josh Peek*
+
+* Calculations support non-numeric foreign keys. #8154 *Kamal Fariz Mahyuddin*
+
+* with_scope is protected. #8524 *Josh Peek*
+
+* Quickref for association methods. #7723 *marclove, Mindsweeper*
+
+* Calculations: return nil average instead of 0 when there are no rows to average. #8298 *davidw*
+
+* acts_as_nested_set: direct_children is sorted correctly. #4761 *Josh Peek, rails@33lc0.net*
+
+* Raise an exception if both attr_protected and attr_accessible are declared. #8507 *stellsmi*
+
+* SQLite, MySQL, PostgreSQL, Oracle: quote column names in column migration SQL statements. #8466 *marclove, lorenjohnson*
+
+* Allow nil serialized attributes with a set class constraint. #7293 *sandofsky*
+
+* Oracle: support binary fixtures. #7987 *Michael Schoen*
+
+* Fixtures: pull fixture insertion into the database adapters. #7987 *Michael Schoen*
+
+* Announce migration versions as they're performed. *Jeremy Kemper*
+
+* find gracefully copes with blank :conditions. #7599 *Dan Manges, johnnyb*
+
+* validates_numericality_of takes :greater_than, :greater_than_or_equal_to, :equal_to, :less_than, :less_than_or_equal_to, :odd, and :even options. #3952 *Bob Silva, Dan Kubb, Josh Peek*
+
+* MySQL: create_database takes :charset and :collation options. Charset defaults to utf8. #8448 *matt*
+
+* Find with a list of ids supports limit/offset. #8437 *hrudududu*
+
+* Optimistic locking: revert the lock version when an update fails. #7840 *plang*
+
+* Migrations: add_column supports custom column types. #7742 *jsgarvin, Theory*
+
+* Load database adapters on demand. Eliminates config.connection_adapters and RAILS_CONNECTION_ADAPTERS. Add your lib directory to the $LOAD_PATH and put your custom adapter in lib/active_record/connection_adapters/adaptername_adapter.rb. This way you can provide custom adapters as plugins or gems without modifying Rails. *Jeremy Kemper*
+
+* Ensure that associations with :dependent => :delete_all respect :conditions option. Closes #8034 *Jack Danger Canty, Josh Peek, Rick Olson*
+
+* belongs_to assignment creates a new proxy rather than modifying its target in-place. #8412 *mmangino@elevatedrails.com*
+
+* Fix column type detection while loading fixtures. Closes #7987 *roderickvd*
+
+* Document deep eager includes. #6267 *Josh Susser, Dan Manges*
+
+* Document warning that associations names shouldn't be reserved words. #4378 *murphy@cYcnus.de, Josh Susser*
+
+* Sanitize Base#inspect. #8392, #8623 *Nik Wakelin, jnoon*
+
+* Replace the transaction {|transaction|..} semantics with a new Exception ActiveRecord::Rollback. *Michael Koziarski*
+
+* Oracle: extract column length for CHAR also. #7866 *ymendel*
+
+* Document :allow_nil option for validates_acceptance_of since it defaults to true. *tzaharia*
+
+* Update documentation for :dependent declaration so that it explicitly uses the non-deprecated API. *Jack Danger Canty*
+
+* Add documentation caveat about when to use count_by_sql. *fearoffish*
+
+* Enhance documentation for increment_counter and decrement_counter. *fearoffish*
+
+* Provide brief introduction to what optimistic locking is. *fearoffish*
+
+* Add documentation for :encoding option to mysql adapter. *marclove*
+
+* Added short-hand declaration style to migrations (inspiration from Sexy Migrations, http://errtheblog.com/post/2381) [David Heinemeier Hansson]. Example:
+
+ create_table "products" do |t|
+ t.column "shop_id", :integer
+ t.column "creator_id", :integer
+ t.column "name", :string, :default => "Untitled"
+ t.column "value", :string, :default => "Untitled"
+ t.column "created_at", :datetime
+ t.column "updated_at", :datetime
+ end
+
+ ...can now be written as:
+
+ create_table :products do |t|
+ t.integer :shop_id, :creator_id
+ t.string :name, :value, :default => "Untitled"
+ t.timestamps
+ end
+
+* Use association name for the wrapper element when using .to_xml. Previous behavior lead to non-deterministic situations with STI and polymorphic associations. *Michael Koziarski, jstrachan*
+
+* Improve performance of calling .create on has_many :through associations. *evan*
+
+* Improved cloning performance by relying less on exception raising #8159 *Blaine*
+
+* Added ActiveRecord::Base.inspect to return a column-view like #<Post id:integer, title:string, body:text> *David Heinemeier Hansson*
+
+* Added yielding of Builder instance for ActiveRecord::Base#to_xml calls *David Heinemeier Hansson*
+
+* Small additions and fixes for ActiveRecord documentation. Closes #7342 *Jeremy McAnally*
+
+* Add helpful debugging info to the ActiveRecord::StatementInvalid exception in ActiveRecord::ConnectionAdapters::SqliteAdapter#table_structure. Closes #7925. *court3nay*
+
+* SQLite: binary escaping works with $KCODE='u'. #7862 *tsuka*
+
+* Base#to_xml supports serialized attributes. #7502 *jonathan*
+
+* Base.update_all :order and :limit options. Useful for MySQL updates that must be ordered to avoid violating unique constraints. *Jeremy Kemper*
+
+* Remove deprecated object transactions. People relying on this functionality should install the object_transactions plugin at http://code.bitsweat.net/svn/object_transactions. Closes #5637 *Michael Koziarski, Jeremy Kemper*
+
+* PostgreSQL: remove DateTime -> Time downcast. Warning: do not enable translate_results for the C bindings if you have timestamps outside Time's domain. *Jeremy Kemper*
+
+* find_or_create_by_* takes a hash so you can create with more attributes than are in the method name. For example, Person.find_or_create_by_name(:name => 'Henry', :comments => 'Hi new user!') is equivalent to Person.find_by_name('Henry') || Person.create(:name => 'Henry', :comments => 'Hi new user!'). #7368 *Josh Susser*
+
+* Make sure with_scope takes both :select and :joins into account when setting :readonly. Allows you to save records you retrieve using method_missing on a has_many :through associations. *Michael Koziarski*
+
+* Allow a polymorphic :source for has_many :through associations. Closes #7143 *protocool*
+
+* Consistent public/protected/private visibility for chained methods. #7813 *Dan Manges*
+
+* Oracle: fix quoted primary keys and datetime overflow. #7798 *Michael Schoen*
+
+* Consistently quote primary key column names. #7763 *toolmantim*
+
+* Fixtures: fix YAML ordered map support. #2665 *Manuel Holtgrewe, nfbuckley*
+
+* DateTimes assume the default timezone. #7764 *Geoff Buesing*
+
+* Sybase: hide timestamp columns since they're inherently read-only. #7716 *Mike Joyce*
+
+* Oracle: overflow Time to DateTime. #7718 *Michael Schoen*
+
+* PostgreSQL: don't use async_exec and async_query with postgres-pr. #7727, #7762 *flowdelic, toolmantim*
+
+* Fix has_many :through << with custom foreign keys. #6466, #7153 *naffis, Rich Collins*
+
+* Test DateTime native type in migrations, including an edge case with dates
+ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
+* SQLServer: correctly schema-dump tables with no indexes or descending indexes. #7333, #7703 *Jakob Skjerning, Tom Ward*
+
+* SQLServer: recognize real column type as Ruby float. #7057 *sethladd, Tom Ward*
+
+* Added fixtures :all as a way of loading all fixtures in the fixture directory at once #7214 *Manfred Stienstra*
+
+* Added database connection as a yield parameter to ActiveRecord::Base.transaction so you can manually rollback [David Heinemeier Hansson]. Example:
+
+ transaction do |transaction|
+ david.withdrawal(100)
+ mary.deposit(100)
+ transaction.rollback! # rolls back the transaction that was otherwise going to be successful
+ end
+
+* Made increment_counter/decrement_counter play nicely with optimistic locking, and added a more general update_counters method *Jamis Buck*
+
+* Reworked David's query cache to be available as Model.cache {...}. For the duration of the block no select query should be run more then once. Any inserts/deletes/executes will flush the whole cache however *Tobias Lütke*
+ Task.cache { Task.find(1); Task.find(1) } # => 1 query
+
+* When dealing with SQLite3, use the table_info pragma helper, so that the bindings can do some translation for when sqlite3 breaks incompatibly between point releases. *Jamis Buck*
+
+* Oracle: fix lob and text default handling. #7344 *gfriedrich, Michael Schoen*
+
+* SQLServer: don't choke on strings containing 'null'. #7083 *Jakob Skjerning*
+
+* MySQL: blob and text columns may not have defaults in 5.x. Update fixtures schema for strict mode. #6695 *Dan Kubb*
+
+* update_all can take a Hash argument. sanitize_sql splits into two methods for conditions and assignment since NULL values and delimiters are handled differently. #6583, #7365 *sandofsky, Assaf*
+
+* MySQL: SET SQL_AUTO_IS_NULL=0 so 'where id is null' doesn't select the last inserted id. #6778 *Jonathan Viney, timc*
+
+* Use Date#to_s(:db) for quoted dates. #7411 *Michael Schoen*
+
+* Don't create instance writer methods for class attributes. Closes #7401 *Rick Olson*
+
+* Docs: validations examples. #7343 *zackchandler*
+
+* Add missing tests ensuring callbacks work with class inheritance. Closes #7339 *sandofsky*
+
+* Fixtures use the table name and connection from set_fixture_class. #7330 *Anthony Eden*
+
+* Remove useless code in #attribute_present? since 0 != blank?. Closes #7249 *Josh Susser*
+
+* Fix minor doc typos. Closes #7157 *Josh Susser*
+
+* Fix incorrect usage of #classify when creating the eager loading join statement. Closes #7044 *Josh Susser*
+
+* SQLServer: quote table name in indexes query. #2928 *keithm@infused.org*
+
+* Subclasses of an abstract class work with single-table inheritance. #5704, #7284 *BertG, nick+rails@ag.arizona.edu*
+
+* Make sure sqlite3 driver closes open connections on disconnect *Rob Rasmussen*
+
+* [DOC] clear up some ambiguity with the way has_and_belongs_to_many creates the default join table name. #7072 *Jeremy McAnally*
+
+* change_column accepts :default => nil. Skip column options for primary keys. #6956, #7048 *Dan Manges, Jeremy Kemper*
+
+* MySQL, PostgreSQL: change_column_default quotes the default value and doesn't lose column type information. #3987, #6664 *Jonathan Viney, Manfred Stienstra, altano@bigfoot.com*
+
+* Oracle: create_table takes a :sequence_name option to override the 'tablename_seq' default. #7000 *Michael Schoen*
+
+* MySQL: retain SSL settings on reconnect. #6976 *randyv2*
+
+* Apply scoping during initialize instead of create. Fixes setting of foreign key when using find_or_initialize_by with scoping. *Cody Fauser*
+
+* SQLServer: handle [quoted] table names. #6635 *rrich*
+
+* acts_as_nested_set works with single-table inheritance. #6030 *Josh Susser*
+
+* PostgreSQL, Oracle: correctly perform eager finds with :limit and :order. #4668, #7021 *eventualbuddha, Michael Schoen*
+
+* Pass a range in :conditions to use the SQL BETWEEN operator. #6974 *Dan Manges*
+ Student.find(:all, :conditions => { :grade => 9..12 })
+
+* Fix the Oracle adapter for serialized attributes stored in CLOBs. Closes #6825 *mschoen, tdfowler*
+
+* [DOCS] Apply more documentation for ActiveRecord Reflection. Closes #4055 *Robby Russell*
+
+* [DOCS] Document :allow_nil option of #validate_uniqueness_of. Closes #3143 *Caio Chassot*
+
+* Bring the sybase adapter up to scratch for 1.2 release. *jsheets*
+
+* Rollback new_record? and id when an exception is raised in a save callback. #6910 *Ben Curren, outerim*
+
+* Pushing a record on an association collection doesn't unnecessarily load all the associated records. *Obie Fernandez, Jeremy Kemper*
+
+* Oracle: fix connection reset failure. #6846 *leonlleslie*
+
+* Subclass instantiation doesn't try to explicitly require the corresponding subclass. #6840 *leei, Jeremy Kemper*
+
+* fix faulty inheritance tests and that eager loading grabs the wrong inheritance column when the class of your association is an STI subclass. Closes #6859 *protocool*
+
+* Consolidated different create and create! versions to call through to the base class with scope. This fixes inconsistencies, especially related to protected attribtues. Closes #5847 *Alexander Dymo, Tobias Lütke*
+
+* find supports :lock with :include. Check whether your database allows SELECT ... FOR UPDATE with outer joins before using. #6764 *vitaly, Jeremy Kemper*
+
+* Add AssociationCollection#create! to be consistent with AssociationCollection#create when dealing with a foreign key that is a protected attribute *Cody Fauser*
+
+* Added counter optimization for AssociationCollection#any? so person.friends.any? won't actually load the full association if we have the count in a cheaper form *David Heinemeier Hansson*
+
+* Change fixture_path to a class inheritable accessor allowing test cases to have their own custom set of fixtures. #6672 *Zach Dennis*
+
+* Quote ActiveSupport::Multibyte::Chars. #6653 *Julian Tarkhanov*
+
+* Simplify query_attribute by typecasting the attribute value and checking whether it's nil, false, zero or blank. #6659 *Jonathan Viney*
+
+* validates_numericality_of uses \A \Z to ensure the entire string matches rather than ^ $ which may match one valid line of a multiline string. #5716 *Andreas Schwarz*
+
+* Run validations in the order they were declared. #6657 *obrie*
+
+* MySQL: detect when a NOT NULL column without a default value is misreported as default ''. Can't detect for string, text, and binary columns since '' is a legitimate default. #6156 *simon@redhillconsulting.com.au, obrie, Jonathan Viney, Jeremy Kemper*
+
+* Simplify association proxy implementation by factoring construct_scope out of method_missing. #6643 *martin*
+
+* Oracle: automatically detect the primary key. #6594 *vesaria, Michael Schoen*
+
+* Oracle: to increase performance, prefetch 100 rows and enable similar cursor sharing. Both are configurable in database.yml. #6607 *philbogle@gmail.com, ray.fortna@jobster.com, Michael Schoen*
+
+* Don't inspect unloaded associations. #2905 *lmarlow*
+
+* SQLite: use AUTOINCREMENT primary key in >= 3.1.0. #6588, #6616 *careo, lukfugl*
+
+* Cache inheritance_column. #6592 *Stefan Kaes*
+
+* Firebird: decimal/numeric support. #6408 *macrnic*
+
+* make add_order a tad faster. #6567 *Stefan Kaes*
+
+* Find with :include respects scoped :order. #5850
+
+* Support nil and Array in :conditions => { attr => value } hashes. #6548 *Assaf, Jeremy Kemper*
+ find(:all, :conditions => { :topic_id => [1, 2, 3], :last_read => nil }
+
+* Consistently use LOWER() for uniqueness validations (rather than mixing with UPPER()) so the database can always use a functional index on the lowercased column. #6495 *Si*
+
+* SQLite: fix calculations workaround, remove count(distinct) query rewrite, cleanup test connection scripts. *Jeremy Kemper*
+
+* SQLite: count(distinct) queries supported in >= 3.2.6. #6544 *Bob Silva*
+
+* Dynamically generate reader methods for serialized attributes. #6362 *Stefan Kaes*
+
+* Deprecation: object transactions warning. *Jeremy Kemper*
+
+* has_one :dependent => :nullify ignores nil associates. #4848, #6528 *bellis@deepthought.org, janovetz, Jeremy Kemper*
+
+* Oracle: resolve test failures, use prefetched primary key for inserts, check for null defaults, fix limited id selection for eager loading. Factor out some common methods from all adapters. #6515 *Michael Schoen*
+
+* Make add_column use the options hash with the Sqlite Adapter. Closes #6464 *obrie*
+
+* Document other options available to migration's add_column. #6419 *grg*
+
+* MySQL: all_hashes compatibility with old MysqlRes class. #6429, #6601 *Jeremy Kemper*
+
+* Fix has_many :through to add the appropriate conditions when going through an association using STI. Closes #5783. *Jonathan Viney*
+
+* fix select_limited_ids_list issues in postgresql, retain current behavior in other adapters *Rick Olson*
+
+* Restore eager condition interpolation, document it's differences *Rick Olson*
+
+* Don't rollback in teardown unless a transaction was started. Don't start a transaction in create_fixtures if a transaction is started. #6282 *Jacob Fugal, Jeremy Kemper*
+
+* Add #delete support to has_many :through associations. Closes #6049 *Martin Landers*
+
+* Reverted old select_limited_ids_list postgresql fix that caused issues in mysql. Closes #5851 *Rick Olson*
+
+* Removes the ability for eager loaded conditions to be interpolated, since there is no model instance to use as a context for interpolation. #5553 *turnip@turnipspatch.com*
+
+* Added timeout option to SQLite3 configurations to deal more gracefully with SQLite3::BusyException, now the connection can instead retry for x seconds to see if the db clears up before throwing that exception #6126 *wreese@gmail.com*
+
+* Added update_attributes! which uses save! to raise an exception if a validation error prevents saving #6192 *jonathan*
+
+* Deprecated add_on_boundary_breaking (use validates_length_of instead) #6292 *Bob Silva*
+
+* The has_many create method works with polymorphic associations. #6361 *Dan Peterson*
+
+* MySQL: introduce Mysql::Result#all_hashes to support further optimization. #5581 *Stefan Kaes*
+
+* save! shouldn't validate twice. #6324 *maiha, Bob Silva*
+
+* Association collections have an _ids reader method to match the existing writer for collection_select convenience (e.g. employee.task_ids). The writer method skips blank ids so you can safely do @employee.task_ids = params[:tasks] without checking every time for an empty list or blank values. #1887, #5780 *Michael Schuerig*
+
+* Add an attribute reader method for ActiveRecord::Base.observers *Rick Olson*
+
+* Deprecation: count class method should be called with an options hash rather than two args for conditions and joins. #6287 *Bob Silva*
+
+* has_one associations with a nil target may be safely marshaled. #6279 *norbauer, Jeremy Kemper*
+
+* Duplicate the hash provided to AR::Base#to_xml to prevent unexpected side effects *Michael Koziarski*
+
+* Add a :namespace option to AR::Base#to_xml *Michael Koziarski*
+
+* Deprecation tests. Remove warnings for dynamic finders and for the foo_count method if it's also an attribute. *Jeremy Kemper*
+
+* Mock Time.now for more accurate Touch mixin tests. #6213 *Dan Peterson*
+
+* Improve yaml fixtures error reporting. #6205 *Bruce Williams*
+
+* Rename AR::Base#quote so people can use that name in their models. #3628 *Michael Koziarski*
+
+* Add deprecation warning for inferred foreign key. #6029 *Josh Susser*
+
+* Fixed the Ruby/MySQL adapter we ship with Active Record to work with the new authentication handshake that was introduced in MySQL 4.1, along with the other protocol changes made at that time #5723 *jimw@mysql.com*
+
+* Deprecation: use :dependent => :delete_all rather than :exclusively_dependent => true. #6024 *Josh Susser*
+
+* Document validates_presences_of behavior with booleans: you probably want validates_inclusion_of :attr, :in => [true, false]. #2253 *Bob Silva*
+
+* Optimistic locking: gracefully handle nil versions, treat as zero. #5908 *Tom Ward*
+
+* to_xml: the :methods option works on arrays of records. #5845 *Josh Starcher*
+
+* Deprecation: update docs. #5998 *Jakob Skjerning, Kevin Clark*
+
+* Add some XmlSerialization tests for ActiveRecord *Rick Olson*
+
+* has_many :through conditions are sanitized by the associating class. #5971 *martin.emde@gmail.com*
+
+* Tighten rescue clauses. #5985 *james@grayproductions.net*
+
+* Fix spurious newlines and spaces in AR::Base#to_xml output *Jamis Buck*
+
+* has_one supports the :dependent => :delete option which skips the typical callback chain and deletes the associated object directly from the database. #5927 *Chris Mear, Jonathan Viney*
+
+* Nested subclasses are not prefixed with the parent class' table_name since they should always use the base class' table_name. #5911 *Jonathan Viney*
+
+* SQLServer: work around bug where some unambiguous date formats are not correctly identified if the session language is set to german. #5894 *Tom Ward, kruth@bfpi*
+
+* SQLServer: fix eager association test. #5901 *Tom Ward*
+
+* Clashing type columns due to a sloppy join shouldn't wreck single-table inheritance. #5838 *Kevin Clark*
+
+* Fixtures: correct escaping of \n and \r. #5859 *evgeny.zislis@gmail.com*
+
+* Migrations: gracefully handle missing migration files. #5857 *eli.gordon@gmail.com*
+
+* MySQL: update test schema for MySQL 5 strict mode. #5861 *Tom Ward*
+
+* to_xml: correct naming of included associations. #5831 *Josh Starcher*
+
+* Pushing a record onto a has_many :through sets the association's foreign key to the associate's primary key and adds it to the correct association. #5815, #5829 *Josh Susser*
+
+* Add records to has_many :through using <<, push, and concat by creating the association record. Raise if base or associate are new records since both ids are required to create the association. #build raises since you can't associate an unsaved record. #create! takes an attributes hash and creates the associated record and its association in a transaction. *Jeremy Kemper*
+
+ # Create a tagging to associate the post and tag.
+ post.tags << Tag.find_by_name('old')
+ post.tags.create! :name => 'general'
+
+ # Would have been:
+ post.taggings.create!(:tag => Tag.find_by_name('finally')
+ transaction do
+ post.taggings.create!(:tag => Tag.create!(:name => 'general'))
+ end
+
+* Cache nil results for :included has_one associations also. #5787 *Michael Schoen*
+
+* Fixed a bug which would cause .save to fail after trying to access a empty has_one association on a unsaved record. *Tobias Lütke*
+
+* Nested classes are given table names prefixed by the singular form of the parent's table name. *Jeremy Kemper*
+ Example: Invoice::Lineitem is given table name invoice_lineitems
+
+* Migrations: uniquely name multicolumn indexes so you don't have to. *Jeremy Kemper*
+ # people_active_last_name_index, people_active_deactivated_at_index
+ add_index :people, [:active, :last_name]
+ add_index :people, [:active, :deactivated_at]
+ remove_index :people, [:active, :last_name]
+ remove_index :people, [:active, :deactivated_at]
+
+ WARNING: backward-incompatibility. Multicolumn indexes created before this
+ revision were named using the first column name only. Now they're uniquely
+ named using all indexed columns.
+
+ To remove an old multicolumn index, remove_index :table_name, :first_column
+
+* Fix for deep includes on the same association. *richcollins@gmail.com*
+
+* Tweak fixtures so they don't try to use a non-ActiveRecord class. *Kevin Clark*
+
+* Remove ActiveRecord::Base.reset since Dispatcher doesn't use it anymore. *Rick Olson*
+
+* Document find's :from option. Closes #5762. *andrew@redlinesoftware.com*
+
+* PostgreSQL: autodetected sequences work correctly with multiple schemas. Rely on the schema search_path instead of explicitly qualifying the sequence name with its schema. #5280 *guy.naor@famundo.com*
+
+* Replace Reloadable with Reloadable::Deprecated. *Nicholas Seckar*
+
+* Cache nil results for has_one associations so multiple calls don't call the database. Closes #5757. *Michael Schoen*
+
+* Add documentation for how to disable timestamps on a per model basis. Closes #5684. *matt@mattmargolis.net Marcel Molina Jr.*
+
+* Don't save has_one associations unnecessarily. #5735 *Jonathan Viney*
+
+* Refactor ActiveRecord::Base.reset_subclasses to #reset, and add global observer resetting. *Rick Olson*
+
+* Formally deprecate the deprecated finders. *Michael Koziarski*
+
+* Formally deprecate rich associations. *Michael Koziarski*
+
+* Fixed that default timezones for new / initialize should uphold utc setting #5709 *daniluk@yahoo.com*
+
+* Fix announcement of very long migration names. #5722 *blake@near-time.com*
+
+* The exists? class method should treat a string argument as an id rather than as conditions. #5698 *jeremy@planetargon.com*
+
+* Fixed to_xml with :include misbehaviors when invoked on array of model instances #5690 *alexkwolfe@gmail.com*
+
+* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
+
+ assert (Topic.exists?(:author_name => "David"))
+ assert (Topic.exists?(:author_name => "Mary", :approved => true))
+ assert (Topic.exists?(["parent_id = ?", 1]))
+
+* Schema dumper quotes date :default values. *Dave Thomas*
+
+* Calculate sum with SQL, not Enumerable on HasManyThrough Associations. *Dan Peterson*
+
+* Factor the attribute#{suffix} methods out of method_missing for easier extension. *Jeremy Kemper*
+
+* Patch sql injection vulnerability when using integer or float columns. *Jamis Buck*
+
+* Allow #count through a has_many association to accept :include. *Dan Peterson*
+
+* create_table rdoc: suggest :id => false for habtm join tables. *Zed Shaw*
+
+* PostgreSQL: return array fields as strings. #4664 *Robby Russell*
+
+* SQLServer: added tests to ensure all database statements are closed, refactored identity_insert management code to use blocks, removed update/delete rowcount code out of execute and into update/delete, changed insert to go through execute method, removed unused quoting methods, disabled pessimistic locking tests as feature is currently unsupported, fixed RakeFile to load sqlserver specific tests whether running in ado or odbc mode, fixed support for recently added decimal types, added support for limits on integer types. #5670 *Tom Ward*
+
+* SQLServer: fix db:schema:dump case-sensitivity. #4684 *Will Rogers*
+
+* Oracle: BigDecimal support. #5667 *Michael Schoen*
+
+* Numeric and decimal columns map to BigDecimal instead of Float. Those with scale 0 map to Integer. #5454 *robbat2@gentoo.org, work@ashleymoran.me.uk*
+
+* Firebird migrations support. #5337 *Ken Kunz <kennethkunz@gmail.com>*
+
+* PostgreSQL: create/drop as postgres user. #4790 *mail@matthewpainter.co.uk, mlaster@metavillage.com*
+
+* Update callbacks documentation. #3970 *Robby Russell <robby@planetargon.com>*
+
+* PostgreSQL: correctly quote the ' in pk_and_sequence_for. #5462 *tietew@tietew.net*
+
+* PostgreSQL: correctly quote microseconds in timestamps. #5641 *rick@rickbradley.com*
+
+* Clearer has_one/belongs_to model names (account has_one :user). #5632 *matt@mattmargolis.net*
+
+* Oracle: use nonblocking queries if allow_concurrency is set, fix pessimistic locking, don't guess date vs. time by default (set OracleAdapter.emulate_dates = true for the old behavior), adapter cleanup. #5635 *Michael Schoen*
+
+* Fixed a few Oracle issues: Allows Oracle's odd date handling to still work consistently within #to_xml, Passes test that hardcode insert statement by dropping the :id column, Updated RUNNING_UNIT_TESTS with Oracle instructions, Corrects method signature for #exec #5294 *Michael Schoen*
+
+* Added :group to available options for finds done on associations #5516 *mike@michaeldewey.org*
+
+* Minor tweak to improve performance of ActiveRecord::Base#to_param.
+
+* Observers also watch subclasses created after they are declared. #5535 *daniels@pronto.com.au*
+
+* Removed deprecated timestamps_gmt class methods. *Jeremy Kemper*
+
+* rake build_mysql_database grants permissions to rails@localhost. #5501 *brianegge@yahoo.com*
+
+* PostgreSQL: support microsecond time resolution. #5492 *alex@msgpad.com*
+
+* Add AssociationCollection#sum since the method_missing invokation has been shadowed by Enumerable#sum.
+
+* Added find_or_initialize_by_X which works like find_or_create_by_X but doesn't save the newly instantiated record. *Sam Stephenson*
+
+* Row locking. Provide a locking clause with the :lock finder option or true for the default "FOR UPDATE". Use the #lock! method to obtain a row lock on a single record (reloads the record with :lock => true). *Shugo Maeda*
+ # Obtain an exclusive lock on person 1 so we can safely increment visits.
+ Person.transaction do
+ # select * from people where id=1 for update
+ person = Person.find(1, :lock => true)
+ person.visits += 1
+ person.save!
+ end
+
+* PostgreSQL: introduce allow_concurrency option which determines whether to use blocking or asynchronous #execute. Adapters with blocking #execute will deadlock Ruby threads. The default value is ActiveRecord::Base.allow_concurrency. *Jeremy Kemper*
+
+* Use a per-thread (rather than global) transaction mutex so you may execute concurrent transactions on separate connections. *Jeremy Kemper*
+
+* Change AR::Base#to_param to return a String instead of a Fixnum. Closes #5320. *Nicholas Seckar*
+
+* Use explicit delegation instead of method aliasing for AR::Base.to_param -> AR::Base.id. #5299 (skaes@web.de)
+
+* Refactored ActiveRecord::Base.to_xml to become a delegate for XmlSerializer, which restores sanity to the mega method. This refactoring also reinstates the opinions that type="string" is redundant and ugly and nil-differentiation is not a concern of serialization *David Heinemeier Hansson*
+
+* Added simple hash conditions to find that'll just convert hash to an AND-based condition string #5143 [Hampton Catlin]. Example:
+
+ Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2)
+
+ ...is the same as:
+ Person.find(:all, :conditions => [ "last_name = ? and status = ?", "Catlin", 1 ], :limit => 2)
+
+ This makes it easier to pass in the options from a form or otherwise outside.
+
+
+* Fixed issues with BLOB limits, charsets, and booleans for Firebird #5194, #5191, #5189 *kennethkunz@gmail.com*
+
+* Fixed usage of :limit and with_scope when the association in scope is a 1:m #5208 *alex@purefiction.net*
+
+* Fixed migration trouble with SQLite when NOT NULL is used in the new definition #5215 *greg@lapcominc.com*
+
+* Fixed problems with eager loading and counting on SQL Server #5212 *kajism@yahoo.com*
+
+* Fixed that count distinct should use the selected column even when using :include #5251 *anna@wota.jp*
+
+* Fixed that :includes merged from with_scope won't cause the same association to be loaded more than once if repetition occurs in the clauses #5253 *alex@purefiction.net*
+
+* Allow models to override to_xml. #4989 *Blair Zajac <blair@orcaware.com>*
+
+* PostgreSQL: don't ignore port when host is nil since it's often used to label the domain socket. #5247 *shimbo@is.naist.jp*
+
+* Records and arrays of records are bound as quoted ids. *Jeremy Kemper*
+ Foo.find(:all, :conditions => ['bar_id IN (?)', bars])
+ Foo.find(:first, :conditions => ['bar_id = ?', bar])
+
+* Fixed that Base.find :all, :conditions => [ "id IN (?)", collection ] would fail if collection was empty *David Heinemeier Hansson*
+
+* Add a list of regexes assert_queries skips in the ActiveRecord test suite. *Rick Olson*
+
+* Fix the has_and_belongs_to_many #create doesn't populate the join for new records. Closes #3692 *Josh Susser*
+
+* Provide Association Extensions access to the instance that the association is being accessed from.
+ Closes #4433 *Josh Susser*
+
+* Update OpenBase adaterp's maintainer's email address. Closes #5176. *Derrick Spell*
+
+* Add a quick note about :select and eagerly included associations. *Rick Olson*
+
+* Add docs for the :as option in has_one associations. Closes #5144 *cdcarter@gmail.com*
+
+* Fixed that has_many collections shouldn't load the entire association to do build or create *David Heinemeier Hansson*
+
+* Added :allow_nil option for aggregations #5091 *Ian White*
+
+* Fix Oracle boolean support and tests. Closes #5139. *Michael Schoen*
+
+* create! no longer blows up when no attributes are passed and a :create scope is in effect (e.g. foo.bars.create! failed whereas foo.bars.create!({}) didn't.) *Jeremy Kemper*
+
+* Call Inflector#demodulize on the class name when eagerly including an STI model. Closes #5077 *info@loobmedia.com*
+
+* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. *pdcawley@bofh.org.uk*
+
+* PostgreSQL: migrations support :limit with :integer columns by mapping limit < 4 to smallint, > 4 to bigint, and anything else to integer. #2900 *keegan@thebasement.org*
+
+* Dates and times interpret empty strings as nil rather than 2000-01-01. #4830 *kajism@yahoo.com*
+
+* Allow :uniq => true with has_many :through associations. *Jeremy Kemper*
+
+* Ensure that StringIO is always available for the Schema dumper. *Marcel Molina Jr.*
+
+* Allow AR::Base#to_xml to include methods too. Closes #4921. *johan@textdrive.com*
+
+* Replace superfluous name_to_class_name variant with camelize. *Marcel Molina Jr.*
+
+* Replace alias method chaining with Module#alias_method_chain. *Marcel Molina Jr.*
+
+* Replace Ruby's deprecated append_features in favor of included. *Marcel Molina Jr.*
+
+* Remove duplicate fixture entry in comments.yml. Closes #4923. *Blair Zajac <blair@orcaware.com>*
+
+* Update FrontBase adapter to check binding version. Closes #4920. *mlaster@metavillage.com*
+
+* New Frontbase connections don't start in auto-commit mode. Closes #4922. *mlaster@metavillage.com*
+
+* When grouping, use the appropriate option key. *Marcel Molina Jr.*
+
+* Only modify the sequence name in the FrontBase adapter if the FrontBase adapter is actually being used. *Marcel Molina Jr.*
+
+* Add support for FrontBase (http://www.frontbase.com/) with a new adapter thanks to the hard work of one Mike Laster. Closes #4093. *mlaster@metavillage.com*
+
+* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. *Francois Beausoleil <francois.beausoleil@gmail.com>*
+
+* Fix syntax error in documentation. Closes #4679. *Mislav Marohnić*
+
+* Add Oracle support for CLOB inserts. Closes #4748. *schoenm@earthlink.net sandra.metz@duke.edu*
+
+* Various fixes for sqlserver_adapter (odbc statement finishing, ado schema dumper, drop index). Closes #4831. *kajism@yahoo.com*
+
+* Add support for :order option to with_scope. Closes #3887. *eric.daspet@survol.net*
+
+* Prettify output of schema_dumper by making things line up. Closes #4241 *Caio Chassot <caio@v2studio.com>*
+
+* Make build_postgresql_databases task make databases owned by the postgres user. Closes #4790. *mlaster@metavillage.com*
+
+* Sybase Adapter type conversion cleanup. Closes #4736. *dev@metacasa.net*
+
+* Fix bug where calculations with long alias names return null. *Rick Olson*
+
+* Raise error when trying to add to a has_many :through association. Use the Join Model instead. *Rick Olson*
+
+ @post.tags << @tag # BAD
+ @post.taggings.create(:tag => @tag) # GOOD
+
+* Allow all calculations to take the :include option, not just COUNT (closes #4840) *Rick Olson*
+
+* Update inconsistent migrations documentation. #4683 *machomagna@gmail.com*
+
+* Add ActiveRecord::Errors#to_xml *Jamis Buck*
+
+* Properly quote index names in migrations (closes #4764) *John Long*
+
+* Fix the HasManyAssociation#count method so it uses the new ActiveRecord::Base#count syntax, while maintaining backwards compatibility. *Rick Olson*
+
+* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions *Rick Olson*
+
+* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. *Rick Olson*
+
+* DRY up association collection reader method generation. *Marcel Molina Jr.*
+
+* DRY up and tweak style of the validation error object. *Marcel Molina Jr.*
+
+* Add :case_sensitive option to validates_uniqueness_of (closes #3090) *Rick Olson*
+
+ class Account < ActiveRecord::Base
+ validates_uniqueness_of :email, :case_sensitive => false
+ end
+
+* Allow multiple association extensions with :extend option (closes #4666) *Josh Susser*
+
+ class Account < ActiveRecord::Base
+ has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
+ end
+
+ *1.15.3* (March 12th, 2007)
+
+ * Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]
+
+ * Consistently quote primary key column names. #7763 [toolmantim]
+
+ * Fixtures: fix YAML ordered map support. #2665 [Manuel Holtgrewe, nfbuckley]
+
+ * Fix has_many :through << with custom foreign keys. #6466, #7153 [naffis, Rich Collins]
+
+
+## 1.15.2 (February 5th, 2007) ##
+
+* Pass a range in :conditions to use the SQL BETWEEN operator. #6974 *Dan Manges*
+ Student.find(:all, :conditions => { :grade => 9..12 })
+
+* Don't create instance writer methods for class attributes. *Rick Olson*
+
+* When dealing with SQLite3, use the table_info pragma helper, so that the bindings can do some translation for when sqlite3 breaks incompatibly between point releases. *Jamis Buck*
+
+* SQLServer: don't choke on strings containing 'null'. #7083 *Jakob Skjerning*
+
+* Consistently use LOWER() for uniqueness validations (rather than mixing with UPPER()) so the database can always use a functional index on the lowercased column. #6495 *Si*
+
+* MySQL: SET SQL_AUTO_IS_NULL=0 so 'where id is null' doesn't select the last inserted id. #6778 *Jonathan Viney, timc*
+
+* Fixtures use the table name and connection from set_fixture_class. #7330 *Anthony Eden*
+
+* SQLServer: quote table name in indexes query. #2928 *keithm@infused.org*
+
+
+## 1.15.1 (January 17th, 2007) ##
+
+* Fix nodoc breaking of adapters
+
+
+## 1.15.0 (January 16th, 2007) ##
+
+* [DOC] clear up some ambiguity with the way has_and_belongs_to_many creates the default join table name. #7072 *Jeremy McAnally*
+
+* change_column accepts :default => nil. Skip column options for primary keys. #6956, #7048 *Dan Manges, Jeremy Kemper*
+
+* MySQL, PostgreSQL: change_column_default quotes the default value and doesn't lose column type information. #3987, #6664 *Jonathan Viney, Manfred Stienstra, altano@bigfoot.com*
+
+* Oracle: create_table takes a :sequence_name option to override the 'tablename_seq' default. #7000 *Michael Schoen*
+
+* MySQL: retain SSL settings on reconnect. #6976 *randyv2*
+
+* SQLServer: handle [quoted] table names. #6635 *rrich*
+
+* acts_as_nested_set works with single-table inheritance. #6030 *Josh Susser*
+
+* PostgreSQL, Oracle: correctly perform eager finds with :limit and :order. #4668, #7021 *eventualbuddha, Michael Schoen*
+
+* Fix the Oracle adapter for serialized attributes stored in CLOBs. Closes #6825 *mschoen, tdfowler*
+
+* [DOCS] Apply more documentation for ActiveRecord Reflection. Closes #4055 *Robby Russell*
+
+* [DOCS] Document :allow_nil option of #validate_uniqueness_of. Closes #3143 *Caio Chassot*
+
+* Bring the sybase adapter up to scratch for 1.2 release. *jsheets*
+
+* Oracle: fix connection reset failure. #6846 *leonlleslie*
+
+* Subclass instantiation doesn't try to explicitly require the corresponding subclass. #6840 *leei, Jeremy Kemper*
+
+* fix faulty inheritance tests and that eager loading grabs the wrong inheritance column when the class of your association is an STI subclass. Closes #6859 *protocool*
+
+* find supports :lock with :include. Check whether your database allows SELECT ... FOR UPDATE with outer joins before using. #6764 *vitaly, Jeremy Kemper*
+
+* Support nil and Array in :conditions => { attr => value } hashes. #6548 *Assaf, Jeremy Kemper*
+ find(:all, :conditions => { :topic_id => [1, 2, 3], :last_read => nil }
+
+* Quote ActiveSupport::Multibyte::Chars. #6653 *Julian Tarkhanov*
+
+* MySQL: detect when a NOT NULL column without a default value is misreported as default ''. Can't detect for string, text, and binary columns since '' is a legitimate default. #6156 *simon@redhillconsulting.com.au, obrie, Jonathan Viney, Jeremy Kemper*
+
+* validates_numericality_of uses \A \Z to ensure the entire string matches rather than ^ $ which may match one valid line of a multiline string. #5716 *Andreas Schwarz*
+
+* Oracle: automatically detect the primary key. #6594 *vesaria, Michael Schoen*
+
+* Oracle: to increase performance, prefetch 100 rows and enable similar cursor sharing. Both are configurable in database.yml. #6607 *philbogle@gmail.com, ray.fortna@jobster.com, Michael Schoen*
+
+* Firebird: decimal/numeric support. #6408 *macrnic*
+
+* Find with :include respects scoped :order. #5850
+
+* Dynamically generate reader methods for serialized attributes. #6362 *Stefan Kaes*
+
+* Deprecation: object transactions warning. *Jeremy Kemper*
+
+* has_one :dependent => :nullify ignores nil associates. #6528 *janovetz, Jeremy Kemper*
+
+* Oracle: resolve test failures, use prefetched primary key for inserts, check for null defaults, fix limited id selection for eager loading. Factor out some common methods from all adapters. #6515 *Michael Schoen*
+
+* Make add_column use the options hash with the Sqlite Adapter. Closes #6464 *obrie*
+
+* Document other options available to migration's add_column. #6419 *grg*
+
+* MySQL: all_hashes compatibility with old MysqlRes class. #6429, #6601 *Jeremy Kemper*
+
+* Fix has_many :through to add the appropriate conditions when going through an association using STI. Closes #5783. *Jonathan Viney*
+
+* fix select_limited_ids_list issues in postgresql, retain current behavior in other adapters *Rick Olson*
+
+* Restore eager condition interpolation, document it's differences *Rick Olson*
+
+* Don't rollback in teardown unless a transaction was started. Don't start a transaction in create_fixtures if a transaction is started. #6282 *Jacob Fugal, Jeremy Kemper*
+
+* Add #delete support to has_many :through associations. Closes #6049 *Martin Landers*
+
+* Reverted old select_limited_ids_list postgresql fix that caused issues in mysql. Closes #5851 *Rick Olson*
+
+* Removes the ability for eager loaded conditions to be interpolated, since there is no model instance to use as a context for interpolation. #5553 *turnip@turnipspatch.com*
+
+* Added timeout option to SQLite3 configurations to deal more gracefully with SQLite3::BusyException, now the connection can instead retry for x seconds to see if the db clears up before throwing that exception #6126 *wreese@gmail.com*
+
+* Added update_attributes! which uses save! to raise an exception if a validation error prevents saving #6192 *jonathan*
+
+* Deprecated add_on_boundary_breaking (use validates_length_of instead) #6292 *Bob Silva*
+
+* The has_many create method works with polymorphic associations. #6361 *Dan Peterson*
+
+* MySQL: introduce Mysql::Result#all_hashes to support further optimization. #5581 *Stefan Kaes*
+
+* save! shouldn't validate twice. #6324 *maiha, Bob Silva*
+
+* Association collections have an _ids reader method to match the existing writer for collection_select convenience (e.g. employee.task_ids). The writer method skips blank ids so you can safely do @employee.task_ids = params[:tasks] without checking every time for an empty list or blank values. #1887, #5780 *Michael Schuerig*
+
+* Add an attribute reader method for ActiveRecord::Base.observers *Rick Olson*
+
+* Deprecation: count class method should be called with an options hash rather than two args for conditions and joins. #6287 *Bob Silva*
+
+* has_one associations with a nil target may be safely marshaled. #6279 *norbauer, Jeremy Kemper*
+
+* Duplicate the hash provided to AR::Base#to_xml to prevent unexpected side effects *Michael Koziarski*
+
+* Add a :namespace option to AR::Base#to_xml *Michael Koziarski*
+
+* Deprecation tests. Remove warnings for dynamic finders and for the foo_count method if it's also an attribute. *Jeremy Kemper*
+
+* Mock Time.now for more accurate Touch mixin tests. #6213 *Dan Peterson*
+
+* Improve yaml fixtures error reporting. #6205 *Bruce Williams*
+
+* Rename AR::Base#quote so people can use that name in their models. #3628 *Michael Koziarski*
+
+* Add deprecation warning for inferred foreign key. #6029 *Josh Susser*
+
+* Fixed the Ruby/MySQL adapter we ship with Active Record to work with the new authentication handshake that was introduced in MySQL 4.1, along with the other protocol changes made at that time #5723 *jimw@mysql.com*
+
+* Deprecation: use :dependent => :delete_all rather than :exclusively_dependent => true. #6024 *Josh Susser*
+
+* Optimistic locking: gracefully handle nil versions, treat as zero. #5908 *Tom Ward*
+
+* to_xml: the :methods option works on arrays of records. #5845 *Josh Starcher*
+
+* has_many :through conditions are sanitized by the associating class. #5971 *martin.emde@gmail.com*
+
+* Fix spurious newlines and spaces in AR::Base#to_xml output *Jamis Buck*
+
+* has_one supports the :dependent => :delete option which skips the typical callback chain and deletes the associated object directly from the database. #5927 *Chris Mear, Jonathan Viney*
+
+* Nested subclasses are not prefixed with the parent class' table_name since they should always use the base class' table_name. #5911 *Jonathan Viney*
+
+* SQLServer: work around bug where some unambiguous date formats are not correctly identified if the session language is set to german. #5894 *Tom Ward, kruth@bfpi*
+
+* Clashing type columns due to a sloppy join shouldn't wreck single-table inheritance. #5838 *Kevin Clark*
+
+* Fixtures: correct escaping of \n and \r. #5859 *evgeny.zislis@gmail.com*
+
+* Migrations: gracefully handle missing migration files. #5857 *eli.gordon@gmail.com*
+
+* MySQL: update test schema for MySQL 5 strict mode. #5861 *Tom Ward*
+
+* to_xml: correct naming of included associations. #5831 *Josh Starcher*
+
+* Pushing a record onto a has_many :through sets the association's foreign key to the associate's primary key and adds it to the correct association. #5815, #5829 *Josh Susser*
+
+* Add records to has_many :through using <<, push, and concat by creating the association record. Raise if base or associate are new records since both ids are required to create the association. #build raises since you can't associate an unsaved record. #create! takes an attributes hash and creates the associated record and its association in a transaction. *Jeremy Kemper*
+
+ # Create a tagging to associate the post and tag.
+ post.tags << Tag.find_by_name('old')
+ post.tags.create! :name => 'general'
+
+ # Would have been:
+ post.taggings.create!(:tag => Tag.find_by_name('finally')
+ transaction do
+ post.taggings.create!(:tag => Tag.create!(:name => 'general'))
+ end
+
+* Cache nil results for :included has_one associations also. #5787 *Michael Schoen*
+
+* Fixed a bug which would cause .save to fail after trying to access a empty has_one association on a unsaved record. *Tobias Lütke*
+
+* Nested classes are given table names prefixed by the singular form of the parent's table name. *Jeremy Kemper*
+ Example: Invoice::Lineitem is given table name invoice_lineitems
+
+* Migrations: uniquely name multicolumn indexes so you don't have to. *Jeremy Kemper*
+ # people_active_last_name_index, people_active_deactivated_at_index
+ add_index :people, [:active, :last_name]
+ add_index :people, [:active, :deactivated_at]
+ remove_index :people, [:active, :last_name]
+ remove_index :people, [:active, :deactivated_at]
+
+ WARNING: backward-incompatibility. Multicolumn indexes created before this
+ revision were named using the first column name only. Now they're uniquely
+ named using all indexed columns.
+
+ To remove an old multicolumn index, remove_index :table_name, :first_column
+
+* Fix for deep includes on the same association. *richcollins@gmail.com*
+
+* Tweak fixtures so they don't try to use a non-ActiveRecord class. *Kevin Clark*
+
+* Remove ActiveRecord::Base.reset since Dispatcher doesn't use it anymore. *Rick Olson*
+
+* PostgreSQL: autodetected sequences work correctly with multiple schemas. Rely on the schema search_path instead of explicitly qualifying the sequence name with its schema. #5280 *guy.naor@famundo.com*
+
+* Replace Reloadable with Reloadable::Deprecated. *Nicholas Seckar*
+
+* Cache nil results for has_one associations so multiple calls don't call the database. Closes #5757. *Michael Schoen*
+
+* Don't save has_one associations unnecessarily. #5735 *Jonathan Viney*
+
+* Refactor ActiveRecord::Base.reset_subclasses to #reset, and add global observer resetting. *Rick Olson*
+
+* Formally deprecate the deprecated finders. *Michael Koziarski*
+
+* Formally deprecate rich associations. *Michael Koziarski*
+
+* Fixed that default timezones for new / initialize should uphold utc setting #5709 *daniluk@yahoo.com*
+
+* Fix announcement of very long migration names. #5722 *blake@near-time.com*
+
+* The exists? class method should treat a string argument as an id rather than as conditions. #5698 *jeremy@planetargon.com*
+
+* Fixed to_xml with :include misbehaviors when invoked on array of model instances #5690 *alexkwolfe@gmail.com*
+
+* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
+
+ assert (Topic.exists?(:author_name => "David"))
+ assert (Topic.exists?(:author_name => "Mary", :approved => true))
+ assert (Topic.exists?(["parent_id = ?", 1]))
+
+* Schema dumper quotes date :default values. *Dave Thomas*
+
+* Calculate sum with SQL, not Enumerable on HasManyThrough Associations. *Dan Peterson*
+
+* Factor the attribute#{suffix} methods out of method_missing for easier extension. *Jeremy Kemper*
+
+* Patch sql injection vulnerability when using integer or float columns. *Jamis Buck*
+
+* Allow #count through a has_many association to accept :include. *Dan Peterson*
+
+* create_table rdoc: suggest :id => false for habtm join tables. *Zed Shaw*
+
+* PostgreSQL: return array fields as strings. #4664 *Robby Russell*
+
+* SQLServer: added tests to ensure all database statements are closed, refactored identity_insert management code to use blocks, removed update/delete rowcount code out of execute and into update/delete, changed insert to go through execute method, removed unused quoting methods, disabled pessimistic locking tests as feature is currently unsupported, fixed RakeFile to load sqlserver specific tests whether running in ado or odbc mode, fixed support for recently added decimal types, added support for limits on integer types. #5670 *Tom Ward*
+
+* SQLServer: fix db:schema:dump case-sensitivity. #4684 *Will Rogers*
+
+* Oracle: BigDecimal support. #5667 *Michael Schoen*
+
+* Numeric and decimal columns map to BigDecimal instead of Float. Those with scale 0 map to Integer. #5454 *robbat2@gentoo.org, work@ashleymoran.me.uk*
+
+* Firebird migrations support. #5337 *Ken Kunz <kennethkunz@gmail.com>*
+
+* PostgreSQL: create/drop as postgres user. #4790 *mail@matthewpainter.co.uk, mlaster@metavillage.com*
+
+* PostgreSQL: correctly quote the ' in pk_and_sequence_for. #5462 *tietew@tietew.net*
+
+* PostgreSQL: correctly quote microseconds in timestamps. #5641 *rick@rickbradley.com*
+
+* Clearer has_one/belongs_to model names (account has_one :user). #5632 *matt@mattmargolis.net*
+
+* Oracle: use nonblocking queries if allow_concurrency is set, fix pessimistic locking, don't guess date vs. time by default (set OracleAdapter.emulate_dates = true for the old behavior), adapter cleanup. #5635 *Michael Schoen*
+
+* Fixed a few Oracle issues: Allows Oracle's odd date handling to still work consistently within #to_xml, Passes test that hardcode insert statement by dropping the :id column, Updated RUNNING_UNIT_TESTS with Oracle instructions, Corrects method signature for #exec #5294 *Michael Schoen*
+
+* Added :group to available options for finds done on associations #5516 *mike@michaeldewey.org*
+
+* Observers also watch subclasses created after they are declared. #5535 *daniels@pronto.com.au*
+
+* Removed deprecated timestamps_gmt class methods. *Jeremy Kemper*
+
+* rake build_mysql_database grants permissions to rails@localhost. #5501 *brianegge@yahoo.com*
+
+* PostgreSQL: support microsecond time resolution. #5492 *alex@msgpad.com*
+
+* Add AssociationCollection#sum since the method_missing invokation has been shadowed by Enumerable#sum.
+
+* Added find_or_initialize_by_X which works like find_or_create_by_X but doesn't save the newly instantiated record. *Sam Stephenson*
+
+* Row locking. Provide a locking clause with the :lock finder option or true for the default "FOR UPDATE". Use the #lock! method to obtain a row lock on a single record (reloads the record with :lock => true). *Shugo Maeda*
+ # Obtain an exclusive lock on person 1 so we can safely increment visits.
+ Person.transaction do
+ # select * from people where id=1 for update
+ person = Person.find(1, :lock => true)
+ person.visits += 1
+ person.save!
+ end
+
+* PostgreSQL: introduce allow_concurrency option which determines whether to use blocking or asynchronous #execute. Adapters with blocking #execute will deadlock Ruby threads. The default value is ActiveRecord::Base.allow_concurrency. *Jeremy Kemper*
+
+* Use a per-thread (rather than global) transaction mutex so you may execute concurrent transactions on separate connections. *Jeremy Kemper*
+
+* Change AR::Base#to_param to return a String instead of a Fixnum. Closes #5320. *Nicholas Seckar*
+
+* Use explicit delegation instead of method aliasing for AR::Base.to_param -> AR::Base.id. #5299 (skaes@web.de)
+
+* Refactored ActiveRecord::Base.to_xml to become a delegate for XmlSerializer, which restores sanity to the mega method. This refactoring also reinstates the opinions that type="string" is redundant and ugly and nil-differentiation is not a concern of serialization *David Heinemeier Hansson*
+
+* Added simple hash conditions to find that'll just convert hash to an AND-based condition string #5143 [Hampton Catlin]. Example:
+
+ Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2)
+
+ ...is the same as:
+ Person.find(:all, :conditions => [ "last_name = ? and status = ?", "Catlin", 1 ], :limit => 2)
+
+ This makes it easier to pass in the options from a form or otherwise outside.
+
+
+* Fixed issues with BLOB limits, charsets, and booleans for Firebird #5194, #5191, #5189 *kennethkunz@gmail.com*
+
+* Fixed usage of :limit and with_scope when the association in scope is a 1:m #5208 *alex@purefiction.net*
+
+* Fixed migration trouble with SQLite when NOT NULL is used in the new definition #5215 *greg@lapcominc.com*
+
+* Fixed problems with eager loading and counting on SQL Server #5212 *kajism@yahoo.com*
+
+* Fixed that count distinct should use the selected column even when using :include #5251 *anna@wota.jp*
+
+* Fixed that :includes merged from with_scope won't cause the same association to be loaded more than once if repetition occurs in the clauses #5253 *alex@purefiction.net*
+
+* Allow models to override to_xml. #4989 *Blair Zajac <blair@orcaware.com>*
+
+* PostgreSQL: don't ignore port when host is nil since it's often used to label the domain socket. #5247 *shimbo@is.naist.jp*
+
+* Records and arrays of records are bound as quoted ids. *Jeremy Kemper*
+ Foo.find(:all, :conditions => ['bar_id IN (?)', bars])
+ Foo.find(:first, :conditions => ['bar_id = ?', bar])
+
+* Fixed that Base.find :all, :conditions => [ "id IN (?)", collection ] would fail if collection was empty *David Heinemeier Hansson*
+
+* Add a list of regexes assert_queries skips in the ActiveRecord test suite. *Rick Olson*
+
+* Fix the has_and_belongs_to_many #create doesn't populate the join for new records. Closes #3692 *Josh Susser*
+
+* Provide Association Extensions access to the instance that the association is being accessed from.
+ Closes #4433 *Josh Susser*
+
+* Update OpenBase adaterp's maintainer's email address. Closes #5176. *Derrick Spell*
+
+* Add a quick note about :select and eagerly included associations. *Rick Olson*
+
+* Add docs for the :as option in has_one associations. Closes #5144 *cdcarter@gmail.com*
+
+* Fixed that has_many collections shouldn't load the entire association to do build or create *David Heinemeier Hansson*
+
+* Added :allow_nil option for aggregations #5091 *Ian White*
+
+* Fix Oracle boolean support and tests. Closes #5139. *Michael Schoen*
+
+* create! no longer blows up when no attributes are passed and a :create scope is in effect (e.g. foo.bars.create! failed whereas foo.bars.create!({}) didn't.) *Jeremy Kemper*
+
+* Call Inflector#demodulize on the class name when eagerly including an STI model. Closes #5077 *info@loobmedia.com*
+
+* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. *pdcawley@bofh.org.uk*
+
+* PostgreSQL: migrations support :limit with :integer columns by mapping limit < 4 to smallint, > 4 to bigint, and anything else to integer. #2900 *keegan@thebasement.org*
+
+* Dates and times interpret empty strings as nil rather than 2000-01-01. #4830 *kajism@yahoo.com*
+
+* Allow :uniq => true with has_many :through associations. *Jeremy Kemper*
+
+* Ensure that StringIO is always available for the Schema dumper. *Marcel Molina Jr.*
+
+* Allow AR::Base#to_xml to include methods too. Closes #4921. *johan@textdrive.com*
+
+* Remove duplicate fixture entry in comments.yml. Closes #4923. *Blair Zajac <blair@orcaware.com>*
+
+* When grouping, use the appropriate option key. *Marcel Molina Jr.*
+
+* Add support for FrontBase (http://www.frontbase.com/) with a new adapter thanks to the hard work of one Mike Laster. Closes #4093. *mlaster@metavillage.com*
+
+* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. *Francois Beausoleil <francois.beausoleil@gmail.com>*
+
+* Fix syntax error in documentation. Closes #4679. *Mislav Marohnić*
+
+* Add Oracle support for CLOB inserts. Closes #4748. *schoenm@earthlink.net sandra.metz@duke.edu*
+
+* Various fixes for sqlserver_adapter (odbc statement finishing, ado schema dumper, drop index). Closes #4831. *kajism@yahoo.com*
+
+* Add support for :order option to with_scope. Closes #3887. *eric.daspet@survol.net*
+
+* Prettify output of schema_dumper by making things line up. Closes #4241 *Caio Chassot <caio@v2studio.com>*
+
+* Make build_postgresql_databases task make databases owned by the postgres user. Closes #4790. *mlaster@metavillage.com*
+
+* Sybase Adapter type conversion cleanup. Closes #4736. *dev@metacasa.net*
+
+* Fix bug where calculations with long alias names return null. *Rick Olson*
+
+* Raise error when trying to add to a has_many :through association. Use the Join Model instead. *Rick Olson*
+
+ @post.tags << @tag # BAD
+ @post.taggings.create(:tag => @tag) # GOOD
+
+* Allow all calculations to take the :include option, not just COUNT (closes #4840) *Rick Olson*
+
+* Add ActiveRecord::Errors#to_xml *Jamis Buck*
+
+* Properly quote index names in migrations (closes #4764) *John Long*
+
+* Fix the HasManyAssociation#count method so it uses the new ActiveRecord::Base#count syntax, while maintaining backwards compatibility. *Rick Olson*
+
+* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions *Rick Olson*
+
+* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. *Rick Olson*
+
+* Add :case_sensitive option to validates_uniqueness_of (closes #3090) *Rick Olson*
+
+ class Account < ActiveRecord::Base
+ validates_uniqueness_of :email, :case_sensitive => false
+ end
+
+* Allow multiple association extensions with :extend option (closes #4666) *Josh Susser*
+
+ class Account < ActiveRecord::Base
+ has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
+ end
+
+
+## 1.14.4 (August 8th, 2006) ##
+
+* Add warning about the proper way to validate the presence of a foreign key. #4147 *Francois Beausoleil <francois.beausoleil@gmail.com>*
+
+* Fix syntax error in documentation. #4679 *Mislav Marohnić*
+
+* Update inconsistent migrations documentation. #4683 *machomagna@gmail.com*
+
+
+## 1.14.3 (June 27th, 2006) ##
+
+* Fix announcement of very long migration names. #5722 *blake@near-time.com*
+
+* Update callbacks documentation. #3970 *Robby Russell <robby@planetargon.com>*
+
+* Properly quote index names in migrations (closes #4764) *John Long*
+
+* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions *Rick Olson*
+
+* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. *Rick Olson*
+
+
+## 1.14.2 (April 9th, 2006) ##
+
+* Fixed calculations for the Oracle Adapter (closes #4626) *Michael Schoen*
+
+
+## 1.14.1 (April 6th, 2006) ##
+
+* Fix type_name_with_module to handle type names that begin with '::'. Closes #4614. *Nicholas Seckar*
+
+* Fixed that that multiparameter assignment doesn't work with aggregations (closes #4620) *Lars Pind*
+
+* Enable Limit/Offset in Calculations (closes #4558) *lmarlow*
+
+* Fixed that loading including associations returns all results if Load IDs For Limited Eager Loading returns none (closes #4528) *Rick Olson*
+
+* Fixed HasManyAssociation#find bugs when :finder_sql is set #4600 *lagroue@free.fr*
+
+* Allow AR::Base#respond_to? to behave when @attributes is nil *Ryan Davis*
+
+* Support eager includes when going through a polymorphic has_many association. *Rick Olson*
+
+* Added support for eagerly including polymorphic has_one associations. (closes #4525) *Rick Olson*
+
+ class Post < ActiveRecord::Base
+ has_one :tagging, :as => :taggable
+ end
+
+ Post.find :all, :include => :tagging
+
+* Added descriptive error messages for invalid has_many :through associations: going through :has_one or :has_and_belongs_to_many *Rick Olson*
+
+* Added support for going through a polymorphic has_many association: (closes #4401) *Rick Olson*
+
+ class PhotoCollection < ActiveRecord::Base
+ has_many :photos, :as => :photographic
+ belongs_to :firm
+ end
+
+ class Firm < ActiveRecord::Base
+ has_many :photo_collections
+ has_many :photos, :through => :photo_collections
+ end
+
+* Multiple fixes and optimizations in PostgreSQL adapter, allowing ruby-postgres gem to work properly. *ruben.nine@gmail.com*
+
+* Fixed that AssociationCollection#delete_all should work even if the records of the association are not loaded yet. *Florian Weber*
+
+* Changed those private ActiveRecord methods to take optional third argument :auto instead of nil for performance optimizations. (closes #4456) *Stefan*
+
+* Private ActiveRecord methods add_limit!, add_joins!, and add_conditions! take an OPTIONAL third argument 'scope' (closes #4456) *Rick Olson*
+
+* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model *David Heinemeier Hansson*
+
+* Fixed that records returned from has_and_belongs_to_many associations with additional attributes should be marked as read only (fixes #4512) *David Heinemeier Hansson*
+
+* Do not implicitly mark recordss of has_many :through as readonly but do mark habtm records as readonly (eventually only on join tables without rich attributes). *Marcel Mollina Jr.*
+
+* Fixed broken OCIAdapter #4457 *Michael Schoen*
+
+
+## 1.14.0 (March 27th, 2006) ##
+
+* Replace 'rescue Object' with a finer grained rescue. Closes #4431. *Nicholas Seckar*
+
+* Fixed eager loading so that an aliased table cannot clash with a has_and_belongs_to_many join table *Rick Olson*
+
+* Add support for :include to with_scope *andrew@redlinesoftware.com*
+
+* Support the use of public synonyms with the Oracle adapter; required ruby-oci8 v0.1.14 #4390 *Michael Schoen*
+
+* Change periods (.) in table aliases to _'s. Closes #4251 *jeff@ministrycentered.com*
+
+* Changed has_and_belongs_to_many join to INNER JOIN for Mysql 3.23.x. Closes #4348 *Rick Olson*
+
+* Fixed issue that kept :select options from being scoped *Rick Olson*
+
+* Fixed db_schema_import when binary types are present #3101 *David Heinemeier Hansson*
+
+* Fixed that MySQL enums should always be returned as strings #3501 *David Heinemeier Hansson*
+
+* Change has_many :through to use the :source option to specify the source association. :class_name is now ignored. *Rick Olson*
+
+ class Connection < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :channel
+ end
+
+ class Channel < ActiveRecord::Base
+ has_many :connections
+ has_many :contacts, :through => :connections, :class_name => 'User' # OLD
+ has_many :contacts, :through => :connections, :source => :user # NEW
+ end
+
+* Fixed DB2 adapter so nullable columns will be determines correctly now and quotes from column default values will be removed #4350 *contact@maik-schmidt.de*
+
+* Allow overriding of find parameters in scoped has_many :through calls *Rick Olson*
+
+ In this example, :include => false disables the default eager association from loading. :select changes the standard
+ select clause. :joins specifies a join that is added to the end of the has_many :through query.
+
+ class Post < ActiveRecord::Base
+ has_many :tags, :through => :taggings, :include => :tagging do
+ def add_joins_and_select
+ find :all, :select => 'tags.*, authors.id as author_id', :include => false,
+ :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
+ end
+ end
+ end
+
+* Fixed that schema changes while the database was open would break any connections to an SQLite database (now we reconnect if that error is throw) *David Heinemeier Hansson*
+
+* Don't classify the has_one class when eager loading, it is already singular. Add tests. (closes #4117) *Jonathan Viney*
+
+* Quit ignoring default :include options in has_many :through calls *Mark James*
+
+* Allow has_many :through associations to find the source association by setting a custom class (closes #4307) *Jonathan Viney*
+
+* Eager Loading support added for has_many :through => :has_many associations (see below). *Rick Olson*
+
+* Allow has_many :through to work on has_many associations (closes #3864) [sco@scottraymond.net] Example:
+
+ class Firm < ActiveRecord::Base
+ has_many :clients
+ has_many :invoices, :through => :clients
+ end
+
+ class Client < ActiveRecord::Base
+ belongs_to :firm
+ has_many :invoices
+ end
+
+ class Invoice < ActiveRecord::Base
+ belongs_to :client
+ end
+
+* Raise error when trying to select many polymorphic objects with has_many :through or :include (closes #4226) *Josh Susser*
+
+* Fixed has_many :through to include :conditions set on the :through association. closes #4020 *Jonathan Viney*
+
+* Fix that has_many :through honors the foreign key set by the belongs_to association in the join model (closes #4259) *andylien@gmail.com / Rick Olson*
+
+* SQL Server adapter gets some love #4298 *Ryan Tomayko*
+
+* Added OpenBase database adapter that builds on top of the http://www.spice-of-life.net/ruby-openbase/ driver. All functionality except LIMIT/OFFSET is supported #3528 *derrickspell@cdmplus.com*
+
+* Rework table aliasing to account for truncated table aliases. Add smarter table aliasing when doing eager loading of STI associations. This allows you to use the association name in the order/where clause. [Jonathan Viney / Rick Olson] #4108 Example (SpecialComment is using STI):
+
+ Author.find(:all, :include => { :posts => :special_comments }, :order => 'special_comments.body')
+
+* Add AbstractAdapter#table_alias_for to create table aliases according to the rules of the current adapter. *Rick Olson*
+
+* Provide access to the underlying database connection through Adapter#raw_connection. Enables the use of db-specific methods without complicating the adapters. #2090 *Michael Koziarski*
+
+* Remove broken attempts at handling columns with a default of 'now()' in the postgresql adapter. #2257 *Michael Koziarski*
+
+* Added connection#current_database that'll return of the current database (only works in MySQL, SQL Server, and Oracle so far -- please help implement for the rest of the adapters) #3663 *Tom Ward*
+
+* Fixed that Migration#execute would have the table name prefix appended to its query #4110 *mark.imbriaco@pobox.com*
+
+* Make all tinyint(1) variants act like boolean in mysql (tinyint(1) unsigned, etc.) *Jamis Buck*
+
+* Use association's :conditions when eager loading. [Jeremy Evans] #4144
+
+* Alias the has_and_belongs_to_many join table on eager includes. #4106 *Jeremy Evans*
+
+ This statement would normally error because the projects_developers table is joined twice, and therefore joined_on would be ambiguous.
+
+ Developer.find(:all, :include => {:projects => :developers}, :conditions => 'join_project_developers.joined_on IS NOT NULL')
+
+* Oracle adapter gets some love #4230 *Michael Schoen*
+
+ * Changes :text to CLOB rather than BLOB [Moses Hohman]
+ * Fixes an issue with nil numeric length/scales (several)
+ * Implements support for XMLTYPE columns [wilig / Kubo Takehiro]
+ * Tweaks a unit test to get it all green again
+ * Adds support for #current_database
+
+* Added Base.abstract_class? that marks which classes are not part of the Active Record hierarchy #3704 *Rick Olson*
+
+ class CachedModel < ActiveRecord::Base
+ self.abstract_class = true
+ end
+
+ class Post < CachedModel
+ end
+
+ CachedModel.abstract_class?
+ => true
+
+ Post.abstract_class?
+ => false
+
+ Post.base_class
+ => Post
+
+ Post.table_name
+ => 'posts'
+
+* Allow :dependent options to be used with polymorphic joins. #3820 *Rick Olson*
+
+ class Foo < ActiveRecord::Base
+ has_many :attachments, :as => :attachable, :dependent => :delete_all
+ end
+
+* Nicer error message on has_many :through when :through reflection can not be found. #4042 *court3nay*
+
+* Upgrade to Transaction::Simple 1.3 *Jamis Buck*
+
+* Catch FixtureClassNotFound when using instantiated fixtures on a fixture that has no ActiveRecord model *Rick Olson*
+
+* Allow ordering of calculated results and/or grouped fields in calculations *solo@gatelys.com*
+
+* Make ActiveRecord::Base#save! return true instead of nil on success. #4173 *johan@johansorensen.com*
+
+* Dynamically set allow_concurrency. #4044 *Stefan Kaes*
+
+* Added Base#to_xml that'll turn the current record into a XML representation [David Heinemeier Hansson]. Example:
+
+ topic.to_xml
+
+ ...returns:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <topic>
+ <title>The First Topic</title>
+ <author-name>David</author-name>
+ <id type="integer">1</id>
+ <approved type="boolean">false</approved>
+ <replies-count type="integer">0</replies-count>
+ <bonus-time type="datetime">2000-01-01 08:28:00</bonus-time>
+ <written-on type="datetime">2003-07-16 09:28:00</written-on>
+ <content>Have a nice day</content>
+ <author-email-address>david@loudthinking.com</author-email-address>
+ <parent-id></parent-id>
+ <last-read type="date">2004-04-15</last-read>
+ </topic>
+
+ ...and you can configure with:
+
+ topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
+
+ ...that'll return:
+
+ <topic>
+ <title>The First Topic</title>
+ <author-name>David</author-name>
+ <approved type="boolean">false</approved>
+ <content>Have a nice day</content>
+ <author-email-address>david@loudthinking.com</author-email-address>
+ <parent-id></parent-id>
+ <last-read type="date">2004-04-15</last-read>
+ </topic>
+
+ You can even do load first-level associations as part of the document:
+
+ firm.to_xml :include => [ :account, :clients ]
+
+ ...that'll return something like:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <firm>
+ <id type="integer">1</id>
+ <rating type="integer">1</rating>
+ <name>37signals</name>
+ <clients>
+ <client>
+ <rating type="integer">1</rating>
+ <name>Summit</name>
+ </client>
+ <client>
+ <rating type="integer">1</rating>
+ <name>Microsoft</name>
+ </client>
+ </clients>
+ <account>
+ <id type="integer">1</id>
+ <credit-limit type="integer">50</credit-limit>
+ </account>
+ </firm>
+
+* Allow :counter_cache to take a column name for custom counter cache columns *Jamis Buck*
+
+* Documentation fixes for :dependent *robby@planetargon.com*
+
+* Stop the MySQL adapter crashing when views are present. #3782 *Jonathan Viney*
+
+* Don't classify the belongs_to class, it is already singular #4117 *keithm@infused.org*
+
+* Allow set_fixture_class to take Classes instead of strings for a class in a module. Raise FixtureClassNotFound if a fixture can't load. *Rick Olson*
+
+* Fix quoting of inheritance column for STI eager loading #4098 *Jonathan Viney <jonathan@bluewire.net.nz>*
+
+* Added smarter table aliasing for eager associations for multiple self joins #3580 *Rick Olson*
+
+ * The first time a table is referenced in a join, no alias is used.
+ * After that, the parent class name and the reflection name are used.
+
+ Tree.find(:all, :include => :children) # LEFT OUTER JOIN trees AS tree_children ...
+
+ * Any additional join references get a numerical suffix like '_2', '_3', etc.
+
+* Fixed eager loading problems with single-table inheritance #3580 [Rick Olson]. Post.find(:all, :include => :special_comments) now returns all posts, and any special comments that the posts may have. And made STI work with has_many :through and polymorphic belongs_to.
+
+* Added cascading eager loading that allows for queries like Author.find(:all, :include=> { :posts=> :comments }), which will fetch all authors, their posts, and the comments belonging to those posts in a single query (using LEFT OUTER JOIN) #3913 [anna@wota.jp]. Examples:
+
+ # cascaded in two levels
+ >> Author.find(:all, :include=>{:posts=>:comments})
+ => authors
+ +- posts
+ +- comments
+
+ # cascaded in two levels and normal association
+ >> Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
+ => authors
+ +- posts
+ +- comments
+ +- categorizations
+
+ # cascaded in two levels with two has_many associations
+ >> Author.find(:all, :include=>{:posts=>[:comments, :categorizations]})
+ => authors
+ +- posts
+ +- comments
+ +- categorizations
+
+ # cascaded in three levels
+ >> Company.find(:all, :include=>{:groups=>{:members=>{:favorites}}})
+ => companies
+ +- groups
+ +- members
+ +- favorites
+
+* Make counter cache work when replacing an association #3245 *eugenol@gmail.com*
+
+* Make migrations verbose *Jamis Buck*
+
+* Make counter_cache work with polymorphic belongs_to *Jamis Buck*
+
+* Fixed that calling HasOneProxy#build_model repeatedly would cause saving to happen #4058 *anna@wota.jp*
+
+* Added Sybase database adapter that relies on the Sybase Open Client bindings (see http://raa.ruby-lang.org/project/sybase-ctlib) #3765 [John Sheets]. It's almost completely Active Record compliant (including migrations), but has the following caveats:
+
+ * Does not support DATE SQL column types; use DATETIME instead.
+ * Date columns on HABTM join tables are returned as String, not Time.
+ * Insertions are potentially broken for :polymorphic join tables
+ * BLOB column access not yet fully supported
+
+* Clear stale, cached connections left behind by defunct threads. *Jeremy Kemper*
+
+* CHANGED DEFAULT: set ActiveRecord::Base.allow_concurrency to false. Most AR usage is in single-threaded applications. *Jeremy Kemper*
+
+* Renamed the "oci" adapter to "oracle", but kept the old name as an alias #4017 *Michael Schoen*
+
+* Fixed that Base.save should always return false if the save didn't succeed, including if it has halted by before_save's #1861, #2477 *David Heinemeier Hansson*
+
+* Speed up class -> connection caching and stale connection verification. #3979 *Stefan Kaes*
+
+* Add set_fixture_class to allow the use of table name accessors with models which use set_table_name. *Kevin Clark*
+
+* Added that fixtures to placed in subdirectories of the main fixture files are also loaded #3937 *dblack@wobblini.net*
+
+* Define attribute query methods to avoid method_missing calls. #3677 *Jonathan Viney*
+
+* ActiveRecord::Base.remove_connection explicitly closes database connections and doesn't corrupt the connection cache. Introducing the disconnect! instance method for the PostgreSQL, MySQL, and SQL Server adapters; implementations for the others are welcome. #3591 *Simon Stapleton, Tom Ward*
+
+* Added support for nested scopes #3407 [anna@wota.jp]. Examples:
+
+ Developer.with_scope(:find => { :conditions => "salary > 10000", :limit => 10 }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (salary > 10000) LIMIT 10
+
+ # inner rule is used. (all previous parameters are ignored)
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (name = 'Jamis')
+ end
+
+ # parameters are merged
+ Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (( salary > 10000 ) AND ( name = 'Jamis' )) LIMIT 10
+ end
+ end
+
+* Fixed db2 connection with empty user_name and auth options #3622 *phurley@gmail.com*
+
+* Fixed validates_length_of to work on UTF-8 strings by using characters instead of bytes #3699 *Masao Mutoh*
+
+* Fixed that reflections would bleed across class boundaries in single-table inheritance setups #3796 *Lars Pind*
+
+* Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Base.maxmium, and the generic Base.calculate. All can be used with :group and :having. Calculations and statitics need no longer require custom SQL. #3958 [Rick Olson]. Examples:
+
+ Person.average :age
+ Person.minimum :age
+ Person.maximum :age
+ Person.sum :salary, :group => :last_name
+
+* Renamed Errors#count to Errors#size but kept an alias for the old name (and included an alias for length too) #3920 *Luke Redpath*
+
+* Reflections don't attempt to resolve module nesting of association classes. Simplify type computation. *Jeremy Kemper*
+
+* Improved the Oracle OCI Adapter with better performance for column reflection (from #3210), fixes to migrations (from #3476 and #3742), tweaks to unit tests (from #3610), and improved documentation (from #2446) #3879 *Aggregated by schoenm@earthlink.net*
+
+* Fixed that the schema_info table used by ActiveRecord::Schema.define should respect table pre- and suffixes #3834 *rubyonrails@atyp.de*
+
+* Added :select option to Base.count that'll allow you to select something else than * to be counted on. Especially important for count queries using DISTINCT #3839 *Stefan Kaes*
+
+* Correct syntax error in mysql DDL, and make AAACreateTablesTest run first *Bob Silva*
+
+* Allow :include to be used with has_many :through associations #3611 *Michael Schoen*
+
+* PostgreSQL: smarter schema dumps using pk_and_sequence_for(table). #2920 *Blair Zajac*
+
+* SQLServer: more compatible limit/offset emulation. #3779 *Tom Ward*
+
+* Polymorphic join support for has_one associations (has_one :foo, :as => :bar) #3785 *Rick Olson*
+
+* PostgreSQL: correctly parse negative integer column defaults. #3776 *bellis@deepthought.org*
+
+* Fix problems with count when used with :include *Jeremy Hopple and Kevin Clark*
+
+* ActiveRecord::RecordInvalid now states which validations failed in its default error message *Tobias Lütke*
+
+* Using AssociationCollection#build with arrays of hashes should call build, not create *David Heinemeier Hansson*
+
+* Remove definition of reloadable? from ActiveRecord::Base to make way for new Reloadable code. *Nicholas Seckar*
+
+* Fixed schema handling for DB2 adapter that didn't work: an initial schema could be set, but it wasn't used when getting tables and indexes #3678 *Maik Schmidt*
+
+* Support the :column option for remove_index with the PostgreSQL adapter. #3661 *Shugo Maeda*
+
+* Add documentation for add_index and remove_index. #3600 *Manfred Stienstra <m.stienstra@fngtps.com>*
+
+* If the OCI library is not available, raise an exception indicating as much. #3593 *Michael Schoen*
+
+* Add explicit :order in finder tests as postgresql orders results differently by default. #3577. *Rick Olson*
+
+* Make dynamic finders honor additional passed in :conditions. #3569 *Oleg Pudeyev <pudeyo@rpi.edu>, Marcel Molina Jr.*
+
+* Show a meaningful error when the DB2 adapter cannot be loaded due to missing dependencies. *Nicholas Seckar*
+
+* Make .count work for has_many associations with multi line finder sql *Michael Schoen*
+
+* Add AR::Base.base_class for querying the ancestor AR::Base subclass *Jamis Buck*
+
+* Allow configuration of the column used for optimistic locking *wilsonb@gmail.com*
+
+* Don't hardcode 'id' in acts as list. *ror@philippeapril.com*
+
+* Fix date errors for SQLServer in association tests. #3406 *Kevin Clark*
+
+* Escape database name in MySQL adapter when creating and dropping databases. #3409 *anna@wota.jp*
+
+* Disambiguate table names for columns in validates_uniqueness_of's WHERE clause. #3423 *alex.borovsky@gmail.com*
+
+* .with_scope imposed create parameters now bypass attr_protected *Tobias Lütke*
+
+* Don't raise an exception when there are more keys than there are named bind variables when sanitizing conditions. *Marcel Molina Jr.*
+
+* Multiple enhancements and adjustments to DB2 adaptor. #3377 *contact@maik-schmidt.de*
+
+* Sanitize scoped conditions. *Marcel Molina Jr.*
+
+* Added option to Base.reflection_of_all_associations to specify a specific association to scope the call. For example Base.reflection_of_all_associations(:has_many) *David Heinemeier Hansson*
+
+* Added ActiveRecord::SchemaDumper.ignore_tables which tells SchemaDumper which tables to ignore. Useful for tables with funky column like the ones required for tsearch2. *Tobias Lütke*
+
+* SchemaDumper now doesn't fail anymore when there are unknown column types in the schema. Instead the table is ignored and a Comment is left in the schema.rb. *Tobias Lütke*
+
+* Fixed that saving a model with multiple habtm associations would only save the first one. #3244 *yanowitz-rubyonrails@quantumfoam.org, Florian Weber*
+
+* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 *wejn@box.cz, Rick Olson, Scott Barron*
+
+* removed :piggyback in favor of just allowing :select on :through associations. *Tobias Lütke*
+
+* made method missing delegation to class methods on relation target work on :through associations. *Tobias Lütke*
+
+* made .find() work on :through relations. *Tobias Lütke*
+
+* Fix typo in association docs. #3296. *Blair Zajac*
+
+* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model *Tobias Lütke*
+
+## 1.13.2 (December 13th, 2005) ##
+
+* Become part of Rails 1.0
+
+* MySQL: allow encoding option for mysql.rb driver. *Jeremy Kemper*
+
+* Added option inheritance for find calls on has_and_belongs_to_many and has_many assosociations [David Heinemeier Hansson]. Example:
+
+ class Post
+ has_many :recent_comments, :class_name => "Comment", :limit => 10, :include => :author
+ end
+
+ post.recent_comments.find(:all) # Uses LIMIT 10 and includes authors
+ post.recent_comments.find(:all, :limit => nil) # Uses no limit but include authors
+ post.recent_comments.find(:all, :limit => nil, :include => nil) # Uses no limit and doesn't include authors
+
+* Added option to specify :group, :limit, :offset, and :select options from find on has_and_belongs_to_many and has_many assosociations *David Heinemeier Hansson*
+
+* MySQL: fixes for the bundled mysql.rb driver. #3160 *Justin Forder*
+
+* SQLServer: fix obscure optimistic locking bug. #3068 *kajism@yahoo.com*
+
+* SQLServer: support uniqueidentifier columns. #2930 *keithm@infused.org*
+
+* SQLServer: cope with tables names qualified by owner. #3067 *jeff@ministrycentered.com*
+
+* SQLServer: cope with columns with "desc" in the name. #1950 *Ron Lusk, Ryan Tomayko*
+
+* SQLServer: cope with primary keys with "select" in the name. #3057 *rdifrango@captechventures.com*
+
+* Oracle: active? performs a select instead of a commit. #3133 *Michael Schoen*
+
+* MySQL: more robust test for nullified result hashes. #3124 *Stefan Kaes*
+
+* Reloading an instance refreshes its aggregations as well as its associations. #3024 *François Beausoleil*
+
+* Fixed that using :include together with :conditions array in Base.find would cause NoMethodError #2887 *Paul Hammmond*
+
+* PostgreSQL: more robust sequence name discovery. #3087 *Rick Olson*
+
+* Oracle: use syntax compatible with Oracle 8. #3131 *Michael Schoen*
+
+* MySQL: work around ruby-mysql/mysql-ruby inconsistency with mysql.stat. Eliminate usage of mysql.ping because it doesn't guarantee reconnect. Explicitly close and reopen the connection instead. *Jeremy Kemper*
+
+* Added preliminary support for polymorphic associations *David Heinemeier Hansson*
+
+* Added preliminary support for join models *David Heinemeier Hansson*
+
+* Allow validate_uniqueness_of to be scoped by more than just one column. #1559. *jeremy@jthopple.com, Marcel Molina Jr.*
+
+* Firebird: active? and reconnect! methods for handling stale connections. #428 *Ken Kunz <kennethkunz@gmail.com>*
+
+* Firebird: updated for FireRuby 0.4.0. #3009 *Ken Kunz <kennethkunz@gmail.com>*
+
+* MySQL and PostgreSQL: active? compatibility with the pure-Ruby driver. #428 *Jeremy Kemper*
+
+* Oracle: active? check pings the database rather than testing the last command status. #428 *Michael Schoen*
+
+* SQLServer: resolve column aliasing/quoting collision when using limit or offset in an eager find. #2974 *kajism@yahoo.com*
+
+* Reloading a model doesn't lose track of its connection. #2996 *junk@miriamtech.com, Jeremy Kemper*
+
+* Fixed bug where using update_attribute after pushing a record to a habtm association of the object caused duplicate rows in the join table. #2888 *colman@rominato.com, Florian Weber, Michael Schoen*
+
+* MySQL, PostgreSQL: reconnect! also reconfigures the connection. Otherwise, the connection 'loses' its settings if it times out and is reconnected. #2978 *Shugo Maeda*
+
+* has_and_belongs_to_many: use JOIN instead of LEFT JOIN. *Jeremy Kemper*
+
+* MySQL: introduce :encoding option to specify the character set for client, connection, and results. Only available for MySQL 4.1 and later with the mysql-ruby driver. Do SHOW CHARACTER SET in mysql client to see available encodings. #2975 *Shugo Maeda*
+
+* Add tasks to create, drop and rebuild the MySQL and PostgreSQL test databases. *Marcel Molina Jr.*
+
+* Correct boolean handling in generated reader methods. #2945 *Don Park, Stefan Kaes*
+
+* Don't generate read methods for columns whose names are not valid ruby method names. #2946 *Stefan Kaes*
+
+* Document :force option to create_table. #2921 *Blair Zajac <blair@orcaware.com>*
+
+* Don't add the same conditions twice in has_one finder sql. #2916 *Jeremy Evans*
+
+* Rename Version constant to VERSION. #2802 *Marcel Molina Jr.*
+
+* Introducing the Firebird adapter. Quote columns and use attribute_condition more consistently. Setup guide: http://wiki.rubyonrails.com/rails/pages/Firebird+Adapter #1874 *Ken Kunz <kennethkunz@gmail.com>*
+
+* SQLServer: active? and reconnect! methods for handling stale connections. #428 *kajism@yahoo.com, Tom Ward <tom@popdog.net>*
+
+* Associations handle case-equality more consistently: item.parts.is_a?(Array) and item.parts === Array. #1345 *MarkusQ@reality.com*
+
+* SQLServer: insert uses given primary key value if not nil rather than SELECT @@IDENTITY. #2866 *kajism@yahoo.com, Tom Ward <tom@popdog.net>*
+
+* Oracle: active? and reconnect! methods for handling stale connections. Optionally retry queries after reconnect. #428 *Michael Schoen <schoenm@earthlink.net>*
+
+* Correct documentation for Base.delete_all. #1568 *Newhydra*
+
+* Oracle: test case for column default parsing. #2788 *Michael Schoen <schoenm@earthlink.net>*
+
+* Update documentation for Migrations. #2861 *Tom Werner <tom@cube6media.com>*
+
+* When AbstractAdapter#log rescues an exception, attempt to detect and reconnect to an inactive database connection. Connection adapter must respond to the active? and reconnect! instance methods. Initial support for PostgreSQL, MySQL, and SQLite. Make certain that all statements which may need reconnection are performed within a logged block: for example, this means no avoiding log(sql, name) { } if @logger.nil? #428 *Jeremy Kemper*
+
+* Oracle: Much faster column reflection. #2848 *Michael Schoen <schoenm@earthlink.net>*
+
+* Base.reset_sequence_name analogous to reset_table_name (mostly useful for testing). Base.define_attr_method allows nil values. *Jeremy Kemper*
+
+* PostgreSQL: smarter sequence name defaults, stricter last_insert_id, warn on pk without sequence. *Jeremy Kemper*
+
+* PostgreSQL: correctly discover custom primary key sequences. #2594 *Blair Zajac <blair@orcaware.com>, meadow.nnick@gmail.com, Jeremy Kemper*
+
+* SQLServer: don't report limits for unsupported field types. #2835 *Ryan Tomayko*
+
+* Include the Enumerable module in ActiveRecord::Errors. *Rick Bradley <rick@rickbradley.com>*
+
+* Add :group option, correspond to GROUP BY, to the find method and to the has_many association. #2818 *rubyonrails@atyp.de*
+
+* Don't cast nil or empty strings to a dummy date. #2789 *Rick Bradley <rick@rickbradley.com>*
+
+* acts_as_list plays nicely with inheritance by remembering the class which declared it. #2811 *rephorm@rephorm.com*
+
+* Fix sqlite adaptor's detection of missing dbfile or database declaration. *Nicholas Seckar*
+
+* Fixed acts_as_list for definitions without an explicit :order #2803 *Jonathan Viney*
+
+* Upgrade bundled ruby-mysql 0.2.4 with mysql411 shim (see #440) to ruby-mysql 0.2.6 with a patchset for 4.1 protocol support. Local change [301] is now a part of the main driver; reapplied local change [2182]. Removed GC.start from Result.free. *tommy@tmtm.org, akuroda@gmail.com, Doug Fales <doug.fales@gmail.com>, Jeremy Kemper*
+
+* Correct handling of complex order clauses with SQL Server limit emulation. #2770 *Tom Ward <tom@popdog.net>, Matt B.*
+
+* Correct whitespace problem in Oracle default column value parsing. #2788 *rick@rickbradley.com*
+
+* Destroy associated has_and_belongs_to_many records after all before_destroy callbacks but before destroy. This allows you to act on the habtm association as you please while preserving referential integrity. #2065 *larrywilliams1@gmail.com, sam.kirchmeier@gmail.com, elliot@townx.org, Jeremy Kemper*
+
+* Deprecate the old, confusing :exclusively_dependent option in favor of :dependent => :delete_all. *Jeremy Kemper*
+
+* More compatible Oracle column reflection. #2771 *Ryan Davis <ryand-ruby@zenspider.com>, Michael Schoen <schoenm@earthlink.net>*
+
+
+## 1.13.0 (November 7th, 2005) ##
+
+* Fixed faulty regex in get_table_name method (SQLServerAdapter) #2639 *Ryan Tomayko*
+
+* Added :include as an option for association declarations [David Heinemeier Hansson]. Example:
+
+ has_many :posts, :include => [ :author, :comments ]
+
+* Rename Base.constrain to Base.with_scope so it doesn't conflict with existing concept of database constraints. Make scoping more robust: uniform method => parameters, validated method names and supported finder parameters, raise exception on nested scopes. [Jeremy Kemper] Example:
+
+ Comment.with_scope(:find => { :conditions => 'active=true' }, :create => { :post_id => 5 }) do
+ # Find where name = ? and active=true
+ Comment.find :all, :conditions => ['name = ?', name]
+ # Create comment associated with :post_id
+ Comment.create :body => "Hello world"
+ end
+
+* Fixed that SQL Server should ignore :size declarations on anything but integer and string in the agnostic schema representation #2756 *Ryan Tomayko*
+
+* Added constrain scoping for creates using a hash of attributes bound to the :creation key [David Heinemeier Hansson]. Example:
+
+ Comment.constrain(:creation => { :post_id => 5 }) do
+ # Associated with :post_id
+ Comment.create :body => "Hello world"
+ end
+
+ This is rarely used directly, but allows for find_or_create on associations. So you can do:
+
+ # If the tag doesn't exist, a new one is created that's associated with the person
+ person.tags.find_or_create_by_name("Summer")
+
+* Added find_or_create_by_X as a second type of dynamic finder that'll create the record if it doesn't already exist [David Heinemeier Hansson]. Example:
+
+ # No 'Summer' tag exists
+ Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
+
+ # Now the 'Summer' tag does exist
+ Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
+
+* Added extension capabilities to has_many and has_and_belongs_to_many proxies [David Heinemeier Hansson]. Example:
+
+ class Account < ActiveRecord::Base
+ has_many :people do
+ def find_or_create_by_name(name)
+ first_name, *last_name = name.split
+ last_name = last_name.join " "
+
+ find_or_create_by_first_name_and_last_name(first_name, last_name)
+ end
+ end
+ end
+
+ person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
+ person.first_name # => "David"
+ person.last_name # => "Heinemeier Hansson"
+
+ Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation).
+
+* Omit internal dtproperties table from SQLServer table list. #2729 *Ryan Tomayko*
+
+* Quote column names in generated SQL. #2728 *Ryan Tomayko*
+
+* Correct the pure-Ruby MySQL 4.1.1 shim's version test. #2718 *Jeremy Kemper*
+
+* Add Model.create! to match existing model.save! method. When save! raises RecordInvalid, you can catch the exception, retrieve the invalid record (invalid_exception.record), and see its errors (invalid_exception.record.errors). *Jeremy Kemper*
+
+* Correct fixture behavior when table name pluralization is off. #2719 *Rick Bradley <rick@rickbradley.com>*
+
+* Changed :dbfile to :database for SQLite adapter for consistency (old key still works as an alias) #2644 *Dan Peterson*
+
+* Added migration support for Oracle #2647 *Michael Schoen*
+
+* Worked around that connection can't be reset if allow_concurrency is off. #2648 *Michael Schoen <schoenm@earthlink.net>*
+
+* Fixed SQL Server adapter to pass even more tests and do even better #2634 *Ryan Tomayko*
+
+* Fixed SQL Server adapter so it honors options[:conditions] when applying :limits #1978 *Tom Ward*
+
+* Added migration support to SQL Server adapter (please someone do the same for Oracle and DB2) #2625 *Tom Ward*
+
+* Use AR::Base.silence rather than AR::Base.logger.silence in fixtures to preserve Log4r compatibility. #2618 *dansketcher@gmail.com*
+
+* Constraints are cloned so they can't be inadvertently modified while they're
+ in effect. Added :readonly finder constraint. Calling an association collection's class method (Part.foobar via item.parts.foobar) constrains :readonly => false since the collection's :joins constraint would otherwise force it to true. [Jeremy Kemper <rails@bitsweat.net>]
+* Added :offset and :limit to the kinds of options that Base.constrain can use #2466 *duane.johnson@gmail.com*
+
+* Fixed handling of nil number columns on Oracle and cleaned up tests for Oracle in general #2555 *Michael Schoen*
+
+* Added quoted_true and quoted_false methods and tables to db2_adapter and cleaned up tests for DB2 #2493, #2624 *maik schmidt*
+
+
+## 1.12.2 (October 26th, 2005) ##
+
+* Allow symbols to rename columns when using SQLite adapter. #2531 *Kevin Clark*
+
+* Map Active Record time to SQL TIME. #2575, #2576 *Robby Russell <robby@planetargon.com>*
+
+* Clarify semantics of ActiveRecord::Base#respond_to? #2560 *Stefan Kaes*
+
+* Fixed Association#clear for associations which have not yet been accessed. #2524 *Patrick Lenz <patrick@lenz.sh>*
+
+* HABTM finders shouldn't return readonly records. #2525 *Patrick Lenz <patrick@lenz.sh>*
+
+* Make all tests runnable on their own. #2521. *Blair Zajac <blair@orcaware.com>*
+
+
+## 1.12.1 (October 19th, 2005) ##
+
+* Always parenthesize :conditions options so they may be safely combined with STI and constraints.
+
+* Correct PostgreSQL primary key sequence detection. #2507 *tmornini@infomania.com*
+
+* Added support for using limits in eager loads that involve has_many and has_and_belongs_to_many associations
+
+
+## 1.12.0 (October 16th, 2005) ##
+
+* Update/clean up documentation (rdoc)
+
+* PostgreSQL sequence support. Use set_sequence_name in your model class to specify its primary key sequence. #2292 *Rick Olson <technoweenie@gmail.com>, Robby Russell <robby@planetargon.com>*
+
+* Change default logging colors to work on both white and black backgrounds. *Sam Stephenson*
+
+* YAML fixtures support ordered hashes for fixtures with foreign key dependencies in the same table. #1896 *purestorm@ggnore.net*
+
+* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 *Robby Russell <robby@planetargon.com>*
+
+* Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around.
+
+* Avoid memleak in dev mode when using fcgi
+
+* Simplified .clear on active record associations by using the existing delete_records method. #1906 *Caleb <me@cpb.ca>*
+
+* Delegate access to a customized primary key to the conventional id method. #2444. *Blair Zajac <blair@orcaware.com>*
+
+* Fix errors caused by assigning a has-one or belongs-to property to itself
+
+* Add ActiveRecord::Base.schema_format setting which specifies how databases should be dumped *Sam Stephenson*
+
+* Update DB2 adapter. #2206. *contact@maik-schmidt.de*
+
+* Corrections to SQLServer native data types. #2267. *rails.20.clarry@spamgourmet.com*
+
+* Deprecated ActiveRecord::Base.threaded_connection in favor of ActiveRecord::Base.allow_concurrency.
+
+* Protect id attribute from mass assigment even when the primary key is set to something else. #2438. *Blair Zajac <blair@orcaware.com>*
+
+* Misc doc fixes (typos/grammar/etc.). #2430. *coffee2code*
+
+* Add test coverage for content_columns. #2432. *coffee2code*
+
+* Speed up for unthreaded environments. #2431. *Stefan Kaes*
+
+* Optimization for Mysql selects using mysql-ruby extension greater than 2.6.3. #2426. *Stefan Kaes*
+
+* Speed up the setting of table_name. #2428. *Stefan Kaes*
+
+* Optimize instantiation of STI subclass records. In partial fullfilment of #1236. *Stefan Kaes*
+
+* Fix typo of 'constrains' to 'contraints'. #2069. *Michael Schuerig <michael@schuerig.de>*
+
+* Optimization refactoring for add_limit_offset!. In partial fullfilment of #1236. *Stefan Kaes*
+
+* Add ability to get all siblings, including the current child, with acts_as_tree. Recloses #2140. *Michael Schuerig <michael@schuerig.de>*
+
+* Add geometric type for postgresql adapter. #2233 *Andrew Kaspick*
+
+* Add option (true by default) to generate reader methods for each attribute of a record to avoid the overhead of calling method missing. In partial fullfilment of #1236. *Stefan Kaes*
+
+* Add convenience predicate methods on Column class. In partial fullfilment of #1236. *Stefan Kaes*
+
+* Raise errors when invalid hash keys are passed to ActiveRecord::Base.find. #2363 *Chad Fowler <chad@chadfowler.com>, Nicholas Seckar*
+
+* Added :force option to create_table that'll try to drop the table if it already exists before creating
+
+* Fix transactions so that calling return while inside a transaction will not leave an open transaction on the connection. *Nicholas Seckar*
+
+* Use foreign_key inflection uniformly. #2156 *Blair Zajac <blair@orcaware.com>*
+
+* model.association.clear should destroy associated objects if :dependent => true instead of nullifying their foreign keys. #2221 *joergd@pobox.com, ObieFernandez <obiefernandez@gmail.com>*
+
+* Returning false from before_destroy should cancel the action. #1829 *Jeremy Huffman*
+
+* Recognize PostgreSQL NOW() default as equivalent to CURRENT_TIMESTAMP or CURRENT_DATE, depending on the column's type. #2256 *mat <mat@absolight.fr>*
+
+* Extensive documentation for the abstract database adapter. #2250 *François Beausoleil <fbeausoleil@ftml.net>*
+
+* Clean up Fixtures.reset_sequences for PostgreSQL. Handle tables with no rows and models with custom primary keys. #2174, #2183 *jay@jay.fm, Blair Zajac <blair@orcaware.com>*
+
+* Improve error message when nil is assigned to an attr which validates_size_of within a range. #2022 *Manuel Holtgrewe <purestorm@ggnore.net>*
+
+* Make update_attribute use the same writer method that update_attributes uses.
+ \#2237 [trevor@protocool.com]
+* Make migrations honor table name prefixes and suffixes. #2298 *Jakob Skjerning, Marcel Molina Jr.*
+
+* Correct and optimize PostgreSQL bytea escaping. #1745, #1837 *dave@cherryville.org, ken@miriamtech.com, bellis@deepthought.org*
+
+* Fixtures should only reset a PostgreSQL sequence if it corresponds to an integer primary key named id. #1749 *chris@chrisbrinker.com*
+
+* Standardize the interpretation of boolean columns in the Mysql and Sqlite adapters. (Use MysqlAdapter.emulate_booleans = false to disable this behavior)
+
+* Added new symbol-driven approach to activating observers with Base#observers= [David Heinemeier Hansson]. Example:
+
+ ActiveRecord::Base.observers = :cacher, :garbage_collector
+
+* Added AbstractAdapter#select_value and AbstractAdapter#select_values as convenience methods for selecting single values, instead of hashes, of the first column in a SELECT #2283 *solo@gatelys.com*
+
+* Wrap :conditions in parentheses to prevent problems with OR's #1871 *Jamis Buck*
+
+* Allow the postgresql adapter to work with the SchemaDumper. *Jamis Buck*
+
+* Add ActiveRecord::SchemaDumper for dumping a DB schema to a pure-ruby file, making it easier to consolidate large migration lists and port database schemas between databases. *Jamis Buck*
+
+* Fixed migrations for Windows when using more than 10 *David Naseby*
+
+* Fixed that the create_x method from belongs_to wouldn't save the association properly #2042 *Florian Weber*
+
+* Fixed saving a record with two unsaved belongs_to associations pointing to the same object #2023 *Tobias Lütke*
+
+* Improved migrations' behavior when the schema_info table is empty. *Nicholas Seckar*
+
+* Fixed that Observers didn't observe sub-classes #627 *Florian Weber*
+
+* Fix eager loading error messages, allow :include to specify tables using strings or symbols. Closes #2222 *Marcel Molina Jr.*
+
+* Added check for RAILS_CONNECTION_ADAPTERS on startup and only load the connection adapters specified within if its present (available in Rails through config.connection_adapters using the new config) #1958 *skae*
+
+* Fixed various problems with has_and_belongs_to_many when using customer finder_sql #2094 *Florian Weber*
+
+* Added better exception error when unknown column types are used with migrations #1814 *François Beausoleil*
+
+* Fixed "connection lost" issue with the bundled Ruby/MySQL driver (would kill the app after 8 hours of inactivity) #2163, #428 *kajism@yahoo.com*
+
+* Fixed comparison of Active Record objects so two new objects are not equal #2099 *deberg*
+
+* Fixed that the SQL Server adapter would sometimes return DBI::Timestamp objects instead of Time #2127 *Tom Ward*
+
+* Added the instance methods #root and #ancestors on acts_as_tree and fixed siblings to not include the current node #2142, #2140 *coffee2code*
+
+* Fixed that Active Record would call SHOW FIELDS twice (or more) for the same model when the cached results were available #1947 *sd@notso.net*
+
+* Added log_level and use_silence parameter to ActiveRecord::Base.benchmark. The first controls at what level the benchmark statement will be logged (now as debug, instead of info) and the second that can be passed false to include all logging statements during the benchmark block/
+
+* Make sure the schema_info table is created before querying the current version #1903
+
+* Fixtures ignore table name prefix and suffix #1987 *Jakob Skjerning*
+
+* Add documentation for index_type argument to add_index method for migrations #2005 *Blaine*
+
+* Modify read_attribute to allow a symbol argument #2024 *Ken Kunz*
+
+* Make destroy return self #1913 *Sebastian Kanthak*
+
+* Fix typo in validations documentation #1938 *court3nay*
+
+* Make acts_as_list work for insert_at(1) #1966 *hensleyl@papermountain.org*
+
+* Fix typo in count_by_sql documentation #1969 *Alexey Verkhovsky*
+
+* Allow add_column and create_table to specify NOT NULL #1712 *emptysands@gmail.com*
+
+* Fix create_table so that id column is implicitly added *Rick Olson*
+
+* Default sequence names for Oracle changed to #{table_name}_seq, which is the most commonly used standard. In addition, a new method ActiveRecord::Base#set_sequence_name allows the developer to set the sequence name per model. This is a non-backwards-compatible change -- anyone using the old-style "rails_sequence" will need to either create new sequences, or set: ActiveRecord::Base.set_sequence_name = "rails_sequence" #1798
+
+* OCIAdapter now properly handles synonyms, which are commonly used to separate out the schema owner from the application user #1798
+
+* Fixed the handling of camelCase columns names in Oracle #1798
+
+* Implemented for OCI the Rakefile tasks of :clone_structure_to_test, :db_structure_dump, and :purge_test_database, which enable Oracle folks to enjoy all the agile goodness of Rails for testing. Note that the current implementation is fairly limited -- only tables and sequences are cloned, not constraints or indexes. A full clone in Oracle generally requires some manual effort, and is version-specific. Post 9i, Oracle recommends the use of the DBMS_METADATA package, though that approach requires editing of the physical characteristics generated #1798
+
+* Fixed the handling of multiple blob columns in Oracle if one or more of them are null #1798
+
+* Added support for calling constrained class methods on has_many and has_and_belongs_to_many collections #1764 *Tobias Lütke*
+
+ class Comment < AR:B
+ def self.search(q)
+ find(:all, :conditions => ["body = ?", q])
+ end
+ end
+
+ class Post < AR:B
+ has_many :comments
+ end
+
+ Post.find(1).comments.search('hi') # => SELECT * from comments WHERE post_id = 1 AND body = 'hi'
+
+ NOTICE: This patch changes the underlying SQL generated by has_and_belongs_to_many queries. If your relying on that, such as
+ by explicitly referencing the old t and j aliases, you'll need to update your code. Of course, you _shouldn't_ be relying on
+ details like that no less than you should be diving in to touch private variables. But just in case you do, consider yourself
+ noticed :)
+
+* Added migration support for SQLite (using temporary tables to simulate ALTER TABLE) #1771 *Sam Stephenson*
+
+* Remove extra definition of supports_migrations? from abstract_adaptor.rb *Nicholas Seckar*
+
+* Fix acts_as_list so that moving next-to-last item to the bottom does not result in duplicate item positions
+
+* Fixed incompatibility in DB2 adapter with the new limit/offset approach #1718 *Maik Schmidt*
+
+* Added :select option to find which can specify a different value than the default *, like find(:all, :select => "first_name, last_name"), if you either only want to select part of the columns or exclude columns otherwise included from a join #1338 *Stefan Kaes*
+
+
+## 1.11.1 (11 July, 2005) ##
+
+* Added support for limit and offset with eager loading of has_one and belongs_to associations. Using the options with has_many and has_and_belongs_to_many associations will now raise an ActiveRecord::ConfigurationError #1692 *Rick Olson*
+
+* Fixed that assume_bottom_position (in acts_as_list) could be called on items already last in the list and they would move one position away from the list #1648 *tyler@kianta.com*
+
+* Added ActiveRecord::Base.threaded_connections flag to turn off 1-connection per thread (required for thread safety). By default it's on, but WEBrick in Rails need it off #1685 *Sam Stephenson*
+
+* Correct reflected table name for singular associations. #1688 *court3nay*
+
+* Fixed optimistic locking with SQL Server #1660 *tom@popdog.net*
+
+* Added ActiveRecord::Migrator.migrate that can figure out whether to go up or down based on the target version and the current
+
+* Added better error message for "packets out of order" #1630 *court3nay*
+
+* Fixed first run of "rake migrate" on PostgreSQL by not expecting a return value on the id #1640
+
+
+## 1.11.0 (6 July, 2005) ##
+
+* Fixed that Yaml error message in fixtures hid the real error #1623 *Nicholas Seckar*
+
+* Changed logging of SQL statements to use the DEBUG level instead of INFO
+
+* Added new Migrations framework for describing schema transformations in a way that can be easily applied across multiple databases #1604 [Tobias Lütke] See documentation under ActiveRecord::Migration and the additional support in the Rails rakefile/generator.
+
+* Added callback hooks to association collections #1549 [Florian Weber]. Example:
+
+ class Project
+ has_and_belongs_to_many :developers, :before_add => :evaluate_velocity
+
+ def evaluate_velocity(developer)
+ ...
+ end
+ end
+
+ ..raising an exception will cause the object not to be added (or removed, with before_remove).
+
+
+* Fixed Base.content_columns call for SQL Server adapter #1450 *DeLynn Berry*
+
+* Fixed Base#write_attribute to work with both symbols and strings #1190 *Paul Legato*
+
+* Fixed that has_and_belongs_to_many didn't respect single table inheritance types #1081 *Florian Weber*
+
+* Speed up ActiveRecord#method_missing for the common case (read_attribute).
+
+* Only notify observers on after_find and after_initialize if these methods are defined on the model. #1235 *Stefan Kaes*
+
+* Fixed that single-table inheritance sub-classes couldn't be used to limit the result set with eager loading #1215 *Chris McGrath*
+
+* Fixed validates_numericality_of to work with overrided getter-method when :allow_nil is on #1316 *raidel@onemail.at*
+
+* Added roots, root, and siblings to the batch of methods added by acts_as_tree #1541 *Michael Schuerig*
+
+* Added support for limit/offset with the MS SQL Server driver so that pagination will now work #1569 *DeLynn Berry*
+
+* Added support for ODBC connections to MS SQL Server so you can connect from a non-Windows machine #1569 *Mark Imbriaco/DeLynn Berry*
+
+* Fixed that multiparameter posts ignored attr_protected #1532 *alec+rails@veryclever.net*
+
+* Fixed problem with eager loading when using a has_and_belongs_to_many association using :association_foreign_key #1504 *flash@vanklinkenbergsoftware.nl*
+
+* Fixed Base#find to honor the documentation on how :joins work and make them consistent with Base#count #1405 [pritchie@gmail.com]. What used to be:
+
+ Developer.find :all, :joins => 'developers_projects', :conditions => 'id=developer_id AND project_id=1'
+
+ ...should instead be:
+
+ Developer.find(
+ :all,
+ :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
+ :conditions => 'project_id=1'
+ )
+
+* Fixed that validations didn't respecting custom setting for too_short, too_long messages #1437 *Marcel Molina Jr.*
+
+* Fixed that clear_association_cache doesn't delete new associations on new records (so you can safely place new records in the session with Action Pack without having new associations wiped) #1494 *cluon*
+
+* Fixed that calling Model.find([]) returns [] and doesn't throw an exception #1379
+
+* Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 *Alisdair McDiarmid*
+
+* Fixed saving of in-memory association structures to happen as an after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice
+
+* Allow any Enumerable, not just Array, to work as bind variables #1344 *Jeremy Kemper*
+
+* Added actual database-changing behavior to collection assigment for has_many and has_and_belongs_to_many #1425 [Sebastian Kanthak].
+ Example:
+
+ david.projects = [Project.find(1), Project.new("name" => "ActionWebSearch")]
+ david.save
+
+ If david.projects already contain the project with ID 1, this is left unchanged. Any other projects are dropped. And the new
+ project is saved when david.save is called.
+
+ Also included is a way to do assignments through IDs, which is perfect for checkbox updating, so you get to do:
+
+ david.project_ids = [1, 5, 7]
+
+* Corrected typo in find SQL for has_and_belongs_to_many. #1312 *ben@bensinclair.com*
+
+* Fixed sanitized conditions for has_many finder method. #1281 *jackc@hylesanderson.com, pragdave, Tobias Lütke*
+
+* Comprehensive PostgreSQL schema support. Use the optional schema_search_path directive in database.yml to give a comma-separated list of schemas to search for your tables. This allows you, for example, to have tables in a shared schema without having to use a custom table name. See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html to learn more. #827 *dave@cherryville.org*
+
+* Corrected @@configurations typo #1410 *david@ruppconsulting.com*
+
+* Return PostgreSQL columns in the order they were declared #1374 *perlguy@gmail.com*
+
+* Allow before/after update hooks to work on models using optimistic locking
+
+* Eager loading of dependent has_one associations won't delete the association #1212
+
+* Added a second parameter to the build and create method for has_one that controls whether the existing association should be replaced (which means nullifying its foreign key as well). By default this is true, but false can be passed to prevent it.
+
+* Using transactional fixtures now causes the data to be loaded only once.
+
+* Added fixture accessor methods that can be used when instantiated fixtures are disabled.
+
+ fixtures :web_sites
+
+ def test_something
+ assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
+ end
+
+* Added DoubleRenderError exception that'll be raised if render* is called twice #518 *Nicholas Seckar*
+
+* Fixed exceptions occuring after render has been called #1096 *Nicholas Seckar*
+
+* CHANGED: validates_presence_of now uses Errors#add_on_blank, which will make " " fail the validation where it didn't before #1309
+
+* Added Errors#add_on_blank which works like Errors#add_on_empty, but uses Object#blank? instead
+
+* Added the :if option to all validations that can either use a block or a method pointer to determine whether the validation should be run or not. #1324 [Duane Johnson/jhosteny]. Examples:
+
+ Conditional validations such as the following are made possible:
+ validates_numericality_of :income, :if => :employed?
+
+ Conditional validations can also solve the salted login generator problem:
+ validates_confirmation_of :password, :if => :new_password?
+
+ Using blocks:
+ validates_presence_of :username, :if => Proc.new { |user| user.signup_step > 1 }
+
+* Fixed use of construct_finder_sql when using :join #1288 *dwlt@dwlt.net*
+
+* Fixed that :delete_sql in has_and_belongs_to_many associations couldn't access record properties #1299 *Rick Olson*
+
+* Fixed that clone would break when an aggregate had the same name as one of its attributes #1307 *Jeremy Kemper*
+
+* Changed that destroying an object will only freeze the attributes hash, which keeps the object from having attributes changed (as that wouldn't make sense), but allows for the querying of associations after it has been destroyed.
+
+* Changed the callbacks such that observers are notified before the in-object callbacks are triggered. Without this change, it wasn't possible to act on the whole object in something like a before_destroy observer without having the objects own callbacks (like deleting associations) called first.
+
+* Added option for passing an array to the find_all version of the dynamic finders and have it evaluated as an IN fragment. Example:
+
+ # SELECT * FROM topics WHERE title IN ('First', 'Second')
+ Topic.find_all_by_title(["First", "Second"])
+
+* Added compatibility with camelCase column names for dynamic finders #533 *Dee Zsombor*
+
+* Fixed extraneous comma in count() function that made it not work with joins #1156 *Jarkko Laine/Dee Zsombor*
+
+* Fixed incompatibility with Base#find with an array of ids that would fail when using eager loading #1186 *Alisdair McDiarmid*
+
+* Fixed that validate_length_of lost :on option when :within was specified #1195 *jhosteny@mac.com*
+
+* Added encoding and min_messages options for PostgreSQL #1205 [Shugo Maeda]. Configuration example:
+
+ development:
+ adapter: postgresql
+ database: rails_development
+ host: localhost
+ username: postgres
+ password:
+ encoding: UTF8
+ min_messages: ERROR
+
+* Fixed acts_as_list where deleting an item that was removed from the list would ruin the positioning of other list items #1197 *Jamis Buck*
+
+* Added validates_exclusion_of as a negative of validates_inclusion_of
+
+* Optimized counting of has_many associations by setting the association to empty if the count is 0 so repeated calls doesn't trigger database calls
+
+
+## 1.10.1 (20th April, 2005) ##
+
+* Fixed frivilous database queries being triggered with eager loading on empty associations and other things
+
+* Fixed order of loading in eager associations
+
+* Fixed stray comma when using eager loading and ordering together from has_many associations #1143
+
+
+## 1.10.0 (19th April, 2005) ##
+
+* Added eager loading of associations as a way to solve the N+1 problem more gracefully without piggy-back queries. Example:
+
+ for post in Post.find(:all, :limit => 100)
+ puts "Post: " + post.title
+ puts "Written by: " + post.author.name
+ puts "Last comment on: " + post.comments.first.created_on
+ end
+
+ This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as:
+
+ for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
+
+ ...and the number of database queries needed is now 1.
+
+* Added new unified Base.find API and deprecated the use of find_first and find_all. See the documentation for Base.find. Examples:
+
+ Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
+ Person.find(1, 5, 6, :conditions => "administrator = 1", :order => "created_on DESC")
+ Person.find(:first, :order => "created_on DESC", :offset => 5)
+ Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
+ Person.find(:all, :offset => 10, :limit => 10)
+
+* Added acts_as_nested_set #1000 [wschenk]. Introduction:
+
+ This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with
+ the added feature that you can select the children and all of it's descendants with
+ a single query. A good use case for this is a threaded post system, where you want
+ to display every reply to a comment without multiple selects.
+
+* Added Base.save! that attempts to save the record just like Base.save but will raise a RecordInvalid exception instead of returning false if the record is not valid *Dave Thomas*
+
+* Fixed PostgreSQL usage of fixtures with regards to public schemas and table names with dots #962 *gnuman1@gmail.com*
+
+* Fixed that fixtures were being deleted in the same order as inserts causing FK errors #890 *andrew.john.peters@gmail.com*
+
+* Fixed loading of fixtures in to be in the right order (or PostgreSQL would bark) #1047 *stephenh@chase3000.com*
+
+* Fixed page caching for non-vhost applications living underneath the root #1004 *Ben Schumacher*
+
+* Fixes a problem with the SQL Adapter which was resulting in IDENTITY_INSERT not being set to ON when it should be #1104 *adelle*
+
+* Added the option to specify the acceptance string in validates_acceptance_of #1106 *caleb@aei-tech.com*
+
+* Added insert_at(position) to acts_as_list #1083 *DeLynnB*
+
+* Removed the default order by id on has_and_belongs_to_many queries as it could kill performance on large sets (you can still specify by hand with :order)
+
+* Fixed that Base.silence should restore the old logger level when done, not just set it to DEBUG #1084 *yon@milliped.com*
+
+* Fixed boolean saving on Oracle #1093 *mparrish@pearware.org*
+
+* Moved build_association and create_association for has_one and belongs_to out of deprecation as they work when the association is nil unlike association.build and association.create, which require the association to be already in place #864
+
+* Added rollbacks of transactions if they're active as the dispatcher is killed gracefully (TERM signal) #1054 *Leon Bredt*
+
+* Added quoting of column names for fixtures #997 *jcfischer@gmail.com*
+
+* Fixed counter_sql when no records exist in database for PostgreSQL (would give error, not 0) #1039 *Caleb Tennis*
+
+* Fixed that benchmarking times for rendering included db runtimes #987 *Stefan Kaes*
+
+* Fixed boolean queries for t/f fields in PostgreSQL #995 *dave@cherryville.org*
+
+* Added that model.items.delete(child) will delete the child, not just set the foreign key to nil, if the child is dependent on the model #978 *Jeremy Kemper*
+
+* Fixed auto-stamping of dates (created_on/updated_on) for PostgreSQL #985 *dave@cherryville.org*
+
+* Fixed Base.silence/benchmark to only log if a logger has been configured #986 *Stefan Kaes*
+
+* Added a join parameter as the third argument to Base.find_first and as the second to Base.count #426, #988 *Stefan Kaes*
+
+* Fixed bug in Base#hash method that would treat records with the same string-based id as different *Dave Thomas*
+
+* Renamed DateHelper#distance_of_time_in_words_to_now to DateHelper#time_ago_in_words (old method name is still available as a deprecated alias)
+
+
+## 1.9.1 (27th March, 2005) ##
+
+* Fixed that Active Record objects with float attribute could not be cloned #808
+
+* Fixed that MissingSourceFile's wasn't properly detected in production mode #925 *Nicholas Seckar*
+
+* Fixed that :counter_cache option would look for a line_items_count column for a LineItem object instead of lineitems_count
+
+* Fixed that AR exists?() would explode on postgresql if the passed id did not match the PK type #900 *Scott Barron*
+
+* Fixed the MS SQL adapter to work with the new limit/offset approach and with binary data (still suffering from 7KB limit, though) #901 *delynnb*
+
+
+## 1.9.0 (22th March, 2005) ##
+
+* Added adapter independent limit clause as a two-element array with the first being the limit, the second being the offset #795 [Sam Stephenson]. Example:
+
+ Developer.find_all nil, 'id ASC', 5 # return the first five developers
+ Developer.find_all nil, 'id ASC', [3, 8] # return three developers, starting from #8 and forward
+
+ This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
+
+* Added alias_method :to_param, :id to Base, such that Active Record objects to be used as URL parameters in Action Pack automatically #812 *Nicholas Seckar/Sam Stephenson*
+
+* Improved the performance of the OCI8 adapter for Oracle #723 *pilx/gjenkins*
+
+* Added type conversion before saving a record, so string-based values like "10.0" aren't left for the database to convert #820 *dave@cherryville.org*
+
+* Added with additional settings for working with transactional fixtures and pre-loaded test databases #865 *mindel*
+
+* Fixed acts_as_list to trigger remove_from_list on destroy after the fact, not before, so a unique position can be maintained #871 *Alisdair McDiarmid*
+
+* Added the possibility of specifying fixtures in multiple calls #816 *kim@tinker.com*
+
+* Added Base.exists?(id) that'll return true if an object of the class with the given id exists #854 *stian@grytoyr.net*
+
+* Added optionally allow for nil or empty strings with validates_numericality_of #801 *Sebastian Kanthak*
+
+* Fixed problem with using slashes in validates_format_of regular expressions #801 *Sebastian Kanthak*
+
+* Fixed that SQLite3 exceptions are caught and reported properly #823 *yerejm*
+
+* Added that all types of after_find/after_initialized callbacks are triggered if the explicit implementation is present, not only the explicit implementation itself
+
+* Fixed that symbols can be used on attribute assignment, like page.emails.create(:subject => data.subject, :body => data.body)
+
+
+## 1.8.0 (7th March, 2005) ##
+
+* Added ActiveRecord::Base.colorize_logging to control whether to use colors in logs or not (on by default)
+
+* Added support for timestamp with time zone in PostgreSQL #560 *Scott Barron*
+
+* Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius]. Documentation:
+
+ * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
+ +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
+ objects that should be inspected to determine which attributes triggered the errors.
+ * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
+ You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
+
+* Fixed that postgresql adapter would fails when reading bytea fields with null value #771 *rodrigo k*
+
+* Added transactional fixtures that uses rollback to undo changes to fixtures instead of DELETE/INSERT -- it's much faster. See documentation under Fixtures #760 *Jeremy Kemper*
+
+* Added destruction of dependent objects in has_one associations when a new assignment happens #742 [mindel]. Example:
+
+ class Account < ActiveRecord::Base
+ has_one :credit_card, :dependent => true
+ end
+ class CreditCard < ActiveRecord::Base
+ belongs_to :account
+ end
+
+ account.credit_card # => returns existing credit card, lets say id = 12
+ account.credit_card = CreditCard.create("number" => "123")
+ account.save # => CC with id = 12 is destroyed
+
+
+* Added validates_numericality_of #716 [Sebastian Kanthak/Chris McGrath]. Docuemntation:
+
+ Validates whether the value of the specified attribute is numeric by trying to convert it to
+ a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
+ <tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true).
+
+ class Person < ActiveRecord::Base
+ validates_numericality_of :value, :on => :create
+ end
+
+ Configuration options:
+ * <tt>message</tt> - A custom error message (default is: "is not a number")
+ * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
+ * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
+
+
+* Fixed that HasManyAssociation#count was using :finder_sql rather than :counter_sql if it was available #445 *Scott Barron*
+
+* Added better defaults for composed_of, so statements like composed_of :time_zone, :mapping => %w( time_zone time_zone ) can be written without the mapping part (it's now assumed)
+
+* Added MacroReflection#macro which will return a symbol describing the macro used (like :composed_of or :has_many) #718, #248 *james@slashetc.com*
+
+
+## 1.7.0 (24th February, 2005) ##
+
+* Changed the auto-timestamping feature to use ActiveRecord::Base.default_timezone instead of entertaining the parallel ActiveRecord::Base.timestamps_gmt method. The latter is now deprecated and will throw a warning on use (but still work) #710 *Jamis Buck*
+
+* Added a OCI8-based Oracle adapter that has been verified to work with Oracle 8 and 9 #629 [Graham Jenkins]. Usage notes:
+
+ 1. Key generation uses a sequence "rails_sequence" for all tables. (I couldn't find a simple
+ and safe way of passing table-specific sequence information to the adapter.)
+ 2. Oracle uses DATE or TIMESTAMP datatypes for both dates and times. Consequently I have had to
+ resort to some hacks to get data converted to Date or Time in Ruby.
+ If the column_name ends in _at (like created_at, updated_at) it's created as a Ruby Time. Else if the
+ hours/minutes/seconds are 0, I make it a Ruby Date. Else it's a Ruby Time.
+ This is nasty - but if you use Duck Typing you'll probably not care very much.
+ In 9i it's tempting to map DATE to Date and TIMESTAMP to Time but I don't think that is
+ valid - too many databases use DATE for both.
+ Timezones and sub-second precision on timestamps are not supported.
+ 3. Default values that are functions (such as "SYSDATE") are not supported. This is a
+ restriction of the way active record supports default values.
+ 4. Referential integrity constraints are not fully supported. Under at least
+ some circumstances, active record appears to delete parent and child records out of
+ sequence and out of transaction scope. (Or this may just be a problem of test setup.)
+
+ The OCI8 driver can be retrieved from http://rubyforge.org/projects/ruby-oci8/
+
+* Added option :schema_order to the PostgreSQL adapter to support the use of multiple schemas per database #697 *YuriSchimke*
+
+* Optimized the SQL used to generate has_and_belongs_to_many queries by listing the join table first #693 *yerejm*
+
+* Fixed that when using validation macros with a custom message, if you happened to use single quotes in the message string you would get a parsing error #657 *tonka*
+
+* Fixed that Active Record would throw Broken Pipe errors with FCGI when the MySQL connection timed out instead of reconnecting #428 *Nicholas Seckar*
+
+* Added options to specify an SSL connection for MySQL. Define the following attributes in the connection config (config/database.yml in Rails) to use it: sslkey, sslcert, sslca, sslcapath, sslcipher. To use SSL with no client certs, just set :sslca = '/dev/null'. http://dev.mysql.com/doc/mysql/en/secure-connections.html #604 *daniel@nightrunner.com*
+
+* Added automatic dropping/creating of test tables for running the unit tests on all databases #587 *adelle@bullet.net.au*
+
+* Fixed that find_by_* would fail when column names had numbers #670 *demetrius*
+
+* Fixed the SQL Server adapter on a bunch of issues #667 *DeLynn*
+
+ 1. Created a new columns method that is much cleaner.
+ 2. Corrected a problem with the select and select_all methods
+ that didn't account for the LIMIT clause being passed into raw SQL statements.
+ 3. Implemented the string_to_time method in order to create proper instances of the time class.
+ 4. Added logic to the simplified_type method that allows the database to specify the scale of float data.
+ 5. Adjusted the quote_column_name to account for the fact that MS SQL is bothered by a forward slash in the data string.
+
+* Fixed that the dynamic finder like find_all_by_something_boolean(false) didn't work #649 *lmarlow*
+
+* Added validates_each that validates each specified attribute against a block #610 [Jeremy Kemper]. Example:
+
+ class Person < ActiveRecord::Base
+ validates_each :first_name, :last_name do |record, attr|
+ record.errors.add attr, 'starts with z.' if attr[0] == ?z
+ end
+ end
+
+* Added :allow_nil as an explicit option for validates_length_of, so unless that's set to true having the attribute as nil will also return an error if a range is specified as :within #610 *Jeremy Kemper*
+
+* Added that validates_* now accept blocks to perform validations #618 [Tim Bates]. Example:
+
+ class Person < ActiveRecord::Base
+ validate { |person| person.errors.add("title", "will never be valid") if SHOULD_NEVER_BE_VALID }
+ end
+
+* Addded validation for validate all the associated objects before declaring failure with validates_associated #618 *Tim Bates*
+
+* Added keyword-style approach to defining the custom relational bindings #545 [Jamis Buck]. Example:
+
+ class Project < ActiveRecord::Base
+ primary_key "sysid"
+ table_name "XYZ_PROJECT"
+ inheritance_column { original_inheritance_column + "_id" }
+ end
+
+* Fixed Base#clone for use with PostgreSQL #565 *hanson@surgery.wisc.edu*
+
+
+## 1.6.0 (January 25th, 2005) ##
+
+* Added that has_many association build and create methods can take arrays of record data like Base#create and Base#build to build/create multiple records at once.
+
+* Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336
+
+* Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once.
+
+* Added the option of supplying an array of ids and attributes to Base#update, so that multiple records can be updated at once (inspired by #526/Duane Johnson). Example
+
+ people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
+ Person.update(people.keys, people.values)
+
+* Added ActiveRecord::Base.timestamps_gmt that can be set to true to make the automated timestamping use GMT instead of local time #520 *Scott Baron*
+
+* Added that update_all calls sanitize_sql on its updates argument, so stuff like MyRecord.update_all(['time = ?', Time.now]) works #519 *notahat*
+
+* Fixed that the dynamic finders didn't treat nil as a "IS NULL" but rather "= NULL" case #515 *Demetrius*
+
+* Added bind-named arrays for interpolating a group of ids or strings in conditions #528 *Jeremy Kemper*
+
+* Added that has_and_belongs_to_many associations with additional attributes also can be created between unsaved objects and only committed to the database when Base#save is called on the associator #524 *Eric Anderson*
+
+* Fixed that records fetched with piggy-back attributes or through rich has_and_belongs_to_many associations couldn't be saved due to the extra attributes not part of the table #522 *Eric Anderson*
+
+* Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not
+
+* Fixed that association proxies would fail === tests like PremiumSubscription === @account.subscription
+
+* Fixed that column aliases didn't work as expected with the new MySql411 driver #507 *Demetrius*
+
+* Fixed that find_all would produce invalid sql when called sequentialy #490 *Scott Baron*
+
+
+## 1.5.1 (January 18th, 2005) ##
+
+* Fixed that the belongs_to and has_one proxy would fail a test like 'if project.manager' -- this unfortunately also means that you can't call methods like project.manager.build unless there already is a manager on the project #492 *Tim Bates*
+
+* Fixed that the Ruby/MySQL adapter wouldn't connect if the password was empty #503 *Pelle*
+
+
+## 1.5.0 (January 17th, 2005) ##
+
+* Fixed that unit tests for MySQL are now run as the "rails" user instead of root #455 *Eric Hodel*
+
+* Added validates_associated that enables validation of objects in an unsaved association #398 [Tim Bates]. Example:
+
+ class Book < ActiveRecord::Base
+ has_many :pages
+ belongs_to :library
+
+ validates_associated :pages, :library
+ end
+
+* Added support for associating unsaved objects #402 [Tim Bates]. Rules that govern this addition:
+
+ == Unsaved objects and associations
+
+ You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
+ aware of, mostly involving the saving of associated objects.
+
+ === One-to-one associations
+
+ * Assigning an object to a has_one association automatically saves that object, and the object being replaced (if there is one), in
+ order to update their primary keys - except if the parent object is unsaved (new_record? == true).
+ * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
+ is cancelled.
+ * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
+ * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
+ not save the parent either.
+
+ === Collections
+
+ * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
+ (the owner of the collection) is not yet stored in the database.
+ * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
+ * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
+ * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
+
+* Added replace to associations, so you can do project.manager.replace(new_manager) or project.milestones.replace(new_milestones) #402 *Tim Bates*
+
+* Added build and create methods to has_one and belongs_to associations, so you can now do project.manager.build(attributes) #402 *Tim Bates*
+
+* Added that if a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last. #402 *Tim Bates*
+
+* Fixed that Base#== wouldn't work for multiple references to the same unsaved object #402 *Tim Bates*
+
+* Fixed binary support for PostgreSQL #444 *alex@byzantine.no*
+
+* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
+ collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
+ it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
+ it'll take one less SELECT query if you use length.
+
+* Added Base#attributes that returns a hash of all the attributes with their names as keys and clones of their objects as values #433 *atyp.de*
+
+* Fixed that foreign keys named the same as the association would cause stack overflow #437 *Eric Anderson*
+
+* Fixed default scope of acts_as_list from "1" to "1 = 1", so it'll work in PostgreSQL (among other places) #427 *Alexey*
+
+* Added Base#reload that reloads the attributes of an object from the database #422 *Andreas Schwarz*
+
+* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 *Jeremy Kemper*
+
+* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 *Matt Mower*
+
+* Added that Observers can use the observes class method instead of overwriting self.observed_class().
+
+ Before:
+ class ListSweeper < ActiveRecord::Base
+ def self.observed_class() [ List, Item ]
+ end
+
+ After:
+ class ListSweeper < ActiveRecord::Base
+ observes List, Item
+ end
+
+* Fixed that conditions in has_many and has_and_belongs_to_many should be interpolated just like the finder_sql is
+
+* Fixed Base#update_attribute to be indifferent to whether a string or symbol is used to describe the name
+
+* Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it easier to flip a switch or flag.
+
+ Before: topic.update_attribute(:approved, !approved?)
+ After : topic.toggle!(:approved)
+
+* Added Base#increment!(attribute) and Base#decrement!(attribute) that also saves the records. Example:
+
+ page.views # => 1
+ page.increment!(:views) # executes an UPDATE statement
+ page.views # => 2
+
+ page.increment(:views).increment!(:views)
+ page.views # => 4
+
+* Added Base#increment(attribute) and Base#decrement(attribute) that encapsulates the += 1 and -= 1 patterns.
+
+
+
+
+## 1.14.2 (April 9th, 2005) ##
+
+* Fixed calculations for the Oracle Adapter (closes #4626) *Michael Schoen*
+
+
+## 1.14.1 (April 6th, 2006) ##
+
+* Fix type_name_with_module to handle type names that begin with '::'. Closes #4614. *Nicholas Seckar*
+
+* Fixed that that multiparameter assignment doesn't work with aggregations (closes #4620) *Lars Pind*
+
+* Enable Limit/Offset in Calculations (closes #4558) *lmarlow*
+
+* Fixed that loading including associations returns all results if Load IDs For Limited Eager Loading returns none (closes #4528) *Rick Olson*
+
+* Fixed HasManyAssociation#find bugs when :finder_sql is set #4600 *lagroue@free.fr*
+
+* Allow AR::Base#respond_to? to behave when @attributes is nil *Ryan Davis*
+
+* Support eager includes when going through a polymorphic has_many association. *Rick Olson*
+
+* Added support for eagerly including polymorphic has_one associations. (closes #4525) *Rick Olson*
+
+ class Post < ActiveRecord::Base
+ has_one :tagging, :as => :taggable
+ end
+
+ Post.find :all, :include => :tagging
+
+* Added descriptive error messages for invalid has_many :through associations: going through :has_one or :has_and_belongs_to_many *Rick Olson*
+
+* Added support for going through a polymorphic has_many association: (closes #4401) *Rick Olson*
+
+ class PhotoCollection < ActiveRecord::Base
+ has_many :photos, :as => :photographic
+ belongs_to :firm
+ end
+
+ class Firm < ActiveRecord::Base
+ has_many :photo_collections
+ has_many :photos, :through => :photo_collections
+ end
+
+* Multiple fixes and optimizations in PostgreSQL adapter, allowing ruby-postgres gem to work properly. *ruben.nine@gmail.com*
+
+* Fixed that AssociationCollection#delete_all should work even if the records of the association are not loaded yet. *Florian Weber*
+
+* Changed those private ActiveRecord methods to take optional third argument :auto instead of nil for performance optimizations. (closes #4456) *Stefan*
+
+* Private ActiveRecord methods add_limit!, add_joins!, and add_conditions! take an OPTIONAL third argument 'scope' (closes #4456) *Rick Olson*
+
+* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model *David Heinemeier Hansson*
+
+* Fixed that records returned from has_and_belongs_to_many associations with additional attributes should be marked as read only (fixes #4512) *David Heinemeier Hansson*
+
+* Do not implicitly mark recordss of has_many :through as readonly but do mark habtm records as readonly (eventually only on join tables without rich attributes). *Marcel Mollina Jr.*
+
+* Fixed broken OCIAdapter #4457 *Michael Schoen*
+
+
+## 1.14.0 (March 27th, 2006) ##
+
+* Replace 'rescue Object' with a finer grained rescue. Closes #4431. *Nicholas Seckar*
+
+* Fixed eager loading so that an aliased table cannot clash with a has_and_belongs_to_many join table *Rick Olson*
+
+* Add support for :include to with_scope *andrew@redlinesoftware.com*
+
+* Support the use of public synonyms with the Oracle adapter; required ruby-oci8 v0.1.14 #4390 *Michael Schoen*
+
+* Change periods (.) in table aliases to _'s. Closes #4251 *jeff@ministrycentered.com*
+
+* Changed has_and_belongs_to_many join to INNER JOIN for Mysql 3.23.x. Closes #4348 *Rick Olson*
+
+* Fixed issue that kept :select options from being scoped *Rick Olson*
+
+* Fixed db_schema_import when binary types are present #3101 *David Heinemeier Hansson*
+
+* Fixed that MySQL enums should always be returned as strings #3501 *David Heinemeier Hansson*
+
+* Change has_many :through to use the :source option to specify the source association. :class_name is now ignored. *Rick Olson*
+
+ class Connection < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :channel
+ end
+
+ class Channel < ActiveRecord::Base
+ has_many :connections
+ has_many :contacts, :through => :connections, :class_name => 'User' # OLD
+ has_many :contacts, :through => :connections, :source => :user # NEW
+ end
+
+* Fixed DB2 adapter so nullable columns will be determines correctly now and quotes from column default values will be removed #4350 *contact@maik-schmidt.de*
+
+* Allow overriding of find parameters in scoped has_many :through calls *Rick Olson*
+
+ In this example, :include => false disables the default eager association from loading. :select changes the standard
+ select clause. :joins specifies a join that is added to the end of the has_many :through query.
+
+ class Post < ActiveRecord::Base
+ has_many :tags, :through => :taggings, :include => :tagging do
+ def add_joins_and_select
+ find :all, :select => 'tags.*, authors.id as author_id', :include => false,
+ :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
+ end
+ end
+ end
+
+* Fixed that schema changes while the database was open would break any connections to an SQLite database (now we reconnect if that error is throw) *David Heinemeier Hansson*
+
+* Don't classify the has_one class when eager loading, it is already singular. Add tests. (closes #4117) *Jonathan Viney*
+
+* Quit ignoring default :include options in has_many :through calls *Mark James*
+
+* Allow has_many :through associations to find the source association by setting a custom class (closes #4307) *Jonathan Viney*
+
+* Eager Loading support added for has_many :through => :has_many associations (see below). *Rick Olson*
+
+* Allow has_many :through to work on has_many associations (closes #3864) [sco@scottraymond.net] Example:
+
+ class Firm < ActiveRecord::Base
+ has_many :clients
+ has_many :invoices, :through => :clients
+ end
+
+ class Client < ActiveRecord::Base
+ belongs_to :firm
+ has_many :invoices
+ end
+
+ class Invoice < ActiveRecord::Base
+ belongs_to :client
+ end
+
+* Raise error when trying to select many polymorphic objects with has_many :through or :include (closes #4226) *Josh Susser*
+
+* Fixed has_many :through to include :conditions set on the :through association. closes #4020 *Jonathan Viney*
+
+* Fix that has_many :through honors the foreign key set by the belongs_to association in the join model (closes #4259) *andylien@gmail.com / Rick Olson*
+
+* SQL Server adapter gets some love #4298 *Ryan Tomayko*
+
+* Added OpenBase database adapter that builds on top of the http://www.spice-of-life.net/ruby-openbase/ driver. All functionality except LIMIT/OFFSET is supported #3528 *derrickspell@cdmplus.com*
+
+* Rework table aliasing to account for truncated table aliases. Add smarter table aliasing when doing eager loading of STI associations. This allows you to use the association name in the order/where clause. [Jonathan Viney / Rick Olson] #4108 Example (SpecialComment is using STI):
+
+ Author.find(:all, :include => { :posts => :special_comments }, :order => 'special_comments.body')
+
+* Add AbstractAdapter#table_alias_for to create table aliases according to the rules of the current adapter. *Rick Olson*
+
+* Provide access to the underlying database connection through Adapter#raw_connection. Enables the use of db-specific methods without complicating the adapters. #2090 *Michael Koziarski*
+
+* Remove broken attempts at handling columns with a default of 'now()' in the postgresql adapter. #2257 *Michael Koziarski*
+
+* Added connection#current_database that'll return of the current database (only works in MySQL, SQL Server, and Oracle so far -- please help implement for the rest of the adapters) #3663 *Tom Ward*
+
+* Fixed that Migration#execute would have the table name prefix appended to its query #4110 *mark.imbriaco@pobox.com*
+
+* Make all tinyint(1) variants act like boolean in mysql (tinyint(1) unsigned, etc.) *Jamis Buck*
+
+* Use association's :conditions when eager loading. [Jeremy Evans] #4144
+
+* Alias the has_and_belongs_to_many join table on eager includes. #4106 *Jeremy Evans*
+
+ This statement would normally error because the projects_developers table is joined twice, and therefore joined_on would be ambiguous.
+
+ Developer.find(:all, :include => {:projects => :developers}, :conditions => 'join_project_developers.joined_on IS NOT NULL')
+
+* Oracle adapter gets some love #4230 *Michael Schoen*
+
+ * Changes :text to CLOB rather than BLOB [Moses Hohman]
+ * Fixes an issue with nil numeric length/scales (several)
+ * Implements support for XMLTYPE columns [wilig / Kubo Takehiro]
+ * Tweaks a unit test to get it all green again
+ * Adds support for #current_database
+
+* Added Base.abstract_class? that marks which classes are not part of the Active Record hierarchy #3704 *Rick Olson*
+
+ class CachedModel < ActiveRecord::Base
+ self.abstract_class = true
+ end
+
+ class Post < CachedModel
+ end
+
+ CachedModel.abstract_class?
+ => true
+
+ Post.abstract_class?
+ => false
+
+ Post.base_class
+ => Post
+
+ Post.table_name
+ => 'posts'
+
+* Allow :dependent options to be used with polymorphic joins. #3820 *Rick Olson*
+
+ class Foo < ActiveRecord::Base
+ has_many :attachments, :as => :attachable, :dependent => :delete_all
+ end
+
+* Nicer error message on has_many :through when :through reflection can not be found. #4042 *court3nay*
+
+* Upgrade to Transaction::Simple 1.3 *Jamis Buck*
+
+* Catch FixtureClassNotFound when using instantiated fixtures on a fixture that has no ActiveRecord model *Rick Olson*
+
+* Allow ordering of calculated results and/or grouped fields in calculations *solo@gatelys.com*
+
+* Make ActiveRecord::Base#save! return true instead of nil on success. #4173 *johan@johansorensen.com*
+
+* Dynamically set allow_concurrency. #4044 *Stefan Kaes*
+
+* Added Base#to_xml that'll turn the current record into a XML representation [David Heinemeier Hansson]. Example:
+
+ topic.to_xml
+
+ ...returns:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <topic>
+ <title>The First Topic</title>
+ <author-name>David</author-name>
+ <id type="integer">1</id>
+ <approved type="boolean">false</approved>
+ <replies-count type="integer">0</replies-count>
+ <bonus-time type="datetime">2000-01-01 08:28:00</bonus-time>
+ <written-on type="datetime">2003-07-16 09:28:00</written-on>
+ <content>Have a nice day</content>
+ <author-email-address>david@loudthinking.com</author-email-address>
+ <parent-id></parent-id>
+ <last-read type="date">2004-04-15</last-read>
+ </topic>
+
+ ...and you can configure with:
+
+ topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
+
+ ...that'll return:
+
+ <topic>
+ <title>The First Topic</title>
+ <author-name>David</author-name>
+ <approved type="boolean">false</approved>
+ <content>Have a nice day</content>
+ <author-email-address>david@loudthinking.com</author-email-address>
+ <parent-id></parent-id>
+ <last-read type="date">2004-04-15</last-read>
+ </topic>
+
+ You can even do load first-level associations as part of the document:
+
+ firm.to_xml :include => [ :account, :clients ]
+
+ ...that'll return something like:
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <firm>
+ <id type="integer">1</id>
+ <rating type="integer">1</rating>
+ <name>37signals</name>
+ <clients>
+ <client>
+ <rating type="integer">1</rating>
+ <name>Summit</name>
+ </client>
+ <client>
+ <rating type="integer">1</rating>
+ <name>Microsoft</name>
+ </client>
+ </clients>
+ <account>
+ <id type="integer">1</id>
+ <credit-limit type="integer">50</credit-limit>
+ </account>
+ </firm>
+
+* Allow :counter_cache to take a column name for custom counter cache columns *Jamis Buck*
+
+* Documentation fixes for :dependent *robby@planetargon.com*
+
+* Stop the MySQL adapter crashing when views are present. #3782 *Jonathan Viney*
+
+* Don't classify the belongs_to class, it is already singular #4117 *keithm@infused.org*
+
+* Allow set_fixture_class to take Classes instead of strings for a class in a module. Raise FixtureClassNotFound if a fixture can't load. *Rick Olson*
+
+* Fix quoting of inheritance column for STI eager loading #4098 *Jonathan Viney <jonathan@bluewire.net.nz>*
+
+* Added smarter table aliasing for eager associations for multiple self joins #3580 *Rick Olson*
+
+ * The first time a table is referenced in a join, no alias is used.
+ * After that, the parent class name and the reflection name are used.
+
+ Tree.find(:all, :include => :children) # LEFT OUTER JOIN trees AS tree_children ...
+
+ * Any additional join references get a numerical suffix like '_2', '_3', etc.
+
+* Fixed eager loading problems with single-table inheritance #3580 [Rick Olson]. Post.find(:all, :include => :special_comments) now returns all posts, and any special comments that the posts may have. And made STI work with has_many :through and polymorphic belongs_to.
+
+* Added cascading eager loading that allows for queries like Author.find(:all, :include=> { :posts=> :comments }), which will fetch all authors, their posts, and the comments belonging to those posts in a single query (using LEFT OUTER JOIN) #3913 [anna@wota.jp]. Examples:
+
+ # cascaded in two levels
+ >> Author.find(:all, :include=>{:posts=>:comments})
+ => authors
+ +- posts
+ +- comments
+
+ # cascaded in two levels and normal association
+ >> Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
+ => authors
+ +- posts
+ +- comments
+ +- categorizations
+
+ # cascaded in two levels with two has_many associations
+ >> Author.find(:all, :include=>{:posts=>[:comments, :categorizations]})
+ => authors
+ +- posts
+ +- comments
+ +- categorizations
+
+ # cascaded in three levels
+ >> Company.find(:all, :include=>{:groups=>{:members=>{:favorites}}})
+ => companies
+ +- groups
+ +- members
+ +- favorites
+
+* Make counter cache work when replacing an association #3245 *eugenol@gmail.com*
+
+* Make migrations verbose *Jamis Buck*
+
+* Make counter_cache work with polymorphic belongs_to *Jamis Buck*
+
+* Fixed that calling HasOneProxy#build_model repeatedly would cause saving to happen #4058 *anna@wota.jp*
+
+* Added Sybase database adapter that relies on the Sybase Open Client bindings (see http://raa.ruby-lang.org/project/sybase-ctlib) #3765 [John Sheets]. It's almost completely Active Record compliant (including migrations), but has the following caveats:
+
+ * Does not support DATE SQL column types; use DATETIME instead.
+ * Date columns on HABTM join tables are returned as String, not Time.
+ * Insertions are potentially broken for :polymorphic join tables
+ * BLOB column access not yet fully supported
+
+* Clear stale, cached connections left behind by defunct threads. *Jeremy Kemper*
+
+* CHANGED DEFAULT: set ActiveRecord::Base.allow_concurrency to false. Most AR usage is in single-threaded applications. *Jeremy Kemper*
+
+* Renamed the "oci" adapter to "oracle", but kept the old name as an alias #4017 *Michael Schoen*
+
+* Fixed that Base.save should always return false if the save didn't succeed, including if it has halted by before_save's #1861, #2477 *David Heinemeier Hansson*
+
+* Speed up class -> connection caching and stale connection verification. #3979 *Stefan Kaes*
+
+* Add set_fixture_class to allow the use of table name accessors with models which use set_table_name. *Kevin Clark*
+
+* Added that fixtures to placed in subdirectories of the main fixture files are also loaded #3937 *dblack@wobblini.net*
+
+* Define attribute query methods to avoid method_missing calls. #3677 *Jonathan Viney*
+
+* ActiveRecord::Base.remove_connection explicitly closes database connections and doesn't corrupt the connection cache. Introducing the disconnect! instance method for the PostgreSQL, MySQL, and SQL Server adapters; implementations for the others are welcome. #3591 *Simon Stapleton, Tom Ward*
+
+* Added support for nested scopes #3407 [anna@wota.jp]. Examples:
+
+ Developer.with_scope(:find => { :conditions => "salary > 10000", :limit => 10 }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (salary > 10000) LIMIT 10
+
+ # inner rule is used. (all previous parameters are ignored)
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (name = 'Jamis')
+ end
+
+ # parameters are merged
+ Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (( salary > 10000 ) AND ( name = 'Jamis' )) LIMIT 10
+ end
+ end
+
+* Fixed db2 connection with empty user_name and auth options #3622 *phurley@gmail.com*
+
+* Fixed validates_length_of to work on UTF-8 strings by using characters instead of bytes #3699 *Masao Mutoh*
+
+* Fixed that reflections would bleed across class boundaries in single-table inheritance setups #3796 *Lars Pind*
+
+* Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Base.maxmium, and the generic Base.calculate. All can be used with :group and :having. Calculations and statitics need no longer require custom SQL. #3958 [Rick Olson]. Examples:
+
+ Person.average :age
+ Person.minimum :age
+ Person.maximum :age
+ Person.sum :salary, :group => :last_name
+
+* Renamed Errors#count to Errors#size but kept an alias for the old name (and included an alias for length too) #3920 *Luke Redpath*
+
+* Reflections don't attempt to resolve module nesting of association classes. Simplify type computation. *Jeremy Kemper*
+
+* Improved the Oracle OCI Adapter with better performance for column reflection (from #3210), fixes to migrations (from #3476 and #3742), tweaks to unit tests (from #3610), and improved documentation (from #2446) #3879 *Aggregated by schoenm@earthlink.net*
+
+* Fixed that the schema_info table used by ActiveRecord::Schema.define should respect table pre- and suffixes #3834 *rubyonrails@atyp.de*
+
+* Added :select option to Base.count that'll allow you to select something else than * to be counted on. Especially important for count queries using DISTINCT #3839 *Stefan Kaes*
+
+* Correct syntax error in mysql DDL, and make AAACreateTablesTest run first *Bob Silva*
+
+* Allow :include to be used with has_many :through associations #3611 *Michael Schoen*
+
+* PostgreSQL: smarter schema dumps using pk_and_sequence_for(table). #2920 *Blair Zajac*
+
+* SQLServer: more compatible limit/offset emulation. #3779 *Tom Ward*
+
+* Polymorphic join support for has_one associations (has_one :foo, :as => :bar) #3785 *Rick Olson*
+
+* PostgreSQL: correctly parse negative integer column defaults. #3776 *bellis@deepthought.org*
+
+* Fix problems with count when used with :include *Jeremy Hopple and Kevin Clark*
+
+* ActiveRecord::RecordInvalid now states which validations failed in its default error message *Tobias Lütke*
+
+* Using AssociationCollection#build with arrays of hashes should call build, not create *David Heinemeier Hansson*
+
+* Remove definition of reloadable? from ActiveRecord::Base to make way for new Reloadable code. *Nicholas Seckar*
+
+* Fixed schema handling for DB2 adapter that didn't work: an initial schema could be set, but it wasn't used when getting tables and indexes #3678 *Maik Schmidt*
+
+* Support the :column option for remove_index with the PostgreSQL adapter. #3661 *Shugo Maeda*
+
+* Add documentation for add_index and remove_index. #3600 *Manfred Stienstra <m.stienstra@fngtps.com>*
+
+* If the OCI library is not available, raise an exception indicating as much. #3593 *Michael Schoen*
+
+* Add explicit :order in finder tests as postgresql orders results differently by default. #3577. *Rick Olson*
+
+* Make dynamic finders honor additional passed in :conditions. #3569 *Oleg Pudeyev <pudeyo@rpi.edu>, Marcel Molina Jr.*
+
+* Show a meaningful error when the DB2 adapter cannot be loaded due to missing dependencies. *Nicholas Seckar*
+
+* Make .count work for has_many associations with multi line finder sql *Michael Schoen*
+
+* Add AR::Base.base_class for querying the ancestor AR::Base subclass *Jamis Buck*
+
+* Allow configuration of the column used for optimistic locking *wilsonb@gmail.com*
+
+* Don't hardcode 'id' in acts as list. *ror@philippeapril.com*
+
+* Fix date errors for SQLServer in association tests. #3406 *Kevin Clark*
+
+* Escape database name in MySQL adapter when creating and dropping databases. #3409 *anna@wota.jp*
+
+* Disambiguate table names for columns in validates_uniqueness_of's WHERE clause. #3423 *alex.borovsky@gmail.com*
+
+* .with_scope imposed create parameters now bypass attr_protected *Tobias Lütke*
+
+* Don't raise an exception when there are more keys than there are named bind variables when sanitizing conditions. *Marcel Molina Jr.*
+
+* Multiple enhancements and adjustments to DB2 adaptor. #3377 *contact@maik-schmidt.de*
+
+* Sanitize scoped conditions. *Marcel Molina Jr.*
+
+* Added option to Base.reflection_of_all_associations to specify a specific association to scope the call. For example Base.reflection_of_all_associations(:has_many) *David Heinemeier Hansson*
+
+* Added ActiveRecord::SchemaDumper.ignore_tables which tells SchemaDumper which tables to ignore. Useful for tables with funky column like the ones required for tsearch2. *Tobias Lütke*
+
+* SchemaDumper now doesn't fail anymore when there are unknown column types in the schema. Instead the table is ignored and a Comment is left in the schema.rb. *Tobias Lütke*
+
+* Fixed that saving a model with multiple habtm associations would only save the first one. #3244 *yanowitz-rubyonrails@quantumfoam.org, Florian Weber*
+
+* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 *wejn@box.cz, Rick Olson, Scott Barron*
+
+* removed :piggyback in favor of just allowing :select on :through associations. *Tobias Lütke*
+
+* made method missing delegation to class methods on relation target work on :through associations. *Tobias Lütke*
+
+* made .find() work on :through relations. *Tobias Lütke*
+
+* Fix typo in association docs. #3296. *Blair Zajac*
+
+* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model *Tobias Lütke*
+
+## 1.13.2 (December 13th, 2005) ##
+
+* Become part of Rails 1.0
+
+* MySQL: allow encoding option for mysql.rb driver. *Jeremy Kemper*
+
+* Added option inheritance for find calls on has_and_belongs_to_many and has_many assosociations [David Heinemeier Hansson]. Example:
+
+ class Post
+ has_many :recent_comments, :class_name => "Comment", :limit => 10, :include => :author
+ end
+
+ post.recent_comments.find(:all) # Uses LIMIT 10 and includes authors
+ post.recent_comments.find(:all, :limit => nil) # Uses no limit but include authors
+ post.recent_comments.find(:all, :limit => nil, :include => nil) # Uses no limit and doesn't include authors
+
+* Added option to specify :group, :limit, :offset, and :select options from find on has_and_belongs_to_many and has_many assosociations *David Heinemeier Hansson*
+
+* MySQL: fixes for the bundled mysql.rb driver. #3160 *Justin Forder*
+
+* SQLServer: fix obscure optimistic locking bug. #3068 *kajism@yahoo.com*
+
+* SQLServer: support uniqueidentifier columns. #2930 *keithm@infused.org*
+
+* SQLServer: cope with tables names qualified by owner. #3067 *jeff@ministrycentered.com*
+
+* SQLServer: cope with columns with "desc" in the name. #1950 *Ron Lusk, Ryan Tomayko*
+
+* SQLServer: cope with primary keys with "select" in the name. #3057 *rdifrango@captechventures.com*
+
+* Oracle: active? performs a select instead of a commit. #3133 *Michael Schoen*
+
+* MySQL: more robust test for nullified result hashes. #3124 *Stefan Kaes*
+
+* Reloading an instance refreshes its aggregations as well as its associations. #3024 *François Beausoleil*
+
+* Fixed that using :include together with :conditions array in Base.find would cause NoMethodError #2887 *Paul Hammmond*
+
+* PostgreSQL: more robust sequence name discovery. #3087 *Rick Olson*
+
+* Oracle: use syntax compatible with Oracle 8. #3131 *Michael Schoen*
+
+* MySQL: work around ruby-mysql/mysql-ruby inconsistency with mysql.stat. Eliminate usage of mysql.ping because it doesn't guarantee reconnect. Explicitly close and reopen the connection instead. *Jeremy Kemper*
+
+* Added preliminary support for polymorphic associations *David Heinemeier Hansson*
+
+* Added preliminary support for join models *David Heinemeier Hansson*
+
+* Allow validate_uniqueness_of to be scoped by more than just one column. #1559. *jeremy@jthopple.com, Marcel Molina Jr.*
+
+* Firebird: active? and reconnect! methods for handling stale connections. #428 *Ken Kunz <kennethkunz@gmail.com>*
+
+* Firebird: updated for FireRuby 0.4.0. #3009 *Ken Kunz <kennethkunz@gmail.com>*
+
+* MySQL and PostgreSQL: active? compatibility with the pure-Ruby driver. #428 *Jeremy Kemper*
+
+* Oracle: active? check pings the database rather than testing the last command status. #428 *Michael Schoen*
+
+* SQLServer: resolve column aliasing/quoting collision when using limit or offset in an eager find. #2974 *kajism@yahoo.com*
+
+* Reloading a model doesn't lose track of its connection. #2996 *junk@miriamtech.com, Jeremy Kemper*
+
+* Fixed bug where using update_attribute after pushing a record to a habtm association of the object caused duplicate rows in the join table. #2888 *colman@rominato.com, Florian Weber, Michael Schoen*
+
+* MySQL, PostgreSQL: reconnect! also reconfigures the connection. Otherwise, the connection 'loses' its settings if it times out and is reconnected. #2978 *Shugo Maeda*
+
+* has_and_belongs_to_many: use JOIN instead of LEFT JOIN. *Jeremy Kemper*
+
+* MySQL: introduce :encoding option to specify the character set for client, connection, and results. Only available for MySQL 4.1 and later with the mysql-ruby driver. Do SHOW CHARACTER SET in mysql client to see available encodings. #2975 *Shugo Maeda*
+
+* Add tasks to create, drop and rebuild the MySQL and PostgreSQL test databases. *Marcel Molina Jr.*
+
+* Correct boolean handling in generated reader methods. #2945 *Don Park, Stefan Kaes*
+
+* Don't generate read methods for columns whose names are not valid ruby method names. #2946 *Stefan Kaes*
+
+* Document :force option to create_table. #2921 *Blair Zajac <blair@orcaware.com>*
+
+* Don't add the same conditions twice in has_one finder sql. #2916 *Jeremy Evans*
+
+* Rename Version constant to VERSION. #2802 *Marcel Molina Jr.*
+
+* Introducing the Firebird adapter. Quote columns and use attribute_condition more consistently. Setup guide: http://wiki.rubyonrails.com/rails/pages/Firebird+Adapter #1874 *Ken Kunz <kennethkunz@gmail.com>*
+
+* SQLServer: active? and reconnect! methods for handling stale connections. #428 *kajism@yahoo.com, Tom Ward <tom@popdog.net>*
+
+* Associations handle case-equality more consistently: item.parts.is_a?(Array) and item.parts === Array. #1345 *MarkusQ@reality.com*
+
+* SQLServer: insert uses given primary key value if not nil rather than SELECT @@IDENTITY. #2866 *kajism@yahoo.com, Tom Ward <tom@popdog.net>*
+
+* Oracle: active? and reconnect! methods for handling stale connections. Optionally retry queries after reconnect. #428 *Michael Schoen <schoenm@earthlink.net>*
+
+* Correct documentation for Base.delete_all. #1568 *Newhydra*
+
+* Oracle: test case for column default parsing. #2788 *Michael Schoen <schoenm@earthlink.net>*
+
+* Update documentation for Migrations. #2861 *Tom Werner <tom@cube6media.com>*
+
+* When AbstractAdapter#log rescues an exception, attempt to detect and reconnect to an inactive database connection. Connection adapter must respond to the active? and reconnect! instance methods. Initial support for PostgreSQL, MySQL, and SQLite. Make certain that all statements which may need reconnection are performed within a logged block: for example, this means no avoiding log(sql, name) { } if @logger.nil? #428 *Jeremy Kemper*
+
+* Oracle: Much faster column reflection. #2848 *Michael Schoen <schoenm@earthlink.net>*
+
+* Base.reset_sequence_name analogous to reset_table_name (mostly useful for testing). Base.define_attr_method allows nil values. *Jeremy Kemper*
+
+* PostgreSQL: smarter sequence name defaults, stricter last_insert_id, warn on pk without sequence. *Jeremy Kemper*
+
+* PostgreSQL: correctly discover custom primary key sequences. #2594 *Blair Zajac <blair@orcaware.com>, meadow.nnick@gmail.com, Jeremy Kemper*
+
+* SQLServer: don't report limits for unsupported field types. #2835 *Ryan Tomayko*
+
+* Include the Enumerable module in ActiveRecord::Errors. *Rick Bradley <rick@rickbradley.com>*
+
+* Add :group option, correspond to GROUP BY, to the find method and to the has_many association. #2818 *rubyonrails@atyp.de*
+
+* Don't cast nil or empty strings to a dummy date. #2789 *Rick Bradley <rick@rickbradley.com>*
+
+* acts_as_list plays nicely with inheritance by remembering the class which declared it. #2811 *rephorm@rephorm.com*
+
+* Fix sqlite adaptor's detection of missing dbfile or database declaration. *Nicholas Seckar*
+
+* Fixed acts_as_list for definitions without an explicit :order #2803 *Jonathan Viney*
+
+* Upgrade bundled ruby-mysql 0.2.4 with mysql411 shim (see #440) to ruby-mysql 0.2.6 with a patchset for 4.1 protocol support. Local change [301] is now a part of the main driver; reapplied local change [2182]. Removed GC.start from Result.free. *tommy@tmtm.org, akuroda@gmail.com, Doug Fales <doug.fales@gmail.com>, Jeremy Kemper*
+
+* Correct handling of complex order clauses with SQL Server limit emulation. #2770 *Tom Ward <tom@popdog.net>, Matt B.*
+
+* Correct whitespace problem in Oracle default column value parsing. #2788 *rick@rickbradley.com*
+
+* Destroy associated has_and_belongs_to_many records after all before_destroy callbacks but before destroy. This allows you to act on the habtm association as you please while preserving referential integrity. #2065 *larrywilliams1@gmail.com, sam.kirchmeier@gmail.com, elliot@townx.org, Jeremy Kemper*
+
+* Deprecate the old, confusing :exclusively_dependent option in favor of :dependent => :delete_all. *Jeremy Kemper*
+
+* More compatible Oracle column reflection. #2771 *Ryan Davis <ryand-ruby@zenspider.com>, Michael Schoen <schoenm@earthlink.net>*
+
+
+## 1.13.0 (November 7th, 2005) ##
+
+* Fixed faulty regex in get_table_name method (SQLServerAdapter) #2639 *Ryan Tomayko*
+
+* Added :include as an option for association declarations [David Heinemeier Hansson]. Example:
+
+ has_many :posts, :include => [ :author, :comments ]
+
+* Rename Base.constrain to Base.with_scope so it doesn't conflict with existing concept of database constraints. Make scoping more robust: uniform method => parameters, validated method names and supported finder parameters, raise exception on nested scopes. [Jeremy Kemper] Example:
+
+ Comment.with_scope(:find => { :conditions => 'active=true' }, :create => { :post_id => 5 }) do
+ # Find where name = ? and active=true
+ Comment.find :all, :conditions => ['name = ?', name]
+ # Create comment associated with :post_id
+ Comment.create :body => "Hello world"
+ end
+
+* Fixed that SQL Server should ignore :size declarations on anything but integer and string in the agnostic schema representation #2756 *Ryan Tomayko*
+
+* Added constrain scoping for creates using a hash of attributes bound to the :creation key [David Heinemeier Hansson]. Example:
+
+ Comment.constrain(:creation => { :post_id => 5 }) do
+ # Associated with :post_id
+ Comment.create :body => "Hello world"
+ end
+
+ This is rarely used directly, but allows for find_or_create on associations. So you can do:
+
+ # If the tag doesn't exist, a new one is created that's associated with the person
+ person.tags.find_or_create_by_name("Summer")
+
+* Added find_or_create_by_X as a second type of dynamic finder that'll create the record if it doesn't already exist [David Heinemeier Hansson]. Example:
+
+ # No 'Summer' tag exists
+ Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
+
+ # Now the 'Summer' tag does exist
+ Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
+
+* Added extension capabilities to has_many and has_and_belongs_to_many proxies [David Heinemeier Hansson]. Example:
+
+ class Account < ActiveRecord::Base
+ has_many :people do
+ def find_or_create_by_name(name)
+ first_name, *last_name = name.split
+ last_name = last_name.join " "
+
+ find_or_create_by_first_name_and_last_name(first_name, last_name)
+ end
+ end
+ end
+
+ person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
+ person.first_name # => "David"
+ person.last_name # => "Heinemeier Hansson"
+
+ Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation).
+
+* Omit internal dtproperties table from SQLServer table list. #2729 *Ryan Tomayko*
+
+* Quote column names in generated SQL. #2728 *Ryan Tomayko*
+
+* Correct the pure-Ruby MySQL 4.1.1 shim's version test. #2718 *Jeremy Kemper*
+
+* Add Model.create! to match existing model.save! method. When save! raises RecordInvalid, you can catch the exception, retrieve the invalid record (invalid_exception.record), and see its errors (invalid_exception.record.errors). *Jeremy Kemper*
+
+* Correct fixture behavior when table name pluralization is off. #2719 *Rick Bradley <rick@rickbradley.com>*
+
+* Changed :dbfile to :database for SQLite adapter for consistency (old key still works as an alias) #2644 *Dan Peterson*
+
+* Added migration support for Oracle #2647 *Michael Schoen*
+
+* Worked around that connection can't be reset if allow_concurrency is off. #2648 *Michael Schoen <schoenm@earthlink.net>*
+
+* Fixed SQL Server adapter to pass even more tests and do even better #2634 *Ryan Tomayko*
+
+* Fixed SQL Server adapter so it honors options[:conditions] when applying :limits #1978 *Tom Ward*
+
+* Added migration support to SQL Server adapter (please someone do the same for Oracle and DB2) #2625 *Tom Ward*
+
+* Use AR::Base.silence rather than AR::Base.logger.silence in fixtures to preserve Log4r compatibility. #2618 *dansketcher@gmail.com*
+
+* Constraints are cloned so they can't be inadvertently modified while they're
+ in effect. Added :readonly finder constraint. Calling an association collection's class method (Part.foobar via item.parts.foobar) constrains :readonly => false since the collection's :joins constraint would otherwise force it to true. [Jeremy Kemper <rails@bitsweat.net>]
+* Added :offset and :limit to the kinds of options that Base.constrain can use #2466 *duane.johnson@gmail.com*
+
+* Fixed handling of nil number columns on Oracle and cleaned up tests for Oracle in general #2555 *Michael Schoen*
+
+* Added quoted_true and quoted_false methods and tables to db2_adapter and cleaned up tests for DB2 #2493, #2624 *maik schmidt*
+
+
+## 1.12.2 (October 26th, 2005) ##
+
+* Allow symbols to rename columns when using SQLite adapter. #2531 *Kevin Clark*
+
+* Map Active Record time to SQL TIME. #2575, #2576 *Robby Russell <robby@planetargon.com>*
+
+* Clarify semantics of ActiveRecord::Base#respond_to? #2560 *Stefan Kaes*
+
+* Fixed Association#clear for associations which have not yet been accessed. #2524 *Patrick Lenz <patrick@lenz.sh>*
+
+* HABTM finders shouldn't return readonly records. #2525 *Patrick Lenz <patrick@lenz.sh>*
+
+* Make all tests runnable on their own. #2521. *Blair Zajac <blair@orcaware.com>*
+
+
+## 1.12.1 (October 19th, 2005) ##
+
+* Always parenthesize :conditions options so they may be safely combined with STI and constraints.
+
+* Correct PostgreSQL primary key sequence detection. #2507 *tmornini@infomania.com*
+
+* Added support for using limits in eager loads that involve has_many and has_and_belongs_to_many associations
+
+
+## 1.12.0 (October 16th, 2005) ##
+
+* Update/clean up documentation (rdoc)
+
+* PostgreSQL sequence support. Use set_sequence_name in your model class to specify its primary key sequence. #2292 *Rick Olson <technoweenie@gmail.com>, Robby Russell <robby@planetargon.com>*
+
+* Change default logging colors to work on both white and black backgrounds. *Sam Stephenson*
+
+* YAML fixtures support ordered hashes for fixtures with foreign key dependencies in the same table. #1896 *purestorm@ggnore.net*
+
+* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 *Robby Russell <robby@planetargon.com>*
+
+* Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around.
+
+* Avoid memleak in dev mode when using fcgi
+
+* Simplified .clear on active record associations by using the existing delete_records method. #1906 *Caleb <me@cpb.ca>*
+
+* Delegate access to a customized primary key to the conventional id method. #2444. *Blair Zajac <blair@orcaware.com>*
+
+* Fix errors caused by assigning a has-one or belongs-to property to itself
+
+* Add ActiveRecord::Base.schema_format setting which specifies how databases should be dumped *Sam Stephenson*
+
+* Update DB2 adapter. #2206. *contact@maik-schmidt.de*
+
+* Corrections to SQLServer native data types. #2267. *rails.20.clarry@spamgourmet.com*
+
+* Deprecated ActiveRecord::Base.threaded_connection in favor of ActiveRecord::Base.allow_concurrency.
+
+* Protect id attribute from mass assigment even when the primary key is set to something else. #2438. *Blair Zajac <blair@orcaware.com>*
+
+* Misc doc fixes (typos/grammar/etc.). #2430. *coffee2code*
+
+* Add test coverage for content_columns. #2432. *coffee2code*
+
+* Speed up for unthreaded environments. #2431. *Stefan Kaes*
+
+* Optimization for Mysql selects using mysql-ruby extension greater than 2.6.3. #2426. *Stefan Kaes*
+
+* Speed up the setting of table_name. #2428. *Stefan Kaes*
+
+* Optimize instantiation of STI subclass records. In partial fullfilment of #1236. *Stefan Kaes*
+
+* Fix typo of 'constrains' to 'contraints'. #2069. *Michael Schuerig <michael@schuerig.de>*
+
+* Optimization refactoring for add_limit_offset!. In partial fullfilment of #1236. *Stefan Kaes*
+
+* Add ability to get all siblings, including the current child, with acts_as_tree. Recloses #2140. *Michael Schuerig <michael@schuerig.de>*
+
+* Add geometric type for postgresql adapter. #2233 *Andrew Kaspick*
+
+* Add option (true by default) to generate reader methods for each attribute of a record to avoid the overhead of calling method missing. In partial fullfilment of #1236. *Stefan Kaes*
+
+* Add convenience predicate methods on Column class. In partial fullfilment of #1236. *Stefan Kaes*
+
+* Raise errors when invalid hash keys are passed to ActiveRecord::Base.find. #2363 *Chad Fowler <chad@chadfowler.com>, Nicholas Seckar*
+
+* Added :force option to create_table that'll try to drop the table if it already exists before creating
+
+* Fix transactions so that calling return while inside a transaction will not leave an open transaction on the connection. *Nicholas Seckar*
+
+* Use foreign_key inflection uniformly. #2156 *Blair Zajac <blair@orcaware.com>*
+
+* model.association.clear should destroy associated objects if :dependent => true instead of nullifying their foreign keys. #2221 *joergd@pobox.com, ObieFernandez <obiefernandez@gmail.com>*
+
+* Returning false from before_destroy should cancel the action. #1829 *Jeremy Huffman*
+
+* Recognize PostgreSQL NOW() default as equivalent to CURRENT_TIMESTAMP or CURRENT_DATE, depending on the column's type. #2256 *mat <mat@absolight.fr>*
+
+* Extensive documentation for the abstract database adapter. #2250 *François Beausoleil <fbeausoleil@ftml.net>*
+
+* Clean up Fixtures.reset_sequences for PostgreSQL. Handle tables with no rows and models with custom primary keys. #2174, #2183 *jay@jay.fm, Blair Zajac <blair@orcaware.com>*
+
+* Improve error message when nil is assigned to an attr which validates_size_of within a range. #2022 *Manuel Holtgrewe <purestorm@ggnore.net>*
+
+* Make update_attribute use the same writer method that update_attributes uses.
+ \#2237 [trevor@protocool.com]
+* Make migrations honor table name prefixes and suffixes. #2298 *Jakob Skjerning, Marcel Molina Jr.*
+
+* Correct and optimize PostgreSQL bytea escaping. #1745, #1837 *dave@cherryville.org, ken@miriamtech.com, bellis@deepthought.org*
+
+* Fixtures should only reset a PostgreSQL sequence if it corresponds to an integer primary key named id. #1749 *chris@chrisbrinker.com*
+
+* Standardize the interpretation of boolean columns in the Mysql and Sqlite adapters. (Use MysqlAdapter.emulate_booleans = false to disable this behavior)
+
+* Added new symbol-driven approach to activating observers with Base#observers= [David Heinemeier Hansson]. Example:
+
+ ActiveRecord::Base.observers = :cacher, :garbage_collector
+
+* Added AbstractAdapter#select_value and AbstractAdapter#select_values as convenience methods for selecting single values, instead of hashes, of the first column in a SELECT #2283 *solo@gatelys.com*
+
+* Wrap :conditions in parentheses to prevent problems with OR's #1871 *Jamis Buck*
+
+* Allow the postgresql adapter to work with the SchemaDumper. *Jamis Buck*
+
+* Add ActiveRecord::SchemaDumper for dumping a DB schema to a pure-ruby file, making it easier to consolidate large migration lists and port database schemas between databases. *Jamis Buck*
+
+* Fixed migrations for Windows when using more than 10 *David Naseby*
+
+* Fixed that the create_x method from belongs_to wouldn't save the association properly #2042 *Florian Weber*
+
+* Fixed saving a record with two unsaved belongs_to associations pointing to the same object #2023 *Tobias Lütke*
+
+* Improved migrations' behavior when the schema_info table is empty. *Nicholas Seckar*
+
+* Fixed that Observers didn't observe sub-classes #627 *Florian Weber*
+
+* Fix eager loading error messages, allow :include to specify tables using strings or symbols. Closes #2222 *Marcel Molina Jr.*
+
+* Added check for RAILS_CONNECTION_ADAPTERS on startup and only load the connection adapters specified within if its present (available in Rails through config.connection_adapters using the new config) #1958 *skae*
+
+* Fixed various problems with has_and_belongs_to_many when using customer finder_sql #2094 *Florian Weber*
+
+* Added better exception error when unknown column types are used with migrations #1814 *François Beausoleil*
+
+* Fixed "connection lost" issue with the bundled Ruby/MySQL driver (would kill the app after 8 hours of inactivity) #2163, #428 *kajism@yahoo.com*
+
+* Fixed comparison of Active Record objects so two new objects are not equal #2099 *deberg*
+
+* Fixed that the SQL Server adapter would sometimes return DBI::Timestamp objects instead of Time #2127 *Tom Ward*
+
+* Added the instance methods #root and #ancestors on acts_as_tree and fixed siblings to not include the current node #2142, #2140 *coffee2code*
+
+* Fixed that Active Record would call SHOW FIELDS twice (or more) for the same model when the cached results were available #1947 *sd@notso.net*
+
+* Added log_level and use_silence parameter to ActiveRecord::Base.benchmark. The first controls at what level the benchmark statement will be logged (now as debug, instead of info) and the second that can be passed false to include all logging statements during the benchmark block/
+
+* Make sure the schema_info table is created before querying the current version #1903
+
+* Fixtures ignore table name prefix and suffix #1987 *Jakob Skjerning*
+
+* Add documentation for index_type argument to add_index method for migrations #2005 *Blaine*
+
+* Modify read_attribute to allow a symbol argument #2024 *Ken Kunz*
+
+* Make destroy return self #1913 *Sebastian Kanthak*
+
+* Fix typo in validations documentation #1938 *court3nay*
+
+* Make acts_as_list work for insert_at(1) #1966 *hensleyl@papermountain.org*
+
+* Fix typo in count_by_sql documentation #1969 *Alexey Verkhovsky*
+
+* Allow add_column and create_table to specify NOT NULL #1712 *emptysands@gmail.com*
+
+* Fix create_table so that id column is implicitly added *Rick Olson*
+
+* Default sequence names for Oracle changed to #{table_name}_seq, which is the most commonly used standard. In addition, a new method ActiveRecord::Base#set_sequence_name allows the developer to set the sequence name per model. This is a non-backwards-compatible change -- anyone using the old-style "rails_sequence" will need to either create new sequences, or set: ActiveRecord::Base.set_sequence_name = "rails_sequence" #1798
+
+* OCIAdapter now properly handles synonyms, which are commonly used to separate out the schema owner from the application user #1798
+
+* Fixed the handling of camelCase columns names in Oracle #1798
+
+* Implemented for OCI the Rakefile tasks of :clone_structure_to_test, :db_structure_dump, and :purge_test_database, which enable Oracle folks to enjoy all the agile goodness of Rails for testing. Note that the current implementation is fairly limited -- only tables and sequences are cloned, not constraints or indexes. A full clone in Oracle generally requires some manual effort, and is version-specific. Post 9i, Oracle recommends the use of the DBMS_METADATA package, though that approach requires editing of the physical characteristics generated #1798
+
+* Fixed the handling of multiple blob columns in Oracle if one or more of them are null #1798
+
+* Added support for calling constrained class methods on has_many and has_and_belongs_to_many collections #1764 *Tobias Lütke*
+
+ class Comment < AR:B
+ def self.search(q)
+ find(:all, :conditions => ["body = ?", q])
+ end
+ end
+
+ class Post < AR:B
+ has_many :comments
+ end
+
+ Post.find(1).comments.search('hi') # => SELECT * from comments WHERE post_id = 1 AND body = 'hi'
+
+ NOTICE: This patch changes the underlying SQL generated by has_and_belongs_to_many queries. If your relying on that, such as
+ by explicitly referencing the old t and j aliases, you'll need to update your code. Of course, you _shouldn't_ be relying on
+ details like that no less than you should be diving in to touch private variables. But just in case you do, consider yourself
+ noticed :)
+
+* Added migration support for SQLite (using temporary tables to simulate ALTER TABLE) #1771 *Sam Stephenson*
+
+* Remove extra definition of supports_migrations? from abstract_adaptor.rb *Nicholas Seckar*
+
+* Fix acts_as_list so that moving next-to-last item to the bottom does not result in duplicate item positions
+
+* Fixed incompatibility in DB2 adapter with the new limit/offset approach #1718 *Maik Schmidt*
+
+* Added :select option to find which can specify a different value than the default *, like find(:all, :select => "first_name, last_name"), if you either only want to select part of the columns or exclude columns otherwise included from a join #1338 *Stefan Kaes*
+
+
+## 1.11.1 (11 July, 2005) ##
+
+* Added support for limit and offset with eager loading of has_one and belongs_to associations. Using the options with has_many and has_and_belongs_to_many associations will now raise an ActiveRecord::ConfigurationError #1692 *Rick Olson*
+
+* Fixed that assume_bottom_position (in acts_as_list) could be called on items already last in the list and they would move one position away from the list #1648 *tyler@kianta.com*
+
+* Added ActiveRecord::Base.threaded_connections flag to turn off 1-connection per thread (required for thread safety). By default it's on, but WEBrick in Rails need it off #1685 *Sam Stephenson*
+
+* Correct reflected table name for singular associations. #1688 *court3nay*
+
+* Fixed optimistic locking with SQL Server #1660 *tom@popdog.net*
+
+* Added ActiveRecord::Migrator.migrate that can figure out whether to go up or down based on the target version and the current
+
+* Added better error message for "packets out of order" #1630 *court3nay*
+
+* Fixed first run of "rake migrate" on PostgreSQL by not expecting a return value on the id #1640
+
+
+## 1.11.0 (6 July, 2005) ##
+
+* Fixed that Yaml error message in fixtures hid the real error #1623 *Nicholas Seckar*
+
+* Changed logging of SQL statements to use the DEBUG level instead of INFO
+
+* Added new Migrations framework for describing schema transformations in a way that can be easily applied across multiple databases #1604 [Tobias Lütke] See documentation under ActiveRecord::Migration and the additional support in the Rails rakefile/generator.
+
+* Added callback hooks to association collections #1549 [Florian Weber]. Example:
+
+ class Project
+ has_and_belongs_to_many :developers, :before_add => :evaluate_velocity
+
+ def evaluate_velocity(developer)
+ ...
+ end
+ end
+
+ ..raising an exception will cause the object not to be added (or removed, with before_remove).
+
+
+* Fixed Base.content_columns call for SQL Server adapter #1450 *DeLynn Berry*
+
+* Fixed Base#write_attribute to work with both symbols and strings #1190 *Paul Legato*
+
+* Fixed that has_and_belongs_to_many didn't respect single table inheritance types #1081 *Florian Weber*
+
+* Speed up ActiveRecord#method_missing for the common case (read_attribute).
+
+* Only notify observers on after_find and after_initialize if these methods are defined on the model. #1235 *Stefan Kaes*
+
+* Fixed that single-table inheritance sub-classes couldn't be used to limit the result set with eager loading #1215 *Chris McGrath*
+
+* Fixed validates_numericality_of to work with overrided getter-method when :allow_nil is on #1316 *raidel@onemail.at*
+
+* Added roots, root, and siblings to the batch of methods added by acts_as_tree #1541 *Michael Schuerig*
+
+* Added support for limit/offset with the MS SQL Server driver so that pagination will now work #1569 *DeLynn Berry*
+
+* Added support for ODBC connections to MS SQL Server so you can connect from a non-Windows machine #1569 *Mark Imbriaco/DeLynn Berry*
+
+* Fixed that multiparameter posts ignored attr_protected #1532 *alec+rails@veryclever.net*
+
+* Fixed problem with eager loading when using a has_and_belongs_to_many association using :association_foreign_key #1504 *flash@vanklinkenbergsoftware.nl*
+
+* Fixed Base#find to honor the documentation on how :joins work and make them consistent with Base#count #1405 [pritchie@gmail.com]. What used to be:
+
+ Developer.find :all, :joins => 'developers_projects', :conditions => 'id=developer_id AND project_id=1'
+
+ ...should instead be:
+
+ Developer.find(
+ :all,
+ :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
+ :conditions => 'project_id=1'
+ )
+
+* Fixed that validations didn't respecting custom setting for too_short, too_long messages #1437 *Marcel Molina Jr.*
+
+* Fixed that clear_association_cache doesn't delete new associations on new records (so you can safely place new records in the session with Action Pack without having new associations wiped) #1494 *cluon*
+
+* Fixed that calling Model.find([]) returns [] and doesn't throw an exception #1379
+
+* Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 *Alisdair McDiarmid*
+
+* Fixed saving of in-memory association structures to happen as an after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice
+
+* Allow any Enumerable, not just Array, to work as bind variables #1344 *Jeremy Kemper*
+
+* Added actual database-changing behavior to collection assigment for has_many and has_and_belongs_to_many #1425 [Sebastian Kanthak].
+ Example:
+
+ david.projects = [Project.find(1), Project.new("name" => "ActionWebSearch")]
+ david.save
+
+ If david.projects already contain the project with ID 1, this is left unchanged. Any other projects are dropped. And the new
+ project is saved when david.save is called.
+
+ Also included is a way to do assignments through IDs, which is perfect for checkbox updating, so you get to do:
+
+ david.project_ids = [1, 5, 7]
+
+* Corrected typo in find SQL for has_and_belongs_to_many. #1312 *ben@bensinclair.com*
+
+* Fixed sanitized conditions for has_many finder method. #1281 *jackc@hylesanderson.com, pragdave, Tobias Lütke*
+
+* Comprehensive PostgreSQL schema support. Use the optional schema_search_path directive in database.yml to give a comma-separated list of schemas to search for your tables. This allows you, for example, to have tables in a shared schema without having to use a custom table name. See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html to learn more. #827 *dave@cherryville.org*
+
+* Corrected @@configurations typo #1410 *david@ruppconsulting.com*
+
+* Return PostgreSQL columns in the order they were declared #1374 *perlguy@gmail.com*
+
+* Allow before/after update hooks to work on models using optimistic locking
+
+* Eager loading of dependent has_one associations won't delete the association #1212
+
+* Added a second parameter to the build and create method for has_one that controls whether the existing association should be replaced (which means nullifying its foreign key as well). By default this is true, but false can be passed to prevent it.
+
+* Using transactional fixtures now causes the data to be loaded only once.
+
+* Added fixture accessor methods that can be used when instantiated fixtures are disabled.
+
+ fixtures :web_sites
+
+ def test_something
+ assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
+ end
+
+* Added DoubleRenderError exception that'll be raised if render* is called twice #518 *Nicholas Seckar*
+
+* Fixed exceptions occuring after render has been called #1096 *Nicholas Seckar*
+
+* CHANGED: validates_presence_of now uses Errors#add_on_blank, which will make " " fail the validation where it didn't before #1309
+
+* Added Errors#add_on_blank which works like Errors#add_on_empty, but uses Object#blank? instead
+
+* Added the :if option to all validations that can either use a block or a method pointer to determine whether the validation should be run or not. #1324 [Duane Johnson/jhosteny]. Examples:
+
+ Conditional validations such as the following are made possible:
+ validates_numericality_of :income, :if => :employed?
+
+ Conditional validations can also solve the salted login generator problem:
+ validates_confirmation_of :password, :if => :new_password?
+
+ Using blocks:
+ validates_presence_of :username, :if => Proc.new { |user| user.signup_step > 1 }
+
+* Fixed use of construct_finder_sql when using :join #1288 *dwlt@dwlt.net*
+
+* Fixed that :delete_sql in has_and_belongs_to_many associations couldn't access record properties #1299 *Rick Olson*
+
+* Fixed that clone would break when an aggregate had the same name as one of its attributes #1307 *Jeremy Kemper*
+
+* Changed that destroying an object will only freeze the attributes hash, which keeps the object from having attributes changed (as that wouldn't make sense), but allows for the querying of associations after it has been destroyed.
+
+* Changed the callbacks such that observers are notified before the in-object callbacks are triggered. Without this change, it wasn't possible to act on the whole object in something like a before_destroy observer without having the objects own callbacks (like deleting associations) called first.
+
+* Added option for passing an array to the find_all version of the dynamic finders and have it evaluated as an IN fragment. Example:
+
+ # SELECT * FROM topics WHERE title IN ('First', 'Second')
+ Topic.find_all_by_title(["First", "Second"])
+
+* Added compatibility with camelCase column names for dynamic finders #533 *Dee Zsombor*
+
+* Fixed extraneous comma in count() function that made it not work with joins #1156 *Jarkko Laine/Dee Zsombor*
+
+* Fixed incompatibility with Base#find with an array of ids that would fail when using eager loading #1186 *Alisdair McDiarmid*
+
+* Fixed that validate_length_of lost :on option when :within was specified #1195 *jhosteny@mac.com*
+
+* Added encoding and min_messages options for PostgreSQL #1205 [Shugo Maeda]. Configuration example:
+
+ development:
+ adapter: postgresql
+ database: rails_development
+ host: localhost
+ username: postgres
+ password:
+ encoding: UTF8
+ min_messages: ERROR
+
+* Fixed acts_as_list where deleting an item that was removed from the list would ruin the positioning of other list items #1197 *Jamis Buck*
+
+* Added validates_exclusion_of as a negative of validates_inclusion_of
+
+* Optimized counting of has_many associations by setting the association to empty if the count is 0 so repeated calls doesn't trigger database calls
+
+
+## 1.10.1 (20th April, 2005) ##
+
+* Fixed frivilous database queries being triggered with eager loading on empty associations and other things
+
+* Fixed order of loading in eager associations
+
+* Fixed stray comma when using eager loading and ordering together from has_many associations #1143
+
+
+## 1.10.0 (19th April, 2005) ##
+
+* Added eager loading of associations as a way to solve the N+1 problem more gracefully without piggy-back queries. Example:
+
+ for post in Post.find(:all, :limit => 100)
+ puts "Post: " + post.title
+ puts "Written by: " + post.author.name
+ puts "Last comment on: " + post.comments.first.created_on
+ end
+
+ This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as:
+
+ for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
+
+ ...and the number of database queries needed is now 1.
+
+* Added new unified Base.find API and deprecated the use of find_first and find_all. See the documentation for Base.find. Examples:
+
+ Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
+ Person.find(1, 5, 6, :conditions => "administrator = 1", :order => "created_on DESC")
+ Person.find(:first, :order => "created_on DESC", :offset => 5)
+ Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
+ Person.find(:all, :offset => 10, :limit => 10)
+
+* Added acts_as_nested_set #1000 [wschenk]. Introduction:
+
+ This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with
+ the added feature that you can select the children and all of it's descendants with
+ a single query. A good use case for this is a threaded post system, where you want
+ to display every reply to a comment without multiple selects.
+
+* Added Base.save! that attempts to save the record just like Base.save but will raise a RecordInvalid exception instead of returning false if the record is not valid *Dave Thomas*
+
+* Fixed PostgreSQL usage of fixtures with regards to public schemas and table names with dots #962 *gnuman1@gmail.com*
+
+* Fixed that fixtures were being deleted in the same order as inserts causing FK errors #890 *andrew.john.peters@gmail.com*
+
+* Fixed loading of fixtures in to be in the right order (or PostgreSQL would bark) #1047 *stephenh@chase3000.com*
+
+* Fixed page caching for non-vhost applications living underneath the root #1004 *Ben Schumacher*
+
+* Fixes a problem with the SQL Adapter which was resulting in IDENTITY_INSERT not being set to ON when it should be #1104 *adelle*
+
+* Added the option to specify the acceptance string in validates_acceptance_of #1106 *caleb@aei-tech.com*
+
+* Added insert_at(position) to acts_as_list #1083 *DeLynnB*
+
+* Removed the default order by id on has_and_belongs_to_many queries as it could kill performance on large sets (you can still specify by hand with :order)
+
+* Fixed that Base.silence should restore the old logger level when done, not just set it to DEBUG #1084 *yon@milliped.com*
+
+* Fixed boolean saving on Oracle #1093 *mparrish@pearware.org*
+
+* Moved build_association and create_association for has_one and belongs_to out of deprecation as they work when the association is nil unlike association.build and association.create, which require the association to be already in place #864
+
+* Added rollbacks of transactions if they're active as the dispatcher is killed gracefully (TERM signal) #1054 *Leon Bredt*
+
+* Added quoting of column names for fixtures #997 *jcfischer@gmail.com*
+
+* Fixed counter_sql when no records exist in database for PostgreSQL (would give error, not 0) #1039 *Caleb Tennis*
+
+* Fixed that benchmarking times for rendering included db runtimes #987 *Stefan Kaes*
+
+* Fixed boolean queries for t/f fields in PostgreSQL #995 *dave@cherryville.org*
+
+* Added that model.items.delete(child) will delete the child, not just set the foreign key to nil, if the child is dependent on the model #978 *Jeremy Kemper*
+
+* Fixed auto-stamping of dates (created_on/updated_on) for PostgreSQL #985 *dave@cherryville.org*
+
+* Fixed Base.silence/benchmark to only log if a logger has been configured #986 *Stefan Kaes*
+
+* Added a join parameter as the third argument to Base.find_first and as the second to Base.count #426, #988 *Stefan Kaes*
+
+* Fixed bug in Base#hash method that would treat records with the same string-based id as different *Dave Thomas*
+
+* Renamed DateHelper#distance_of_time_in_words_to_now to DateHelper#time_ago_in_words (old method name is still available as a deprecated alias)
+
+
+## 1.9.1 (27th March, 2005) ##
+
+* Fixed that Active Record objects with float attribute could not be cloned #808
+
+* Fixed that MissingSourceFile's wasn't properly detected in production mode #925 *Nicholas Seckar*
+
+* Fixed that :counter_cache option would look for a line_items_count column for a LineItem object instead of lineitems_count
+
+* Fixed that AR exists?() would explode on postgresql if the passed id did not match the PK type #900 *Scott Barron*
+
+* Fixed the MS SQL adapter to work with the new limit/offset approach and with binary data (still suffering from 7KB limit, though) #901 *delynnb*
+
+
+## 1.9.0 (22th March, 2005) ##
+
+* Added adapter independent limit clause as a two-element array with the first being the limit, the second being the offset #795 [Sam Stephenson]. Example:
+
+ Developer.find_all nil, 'id ASC', 5 # return the first five developers
+ Developer.find_all nil, 'id ASC', [3, 8] # return three developers, starting from #8 and forward
+
+ This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
+
+* Added alias_method :to_param, :id to Base, such that Active Record objects to be used as URL parameters in Action Pack automatically #812 *Nicholas Seckar/Sam Stephenson*
+
+* Improved the performance of the OCI8 adapter for Oracle #723 *pilx/gjenkins*
+
+* Added type conversion before saving a record, so string-based values like "10.0" aren't left for the database to convert #820 *dave@cherryville.org*
+
+* Added with additional settings for working with transactional fixtures and pre-loaded test databases #865 *mindel*
+
+* Fixed acts_as_list to trigger remove_from_list on destroy after the fact, not before, so a unique position can be maintained #871 *Alisdair McDiarmid*
+
+* Added the possibility of specifying fixtures in multiple calls #816 *kim@tinker.com*
+
+* Added Base.exists?(id) that'll return true if an object of the class with the given id exists #854 *stian@grytoyr.net*
+
+* Added optionally allow for nil or empty strings with validates_numericality_of #801 *Sebastian Kanthak*
+
+* Fixed problem with using slashes in validates_format_of regular expressions #801 *Sebastian Kanthak*
+
+* Fixed that SQLite3 exceptions are caught and reported properly #823 *yerejm*
+
+* Added that all types of after_find/after_initialized callbacks are triggered if the explicit implementation is present, not only the explicit implementation itself
+
+* Fixed that symbols can be used on attribute assignment, like page.emails.create(:subject => data.subject, :body => data.body)
+
+
+## 1.8.0 (7th March, 2005) ##
+
+* Added ActiveRecord::Base.colorize_logging to control whether to use colors in logs or not (on by default)
+
+* Added support for timestamp with time zone in PostgreSQL #560 *Scott Barron*
+
+* Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius]. Documentation:
+
+ * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
+ +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
+ objects that should be inspected to determine which attributes triggered the errors.
+ * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
+ You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
+
+* Fixed that postgresql adapter would fails when reading bytea fields with null value #771 *rodrigo k*
+
+* Added transactional fixtures that uses rollback to undo changes to fixtures instead of DELETE/INSERT -- it's much faster. See documentation under Fixtures #760 *Jeremy Kemper*
+
+* Added destruction of dependent objects in has_one associations when a new assignment happens #742 [mindel]. Example:
+
+ class Account < ActiveRecord::Base
+ has_one :credit_card, :dependent => true
+ end
+ class CreditCard < ActiveRecord::Base
+ belongs_to :account
+ end
+
+ account.credit_card # => returns existing credit card, lets say id = 12
+ account.credit_card = CreditCard.create("number" => "123")
+ account.save # => CC with id = 12 is destroyed
+
+
+* Added validates_numericality_of #716 [Sebastian Kanthak/Chris McGrath]. Docuemntation:
+
+ Validates whether the value of the specified attribute is numeric by trying to convert it to
+ a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
+ <tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true).
+
+ class Person < ActiveRecord::Base
+ validates_numericality_of :value, :on => :create
+ end
+
+ Configuration options:
+ * <tt>message</tt> - A custom error message (default is: "is not a number")
+ * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
+ * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
+
+
+* Fixed that HasManyAssociation#count was using :finder_sql rather than :counter_sql if it was available #445 *Scott Barron*
+
+* Added better defaults for composed_of, so statements like composed_of :time_zone, :mapping => %w( time_zone time_zone ) can be written without the mapping part (it's now assumed)
+
+* Added MacroReflection#macro which will return a symbol describing the macro used (like :composed_of or :has_many) #718, #248 *james@slashetc.com*
+
+
+## 1.7.0 (24th February, 2005) ##
+
+* Changed the auto-timestamping feature to use ActiveRecord::Base.default_timezone instead of entertaining the parallel ActiveRecord::Base.timestamps_gmt method. The latter is now deprecated and will throw a warning on use (but still work) #710 *Jamis Buck*
+
+* Added a OCI8-based Oracle adapter that has been verified to work with Oracle 8 and 9 #629 [Graham Jenkins]. Usage notes:
+
+ 1. Key generation uses a sequence "rails_sequence" for all tables. (I couldn't find a simple
+ and safe way of passing table-specific sequence information to the adapter.)
+ 2. Oracle uses DATE or TIMESTAMP datatypes for both dates and times. Consequently I have had to
+ resort to some hacks to get data converted to Date or Time in Ruby.
+ If the column_name ends in _at (like created_at, updated_at) it's created as a Ruby Time. Else if the
+ hours/minutes/seconds are 0, I make it a Ruby Date. Else it's a Ruby Time.
+ This is nasty - but if you use Duck Typing you'll probably not care very much.
+ In 9i it's tempting to map DATE to Date and TIMESTAMP to Time but I don't think that is
+ valid - too many databases use DATE for both.
+ Timezones and sub-second precision on timestamps are not supported.
+ 3. Default values that are functions (such as "SYSDATE") are not supported. This is a
+ restriction of the way active record supports default values.
+ 4. Referential integrity constraints are not fully supported. Under at least
+ some circumstances, active record appears to delete parent and child records out of
+ sequence and out of transaction scope. (Or this may just be a problem of test setup.)
+
+ The OCI8 driver can be retrieved from http://rubyforge.org/projects/ruby-oci8/
+
+* Added option :schema_order to the PostgreSQL adapter to support the use of multiple schemas per database #697 *YuriSchimke*
+
+* Optimized the SQL used to generate has_and_belongs_to_many queries by listing the join table first #693 *yerejm*
+
+* Fixed that when using validation macros with a custom message, if you happened to use single quotes in the message string you would get a parsing error #657 *tonka*
+
+* Fixed that Active Record would throw Broken Pipe errors with FCGI when the MySQL connection timed out instead of reconnecting #428 *Nicholas Seckar*
+
+* Added options to specify an SSL connection for MySQL. Define the following attributes in the connection config (config/database.yml in Rails) to use it: sslkey, sslcert, sslca, sslcapath, sslcipher. To use SSL with no client certs, just set :sslca = '/dev/null'. http://dev.mysql.com/doc/mysql/en/secure-connections.html #604 *daniel@nightrunner.com*
+
+* Added automatic dropping/creating of test tables for running the unit tests on all databases #587 *adelle@bullet.net.au*
+
+* Fixed that find_by_* would fail when column names had numbers #670 *demetrius*
+
+* Fixed the SQL Server adapter on a bunch of issues #667 *DeLynn*
+
+ 1. Created a new columns method that is much cleaner.
+ 2. Corrected a problem with the select and select_all methods
+ that didn't account for the LIMIT clause being passed into raw SQL statements.
+ 3. Implemented the string_to_time method in order to create proper instances of the time class.
+ 4. Added logic to the simplified_type method that allows the database to specify the scale of float data.
+ 5. Adjusted the quote_column_name to account for the fact that MS SQL is bothered by a forward slash in the data string.
+
+* Fixed that the dynamic finder like find_all_by_something_boolean(false) didn't work #649 *lmarlow*
+
+* Added validates_each that validates each specified attribute against a block #610 [Jeremy Kemper]. Example:
+
+ class Person < ActiveRecord::Base
+ validates_each :first_name, :last_name do |record, attr|
+ record.errors.add attr, 'starts with z.' if attr[0] == ?z
+ end
+ end
+
+* Added :allow_nil as an explicit option for validates_length_of, so unless that's set to true having the attribute as nil will also return an error if a range is specified as :within #610 *Jeremy Kemper*
+
+* Added that validates_* now accept blocks to perform validations #618 [Tim Bates]. Example:
+
+ class Person < ActiveRecord::Base
+ validate { |person| person.errors.add("title", "will never be valid") if SHOULD_NEVER_BE_VALID }
+ end
+
+* Addded validation for validate all the associated objects before declaring failure with validates_associated #618 *Tim Bates*
+
+* Added keyword-style approach to defining the custom relational bindings #545 [Jamis Buck]. Example:
+
+ class Project < ActiveRecord::Base
+ primary_key "sysid"
+ table_name "XYZ_PROJECT"
+ inheritance_column { original_inheritance_column + "_id" }
+ end
+
+* Fixed Base#clone for use with PostgreSQL #565 *hanson@surgery.wisc.edu*
+
+
+## 1.6.0 (January 25th, 2005) ##
+
+* Added that has_many association build and create methods can take arrays of record data like Base#create and Base#build to build/create multiple records at once.
+
+* Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336
+
+* Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once.
+
+* Added the option of supplying an array of ids and attributes to Base#update, so that multiple records can be updated at once (inspired by #526/Duane Johnson). Example
+
+ people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
+ Person.update(people.keys, people.values)
+
+* Added ActiveRecord::Base.timestamps_gmt that can be set to true to make the automated timestamping use GMT instead of local time #520 *Scott Baron*
+
+* Added that update_all calls sanitize_sql on its updates argument, so stuff like MyRecord.update_all(['time = ?', Time.now]) works #519 *notahat*
+
+* Fixed that the dynamic finders didn't treat nil as a "IS NULL" but rather "= NULL" case #515 *Demetrius*
+
+* Added bind-named arrays for interpolating a group of ids or strings in conditions #528 *Jeremy Kemper*
+
+* Added that has_and_belongs_to_many associations with additional attributes also can be created between unsaved objects and only committed to the database when Base#save is called on the associator #524 *Eric Anderson*
+
+* Fixed that records fetched with piggy-back attributes or through rich has_and_belongs_to_many associations couldn't be saved due to the extra attributes not part of the table #522 *Eric Anderson*
+
+* Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not
+
+* Fixed that association proxies would fail === tests like PremiumSubscription === @account.subscription
+
+* Fixed that column aliases didn't work as expected with the new MySql411 driver #507 *Demetrius*
+
+* Fixed that find_all would produce invalid sql when called sequentialy #490 *Scott Baron*
+
+
+## 1.5.1 (January 18th, 2005) ##
+
+* Fixed that the belongs_to and has_one proxy would fail a test like 'if project.manager' -- this unfortunately also means that you can't call methods like project.manager.build unless there already is a manager on the project #492 *Tim Bates*
+
+* Fixed that the Ruby/MySQL adapter wouldn't connect if the password was empty #503 *Pelle*
+
+
+## 1.5.0 (January 17th, 2005) ##
+
+* Fixed that unit tests for MySQL are now run as the "rails" user instead of root #455 *Eric Hodel*
+
+* Added validates_associated that enables validation of objects in an unsaved association #398 [Tim Bates]. Example:
+
+ class Book < ActiveRecord::Base
+ has_many :pages
+ belongs_to :library
+
+ validates_associated :pages, :library
+ end
+
+* Added support for associating unsaved objects #402 [Tim Bates]. Rules that govern this addition:
+
+ == Unsaved objects and associations
+
+ You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
+ aware of, mostly involving the saving of associated objects.
+
+ === One-to-one associations
+
+ * Assigning an object to a has_one association automatically saves that object, and the object being replaced (if there is one), in
+ order to update their primary keys - except if the parent object is unsaved (new_record? == true).
+ * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
+ is cancelled.
+ * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
+ * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
+ not save the parent either.
+
+ === Collections
+
+ * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
+ (the owner of the collection) is not yet stored in the database.
+ * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
+ * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
+ * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
+
+* Added replace to associations, so you can do project.manager.replace(new_manager) or project.milestones.replace(new_milestones) #402 *Tim Bates*
+
+* Added build and create methods to has_one and belongs_to associations, so you can now do project.manager.build(attributes) #402 *Tim Bates*
+
+* Added that if a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last. #402 *Tim Bates*
+
+* Fixed that Base#== wouldn't work for multiple references to the same unsaved object #402 *Tim Bates*
+
+* Fixed binary support for PostgreSQL #444 *alex@byzantine.no*
+
+* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
+ collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
+ it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
+ it'll take one less SELECT query if you use length.
+
+* Added Base#attributes that returns a hash of all the attributes with their names as keys and clones of their objects as values #433 *atyp.de*
+
+* Fixed that foreign keys named the same as the association would cause stack overflow #437 *Eric Anderson*
+
+* Fixed default scope of acts_as_list from "1" to "1 = 1", so it'll work in PostgreSQL (among other places) #427 *Alexey*
+
+* Added Base#reload that reloads the attributes of an object from the database #422 *Andreas Schwarz*
+
+* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 *Jeremy Kemper*
+
+* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 *Matt Mower*
+
+* Added that Observers can use the observes class method instead of overwriting self.observed_class().
+
+ Before:
+ class ListSweeper < ActiveRecord::Base
+ def self.observed_class() [ List, Item ]
+ end
+
+ After:
+ class ListSweeper < ActiveRecord::Base
+ observes List, Item
+ end
+
+* Fixed that conditions in has_many and has_and_belongs_to_many should be interpolated just like the finder_sql is
+
+* Fixed Base#update_attribute to be indifferent to whether a string or symbol is used to describe the name
+
+* Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it easier to flip a switch or flag.
+
+ Before: topic.update_attribute(:approved, !approved?)
+ After : topic.toggle!(:approved)
+
+* Added Base#increment!(attribute) and Base#decrement!(attribute) that also saves the records. Example:
+
+ page.views # => 1
+ page.increment!(:views) # executes an UPDATE statement
+ page.views # => 2
+
+ page.increment(:views).increment!(:views)
+ page.views # => 4
+
+* Added Base#increment(attribute) and Base#decrement(attribute) that encapsulates the += 1 and -= 1 patterns.
+
+
+## 1.4.0 (January 4th, 2005) ##
+
+* Added automated optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
+ record increments the lock_version column and the locking facilities ensure that records instantiated twice
+ will let the last one saved raise a StaleObjectError if the first was also updated. Example:
+
+ p1 = Person.find(1)
+ p2 = Person.find(1)
+
+ p1.first_name = "Michael"
+ p1.save
+
+ p2.first_name = "should fail"
+ p2.save # Raises a ActiveRecord::StaleObjectError
+
+ You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
+ or otherwise apply the business logic needed to resolve the conflict.
+
+ \#384 *Michael Koziarski*
+
+* Added dynamic attribute-based finders as a cleaner way of getting objects by simple queries without turning to SQL.
+ They work by appending the name of an attribute to <tt>find_by_</tt>, so you get finders like <tt>Person.find_by_user_name,
+ Payment.find_by_transaction_id</tt>. So instead of writing <tt>Person.find_first(["user_name = ?", user_name])</tt>, you just do
+ <tt>Person.find_by_user_name(user_name)</tt>.
+
+ It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
+ <tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
+ <tt>Person.find_first(["user_name = ? AND password = ?", user_name, password])</tt>, you just do
+ <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
+
+ While primarily a construct for easier find_firsts, it can also be used as a construct for find_all by using calls like
+ <tt>Payment.find_all_by_amount(50)</tt> that is turned into <tt>Payment.find_all(["amount = ?", 50])</tt>. This is something not as equally useful,
+ though, as it's not possible to specify the order in which the objects are returned.
+
+* Added block-style for callbacks #332 [Jeremy Kemper].
+
+ Before:
+ before_destroy(Proc.new{ |record| Person.destroy_all "firm_id = #{record.id}" })
+
+ After:
+ before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
+
+* Added :counter_cache option to acts_as_tree that works just like the one you can define on belongs_to #371 *Josh Peek*
+
+* Added Base.default_timezone accessor that determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates
+ and times from the database. This is set to :local by default.
+
+* Added the possibility for adapters to overwrite add_limit! to implement a different limiting scheme than "LIMIT X" used by MySQL, PostgreSQL, and SQLite.
+
+* Added the possibility of having objects with acts_as_list created before their scope is available or...
+
+* Added a db2 adapter that only depends on the Ruby/DB2 bindings (http://raa.ruby-lang.org/project/ruby-db2/) #386 *Maik Schmidt*
+
+* Added the final touches to the Microsoft SQL Server adapter by Joey Gibson that makes it suitable for actual use #394 *DeLynn Barry*
+
+* Added that Base#find takes an optional options hash, including :conditions. Base#find_on_conditions deprecated in favor of #find with :conditions #407 *Jeremy Kemper*
+
+* Added HasManyAssociation#count that works like Base#count #413 *intinig*
+
+* Fixed handling of binary content in blobs and similar fields for Ruby/MySQL and SQLite #409 *xal*
+
+* Fixed a bug in the Ruby/MySQL that caused binary content to be escaped badly and come back mangled #405 *Tobias Lütke*
+
+* Fixed that the const_missing autoload assumes the requested constant is set by require_association and calls const_get to retrieve it.
+ If require_association did not set the constant then const_get will call const_missing, resulting in an infinite loop #380 *Jeremy Kemper*
+
+* Fixed broken transactions that were actually only running object-level and not db level transactions *andreas*
+
+* Fixed that validates_uniqueness_of used 'id' instead of defined primary key #406
+
+* Fixed that the overwritten respond_to? method didn't take two parameters like the original #391
+
+* Fixed quoting in validates_format_of that would allow some rules to pass regardless of input #390 *Dmitry V. Sabanin*
+
+
+## 1.3.0 (December 23, 2004) ##
+
+* Added a require_association hook on const_missing that makes it possible to use any model class without requiring it first. This makes STI look like:
+
+ before:
+ require_association 'person'
+ class Employee < Person
+ end
+
+ after:
+ class Employee < Person
+ end
+
+ This also reduces the usefulness of Controller.model in Action Pack to currently only being for documentation purposes.
+
+* Added that Base.update_all and Base.delete_all return an integer of the number of affected rows #341
+
+* Added scope option to validation_uniqueness #349 *Kent Sibilev*
+
+* Added respondence to *_before_type_cast for all attributes to return their string-state before they were type casted by the column type.
+ This is helpful for getting "100,000" back on a integer-based validation where the value would normally be "100".
+
+* Added allow_nil options to validates_inclusion_of so that validation is only triggered if the attribute is not nil *what-a-day*
+
+* Added work-around for PostgreSQL and the problem of getting fixtures to be created from id 1 on each test case.
+ This only works for auto-incrementing primary keys called "id" for now #359 *Scott Baron*
+
+* Added Base#clear_association_cache to empty all the cached associations #347 *Tobias Lütke*
+
+* Added more informative exceptions in establish_connection #356 *Jeremy Kemper*
+
+* Added Base#update_attributes that'll accept a hash of attributes and save the record (returning true if it passed validation, false otherwise).
+
+ Before:
+ person.attributes = @params["person"]
+ person.save
+
+ Now:
+ person.update_attributes(@params["person"])
+
+* Added Base.destroy and Base.delete to remove records without holding a reference to them first.
+
+* Added that query benchmarking will only happen if its going to be logged anyway #344
+
+* Added higher_item and lower_item as public methods for acts_as_list #342 *Tobias Lütke*
+
+* Fixed that options[:counter_sql] was overwritten with interpolated sql rather than original sql #355 *Jeremy Kemper*
+
+* Fixed that overriding an attribute's accessor would be disregarded by add_on_empty and add_on_boundary_breaking because they simply used
+ the attributes[] hash instead of checking for @base.respond_to?(attr.to_s). *Marten*
+
+* Fixed that Base.table_name would expect a parameter when used in has_and_belongs_to_many joins *Anna Lissa Cruz*
+
+* Fixed that nested transactions now work by letting the outer most transaction have the responsibilty of starting and rolling back the transaction.
+ If any of the inner transactions swallow the exception raised, though, the transaction will not be rolled back. So always let the transaction
+ bubble up even when you've dealt with local issues. Closes #231 and #340.
+
+* Fixed validates_{confirmation,acceptance}_of to only happen when the virtual attributes are not nil #348 *dpiddy@gmail.com*
+
+* Changed the interface on AbstractAdapter to require that adapters return the number of affected rows on delete and update operations.
+
+* Fixed the automated timestamping feature when running under Rails' development environment that resets the inheritable attributes on each request.
+
+
+
+## 1.2.0 ##
+
+* Added Base.validates_inclusion_of that validates whether the value of the specified attribute is available in a particular enumerable
+ object. *what-a-day*
+
+ class Person < ActiveRecord::Base
+ validates_inclusion_of :gender, :in=>%w( m f ), :message=>"woah! what are you then!??!!"
+ validates_inclusion_of :age, :in=>0..99
+ end
+
+* Added acts_as_list that can decorates an existing class with methods like move_higher/lower, move_to_top/bottom. [Tobias Lütke] Example:
+
+ class TodoItem < ActiveRecord::Base
+ acts_as_list :scope => :todo_list_id
+ belongs_to :todo_list
+ end
+
+* Added acts_as_tree that can decorates an existing class with a many to many relationship with itself. Perfect for categories in
+ categories and the likes. *Tobias Lütke*
+
+* Added that Active Records will automatically record creation and/or update timestamps of database objects if fields of the names
+ created_at/created_on or updated_at/updated_on are present. *Tobias Lütke*
+
+* Added Base.default_error_messages as a hash of all the error messages used in the validates_*_of so they can be changed in one place *Tobias Lütke*
+
+* Added automatic transaction block around AssociationCollection.<<, AssociationCollection.delete, and AssociationCollection.destroy_all
+
+* Fixed that Base#find will return an array if given an array -- regardless of the number of elements #270 *Marten*
+
+* Fixed that has_and_belongs_to_many would generate bad sql when naming conventions differed from using vanilla "id" everywhere *RedTerror*
+
+* Added a better exception for when a type column is used in a table without the intention of triggering single-table inheritance. Example:
+
+ ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'bad_class!'.
+ This error is raised because the column 'type' is reserved for storing the class in case of inheritance.
+ Please rename this column if you didn't intend it to be used for storing the inheritance class or
+ overwrite Company.inheritance_column to use another column for that information.
+
+* Added that single-table inheritance will only kick in if the inheritance_column (by default "type") is present. Otherwise, inheritance won't
+ have any magic side effects.
+
+* Added the possibility of marking fields as being in error without adding a message (using nil) to it that'll get displayed wth full_messages #208 *mjobin*
+
+* Fixed Base.errors to be indifferent as to whether strings or symbols are used. Examples:
+
+ Before:
+ errors.add(:name, "must be shorter") if name.size > 10
+ errors.on(:name) # => "must be shorter"
+ errors.on("name") # => nil
+
+ After:
+ errors.add(:name, "must be shorter") if name.size > 10
+ errors.on(:name) # => "must be shorter"
+ errors.on("name") # => "must be shorter"
+
+* Added Base.validates_format_of that Validates whether the value of the specified attribute is of the correct form by matching
+ it against the regular expression provided. *Marcel Molina Jr.*
+
+ class Person < ActiveRecord::Base
+ validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/, :on => :create
+ end
+
+* Added Base.validates_length_of that delegates to add_on_boundary_breaking #312 [Tobias Lütke]. Example:
+
+ Validates that the specified attribute matches the length restrictions supplied in either:
+
+ - configuration[:minimum]
+ - configuration[:maximum]
+ - configuration[:is]
+ - configuration[:within] (aka. configuration[:in])
+
+ Only one option can be used at a time.
+
+ class Person < ActiveRecord::Base
+ validates_length_of :first_name, :maximum=>30
+ validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
+ validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
+ validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
+ validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
+ end
+
+* Added Base.validate_presence as an alternative to implementing validate and doing errors.add_on_empty yourself.
+
+* Added Base.validates_uniqueness_of that alidates whether the value of the specified attributes are unique across the system.
+ Useful for making sure that only one user can be named "davidhh".
+
+ class Person < ActiveRecord::Base
+ validates_uniqueness_of :user_name
+ end
+
+ When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified
+ attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
+
+
+* Added Base.validates_confirmation_of that encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
+
+ Model:
+ class Person < ActiveRecord::Base
+ validates_confirmation_of :password
+ end
+
+ View:
+ <%= password_field "person", "password" %>
+ <%= password_field "person", "password_confirmation" %>
+
+ The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
+ It exists only as an in-memory variable for validating the password. This check is performed both on create and update.
+
+
+* Added Base.validates_acceptance_of that encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
+
+ class Person < ActiveRecord::Base
+ validates_acceptance_of :terms_of_service
+ end
+
+ The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed both on create and update.
+
+ NOTE: The agreement is considered valid if it's set to the string "1". This makes it easy to relate it to an HTML checkbox.
+
+
+* Added validation macros to make the stackable just like the life cycle callbacks. Examples:
+
+ class Person < ActiveRecord::Base
+ validate { |record| record.errors.add("name", "too short") unless name.size > 10 }
+ validate { |record| record.errors.add("name", "too long") unless name.size < 20 }
+ validate_on_create :validate_password
+
+ private
+ def validate_password
+ errors.add("password", "too short") unless password.size > 6
+ end
+ end
+
+* Added the option for sanitizing find_by_sql and the offset parts in regular finds [Sam Stephenson]. Examples:
+
+ Project.find_all ["category = ?", category_name], "created ASC", ["? OFFSET ?", 15, 20]
+ Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
+
+* Fixed value quoting in all generated SQL statements, so that integers are not surrounded in quotes and that all sanitation are happening
+ through the database's own quoting routine. This should hopefully make it lots easier for new adapters that doesn't accept '1' for integer
+ columns.
+
+* Fixed has_and_belongs_to_many guessing of foreign key so that keys are generated correctly for models like SomeVerySpecialClient
+ *Florian Weber*
+
+* Added counter_sql option for has_many associations [Jeremy Kemper]. Documentation:
+
+ <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
+ specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
+
+* Fixed that methods wrapped in callbacks still return their original result #260 *Jeremy Kemper*
+
+* Fixed the Inflector to handle the movie/movies pair correctly #261 *Scott Baron*
+
+* Added named bind-style variable interpolation #281 [Michael Koziarski]. Example:
+
+ Person.find(["id = :id and first_name = :first_name", { :id => 5, :first_name = "bob' or 1=1" }])
+
+* Added bind-style variable interpolation for the condition arrays that uses the adapter's quote method *Michael Koziarski*
+
+ Before:
+ find_first([ "user_name = '%s' AND password = '%s'", user_name, password ])]
+ find_first([ "firm_id = %s", firm_id ])] # unsafe!
+
+ After:
+ find_first([ "user_name = ? AND password = ?", user_name, password ])]
+ find_first([ "firm_id = ?", firm_id ])]
+
+* Added CSV format for fixtures #272 [what-a-day]. (See the new and expanded documentation on fixtures for more information)
+
+* Fixed fixtures using primary key fields called something else than "id" *dave*
+
+* Added proper handling of time fields that are turned into Time objects with the dummy date of 2000/1/1 *HariSeldon*
+
+* Added reverse order of deleting fixtures, so referential keys can be maintained #247 *Tim Bates*
+
+* Added relative path search for sqlite dbfiles in database.yml (if RAILS_ROOT is defined) #233 *Jeremy Kemper*
+
+* Added option to establish_connection where you'll be able to leave out the parameter to have it use the RAILS_ENV environment variable
+
+* Fixed problems with primary keys and postgresql sequences (#230) *Tim Bates*
+
+* Added reloading for associations under cached environments like FastCGI and mod_ruby. This makes it possible to use those environments for development.
+ This is turned on by default, but can be turned off with ActiveRecord::Base.reload_dependencies = false in production environments.
+
+ NOTE: This will only have an effect if you let the associations manage the requiring of model classes. All libraries loaded through
+ require will be "forever" cached. You can, however, use ActiveRecord::Base.load_or_require("library") to get this behavior outside of the
+ auto-loading associations.
+
+* Added ERB capabilities to the fixture files for dynamic fixture generation. You don't need to do anything, just include ERB blocks like:
+
+ david:
+ id: 1
+ name: David
+
+ jamis:
+ id: 2
+ name: Jamis
+
+ <% for digit in 3..10 %>
+ dev_<%= digit %>:
+ id: <%= digit %>
+ name: fixture_<%= digit %>
+ <% end %>
+
+* Changed the yaml fixture searcher to look in the root of the fixtures directory, so when you before could have something like:
+
+ fixtures/developers/fixtures.yaml
+ fixtures/accounts/fixtures.yaml
+
+ ...you now need to do:
+
+ fixtures/developers.yaml
+ fixtures/accounts.yaml
+
+* Changed the fixture format from:
+
+ name: david
+ data:
+ id: 1
+ name: David Heinemeier Hansson
+ birthday: 1979-10-15
+ profession: Systems development
+ ---
+ name: steve
+ data:
+ id: 2
+ name: Steve Ross Kellock
+ birthday: 1974-09-27
+ profession: guy with keyboard
+
+ ...to:
+
+ david:
+ id: 1
+ name: David Heinemeier Hansson
+ birthday: 1979-10-15
+ profession: Systems development
+
+ steve:
+ id: 2
+ name: Steve Ross Kellock
+ birthday: 1974-09-27
+ profession: guy with keyboard
+
+ The change is NOT backwards compatible. Fixtures written in the old YAML style needs to be rewritten!
+
+* All associations will now attempt to require the classes that they associate to. Relieving the need for most explicit 'require' statements.
+
+
+## 1.1.0 (34) ##
+
+* Added automatic fixture setup and instance variable availability. Fixtures can also be automatically
+ instantiated in instance variables relating to their names using the following style:
+
+ class FixturesTest < Test::Unit::TestCase
+ fixtures :developers # you can add more with comma separation
+
+ def test_developers
+ assert_equal 3, @developers.size # the container for all the fixtures is automatically set
+ assert_kind_of Developer, @david # works like @developers["david"].find
+ assert_equal "David Heinemeier Hansson", @david.name
+ end
+ end
+
+* Added HasAndBelongsToManyAssociation#push_with_attributes(object, join_attributes) that can create associations in the join table with additional
+ attributes. This is really useful when you have information that's only relevant to the join itself, such as a "added_on" column for an association
+ between post and category. The added attributes will automatically be injected into objects retrieved through the association similar to the piggy-back
+ approach:
+
+ post.categories.push_with_attributes(category, :added_on => Date.today)
+ post.categories.first.added_on # => Date.today
+
+ NOTE: The categories table doesn't have an added_on column, it's the categories_post join table that does!
+
+* Fixed that :exclusively_dependent and :dependent can't be activated at the same time on has_many associations *Jeremy Kemper*
+
+* Fixed that database passwords couldn't be all numeric *Jeremy Kemper*
+
+* Fixed that calling id would create the instance variable for new_records preventing them from being saved correctly *Jeremy Kemper*
+
+* Added sanitization feature to HasManyAssociation#find_all so it works just like Base.find_all *Sam Stephenson/Jeremy Kemper*
+
+* Added that you can pass overlapping ids to find without getting duplicated records back *Jeremy Kemper*
+
+* Added that Base.benchmark returns the result of the block *Jeremy Kemper*
+
+* Fixed problem with unit tests on Windows with SQLite *paterno*
+
+* Fixed that quotes would break regular non-yaml fixtures *Dmitry Sabanin/daft*
+
+* Fixed fixtures on windows with line endings cause problems under unix / mac *Tobias Lütke*
+
+* Added HasAndBelongsToManyAssociation#find(id) that'll search inside the collection and find the object or record with that id
+
+* Added :conditions option to has_and_belongs_to_many that works just like the one on all the other associations
+
+* Added AssociationCollection#clear to remove all associations from has_many and has_and_belongs_to_many associations without destroying the records *geech*
+
+* Added type-checking and remove in 1-instead-of-N sql statements to AssociationCollection#delete *geech*
+
+* Added a return of self to AssociationCollection#<< so appending can be chained, like project << Milestone.create << Milestone.create *geech*
+
+* Added Base#hash and Base#eql? which means that all of the equality using features of array and other containers now works:
+
+ [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
+
+* Added :uniq as an option to has_and_belongs_to_many which will automatically ensure that AssociateCollection#uniq is called
+ before pulling records out of the association. This is especially useful for three-way (and above) has_and_belongs_to_many associations.
+
+* Added AssociateCollection#uniq which is especially useful for has_and_belongs_to_many associations that can include duplicates,
+ which is common on associations that also use metadata. Usage: post.categories.uniq
+
+* Fixed respond_to? to use a subclass specific hash instead of an Active Record-wide one
+
+* Fixed has_and_belongs_to_many to treat associations between classes in modules properly *Florian Weber*
+
+* Added a NoMethod exception to be raised when query and writer methods are called for attributes that doesn't exist *geech*
+
+* Added a more robust version of Fixtures that throws meaningful errors when on formatting issues *geech*
+
+* Added Base#transaction as a compliment to Base.transaction for prettier use in instance methods *geech*
+
+* Improved the speed of respond_to? by placing the dynamic methods lookup table in a hash *geech*
+
+* Added that any additional fields added to the join table in a has_and_belongs_to_many association
+ will be placed as attributes when pulling records out through has_and_belongs_to_many associations.
+ This is helpful when have information about the association itself that you want available on retrival.
+
+* Added better loading exception catching and RubyGems retries to the database adapters *alexeyv*
+
+* Fixed bug with per-model transactions *daniel*
+
+* Fixed Base#transaction so that it returns the result of the last expression in the transaction block *alexeyv*
+
+* Added Fixture#find to find the record corresponding to the fixture id. The record
+ class name is guessed by using Inflector#classify (also new) on the fixture directory name.
+
+ Before: Document.find(@documents["first"]["id"])
+ After : @documents["first"].find
+
+* Fixed that the table name part of column names ("TABLE.COLUMN") wasn't removed properly *Andreas Schwarz*
+
+* Fixed a bug with Base#size when a finder_sql was used that didn't capitalize SELECT and FROM *geech*
+
+* Fixed quoting problems on SQLite by adding quote_string to the AbstractAdapter that can be overwritten by the concrete
+ adapters for a call to the dbm. *Andreas Schwarz*
+
+* Removed RubyGems backup strategy for requiring SQLite-adapter -- if people want to use gems, they're already doing it with AR.
+
+
+## 1.0.0 (35) ##
+
+* Added OO-style associations methods [Florian Weber]. Examples:
+
+ Project#milestones_count => Project#milestones.size
+ Project#build_to_milestones => Project#milestones.build
+ Project#create_for_milestones => Project#milestones.create
+ Project#find_in_milestones => Project#milestones.find
+ Project#find_all_in_milestones => Project#milestones.find_all
+
+* Added serialize as a new class method to control when text attributes should be YAMLized or not. This means that automated
+ serialization of hashes, arrays, and so on WILL NO LONGER HAPPEN (#10). You need to do something like this:
+
+ class User < ActiveRecord::Base
+ serialize :settings
+ end
+
+ This will assume that settings is a text column and will now YAMLize any object put in that attribute. You can also specify
+ an optional :class_name option that'll raise an exception if a serialized object is retrieved as a descendant of a class not in
+ the hierarchy. Example:
+
+ class User < ActiveRecord::Base
+ serialize :settings, :class_name => "Hash"
+ end
+
+ user = User.create("settings" => %w( one two three ))
+ User.find(user.id).settings # => raises SerializationTypeMismatch
+
+* Added the option to connect to a different database for one model at a time. Just call establish_connection on the class
+ you want to have connected to another database than Base. This will automatically also connect decendents of that class
+ to the different database [Renald Buter].
+
+* Added transactional protection for Base#save. Validations can now check for values knowing that it happens in a transaction and callbacks
+ can raise exceptions knowing that the save will be rolled back. *Suggested by Alexey Verkhovsky*
+
+* Added column name quoting so reserved words, such as "references", can be used as column names *Ryan Platte*
+
+* Added the possibility to chain the return of what happened inside a logged block [geech]:
+
+ This now works:
+ log { ... }.map { ... }
+
+ Instead of doing:
+ result = []
+ log { result = ... }
+ result.map { ... }
+
+* Added "socket" option for the MySQL adapter, so you can change it to something else than "/tmp/mysql.sock" *Anna Lissa Cruz*
+
+* Added respond_to? answers for all the attribute methods. So if Person has a name attribute retrieved from the table schema,
+ person.respond_to? "name" will return true.
+
+* Added Base.benchmark which can be used to aggregate logging and benchmark, so you can measure and represent multiple statements in a single block.
+ Usage (hides all the SQL calls for the individual actions and calculates total runtime for them all):
+
+ Project.benchmark("Creating project") do
+ project = Project.create("name" => "stuff")
+ project.create_manager("name" => "David")
+ project.milestones << Milestone.find_all
+ end
+
+* Added logging of invalid SQL statements *Daniel Von Fange*
+
+* Added alias Errors#[] for Errors#on, so you can now say person.errors["name"] to retrieve the errors for name *Andreas Schwarz*
+
+* Added RubyGems require attempt if sqlite-ruby is not available through regular methods.
+
+* Added compatibility with 2.x series of sqlite-ruby drivers. *Jamis Buck*
+
+* Added type safety for association assignments, so a ActiveRecord::AssociationTypeMismatch will be raised if you attempt to
+ assign an object that's not of the associated class. This cures the problem with nil giving id = 4 and fixnums giving id = 1 on
+ mistaken association assignments. *Reported by Andreas Schwarz*
+
+* Added the option to keep many fixtures in one single YAML document *what-a-day*
+
+* Added the class method "inheritance_column" that can be overwritten to return the name of an alternative column than "type" for storing
+ the type for inheritance hierarchies. *Dave Steinberg*
+
+* Added [] and []= as an alternative way to access attributes when the regular methods have been overwritten *Dave Steinberg*
+
+* Added the option to observer more than one class at the time by specifying observed_class as an array
+
+* Added auto-id propagation support for tables with arbitrary primary keys that have autogenerated sequences associated with them
+ on PostgreSQL. *Dave Steinberg*
+
+* Changed that integer and floats set to "" through attributes= remain as NULL. This was especially a problem for scaffolding and postgresql. (#49)
+
+* Changed the MySQL Adapter to rely on MySQL for its defaults for socket, host, and port *Andreas Schwarz*
+
+* Changed ActionControllerError to decent from StandardError instead of Exception. It can now be caught by a generic rescue.
+
+* Changed class inheritable attributes to not use eval *Caio Chassot*
+
+* Changed Errors#add to now use "invalid" as the default message instead of true, which means full_messages work with those *Marcel Molina Jr.*
+
+* Fixed spelling on Base#add_on_boundry_breaking to Base#add_on_boundary_breaking (old naming still works) *Marcel Molina Jr.*
+
+* Fixed that entries in the has_and_belongs_to_many join table didn't get removed when an associated object was destroyed.
+
+* Fixed unnecessary calls to SET AUTOCOMMIT=0/1 for MySQL adapter *Andreas Schwarz*
+
+* Fixed PostgreSQL defaults are now handled gracefully *Dave Steinberg*
+
+* Fixed increment/decrement_counter are now atomic updates *Andreas Schwarz*
+
+* Fixed the problems the Inflector had turning Attachment into attuchments and Cases into Casis *radsaq/Florian Gross*
+
+* Fixed that cloned records would point attribute references on the parent object *Andreas Schwarz*
+
+* Fixed SQL for type call on inheritance hierarchies *Caio Chassot*
+
+* Fixed bug with typed inheritance *Florian Weber*
+
+* Fixed a bug where has_many collection_count wouldn't use the conditions specified for that association
+
+
+## 0.9.5 ##
+
+* Expanded the table_name guessing rules immensely [Florian Green]. Documentation:
+
+ Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
+ directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
+ to guess the table name from even when called on Reply. The guessing rules are as follows:
+ * Class name ends in "x", "ch" or "ss": "es" is appended, so a Search class becomes a searches table.
+ * Class name ends in "y" preceded by a consonant or "qu": The "y" is replaced with "ies",
+ so a Category class becomes a categories table.
+ * Class name ends in "fe": The "fe" is replaced with "ves", so a Wife class becomes a wives table.
+ * Class name ends in "lf" or "rf": The "f" is replaced with "ves", so a Half class becomes a halves table.
+ * Class name ends in "person": The "person" is replaced with "people", so a Salesperson class becomes a salespeople table.
+ * Class name ends in "man": The "man" is replaced with "men", so a Spokesman class becomes a spokesmen table.
+ * Class name ends in "sis": The "i" is replaced with an "e", so a Basis class becomes a bases table.
+ * Class name ends in "tum" or "ium": The "um" is replaced with an "a", so a Datum class becomes a data table.
+ * Class name ends in "child": The "child" is replaced with "children", so a NodeChild class becomes a node_children table.
+ * Class name ends in an "s": No additional characters are added or removed.
+ * Class name doesn't end in "s": An "s" is appended, so a Comment class becomes a comments table.
+ * Class name with word compositions: Compositions are underscored, so CreditCard class becomes a credit_cards table.
+ Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
+ So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
+
+ You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
+ "mice" table. Example:
+
+ class Mouse < ActiveRecord::Base
+ def self.table_name() "mice" end
+ end
+
+ This conversion is now done through an external class called Inflector residing in lib/active_record/support/inflector.rb.
+
+* Added find_all_in_collection to has_many defined collections. Works like this:
+
+ class Firm < ActiveRecord::Base
+ has_many :clients
+ end
+
+ firm.id # => 1
+ firm.find_all_in_clients "revenue > 1000" # SELECT * FROM clients WHERE firm_id = 1 AND revenue > 1000
+
+ *Requested by Dave Thomas*
+
+* Fixed finders for inheritance hierarchies deeper than one level *Florian Weber*
+
+* Added add_on_boundry_breaking to errors to accompany add_on_empty as a default validation method. It's used like this:
+
+ class Person < ActiveRecord::Base
+ protected
+ def validation
+ errors.add_on_boundry_breaking "password", 3..20
+ end
+ end
+
+ This will add an error to the tune of "is too short (minimum is 3 characters)" or "is too long (minimum is 20 characters)" if
+ the password is outside the boundry. The messages can be changed by passing a third and forth parameter as message strings.
+
+* Implemented a clone method that works properly with AR. It returns a clone of the record that
+ hasn't been assigned an id yet and is treated as a new record.
+
+* Allow for domain sockets in PostgreSQL by not assuming localhost when no host is specified *Scott Barron*
+
+* Fixed that bignums are saved properly instead of attempted to be YAMLized *Andreas Schwartz*
+
+* Fixed a bug in the GEM where the rdoc options weren't being passed according to spec *Chad Fowler*
+
+* Fixed a bug with the exclusively_dependent option for has_many
+
+
+## 0.9.4 ##
+
+* Correctly guesses the primary key when the class is inside a module [Dave Steinberg].
+
+* Added [] and []= as alternatives to read_attribute and write_attribute *Dave Steinberg*
+
+* has_and_belongs_to_many now accepts an :order key to determine in which order the collection is returned [radsaq].
+
+* The ids passed to find and find_on_conditions are now automatically sanitized.
+
+* Added escaping of plings in YAML content.
+
+* Multi-parameter assigns where all the parameters are empty will now be set to nil instead of a new instance of their class.
+
+* Proper type within an inheritance hierarchy is now ensured already at object initialization (instead of first at create)
+
+
+## 0.9.3 ##
+
+* Fixed bug with using a different primary key name together with has_and_belongs_to_many *Investigation by Scott*
+
+* Added :exclusively_dependent option to the has_many association macro. The doc reads:
+
+ If set to true all the associated object are deleted in one SQL statement without having their
+ before_destroy callback run. This should only be used on associations that depend solely on
+ this class and don't need to do any clean-up in before_destroy. The upside is that it's much
+ faster, especially if there's a counter_cache involved.
+
+* Added :port key to connection options, so the PostgreSQL and MySQL adapters can connect to a database server
+ running on another port than the default.
+
+* Converted the new natural singleton methods that prevented AR objects from being saved by PStore
+ (and hence be placed in a Rails session) to a module. *Florian Weber*
+
+* Fixed the use of floats (was broken since 0.9.0+)
+
+* Fixed PostgreSQL adapter so default values are displayed properly when used in conjunction with
+ Action Pack scaffolding.
+
+* Fixed booleans support for PostgreSQL (use real true/false on boolean fields instead of 0/1 on tinyints) *radsaq*
+
+
+## 0.9.2 ##
+
+* Added static method for instantly updating a record
+
+* Treat decimal and numeric as Ruby floats *Andreas Schwartz*
+
+* Treat chars as Ruby strings (fixes problem for Action Pack form helpers too)
+
+* Removed debugging output accidently left in (which would screw web applications)
+
+
+## 0.9.1 ##
+
+* Added MIT license
+
+* Added natural object-style assignment for has_and_belongs_to_many associations. Consider the following model:
+
+ class Event < ActiveRecord::Base
+ has_one_and_belongs_to_many :sponsors
+ end
+
+ class Sponsor < ActiveRecord::Base
+ has_one_and_belongs_to_many :sponsors
+ end
+
+ Earlier, you'd have to use synthetic methods for creating associations between two objects of the above class:
+
+ roskilde_festival.add_to_sponsors(carlsberg)
+ roskilde_festival.remove_from_sponsors(carlsberg)
+
+ nike.add_to_events(world_cup)
+ nike.remove_from_events(world_cup)
+
+ Now you can use regular array-styled methods:
+
+ roskilde_festival.sponsors << carlsberg
+ roskilde_festival.sponsors.delete(carlsberg)
+
+ nike.events << world_cup
+ nike.events.delete(world_cup)
+
+* Added delete method for has_many associations. Using this will nullify an association between the has_many and the belonging
+ object by setting the foreign key to null. Consider this model:
+
+ class Post < ActiveRecord::Base
+ has_many :comments
+ end
+
+ class Comment < ActiveRecord::Base
+ belongs_to :post
+ end
+
+ You could do something like:
+
+ funny_comment.has_post? # => true
+ announcement.comments.delete(funny_comment)
+ funny_comment.has_post? # => false
+
+
+## 0.9.0 ##
+
+* Active Record is now thread safe! (So you can use it with Cerise and WEBrick applications)
+ *Implementation idea by Michael Neumann, debugging assistance by Jamis Buck*
+
+* Improved performance by roughly 400% on a basic test case of pulling 100 records and querying one attribute.
+ This brings the tax for using Active Record instead of "riding on the metal" (using MySQL-ruby C-driver directly) down to ~50%.
+ Done by doing lazy type conversions and caching column information on the class-level.
+
+* Added callback objects and procs as options for implementing the target for callback macros.
+
+* Added "counter_cache" option to belongs_to that automates the usage of increment_counter and decrement_counter. Consider:
+
+ class Post < ActiveRecord::Base
+ has_many :comments
+ end
+
+ class Comment < ActiveRecord::Base
+ belongs_to :post
+ end
+
+ Iterating over 100 posts like this:
+
+ <% for post in @posts %>
+ <%= post.title %> has <%= post.comments_count %> comments
+ <% end %>
+
+ Will generate 100 SQL count queries -- one for each call to post.comments_count. If you instead add a "comments_count" int column
+ to the posts table and rewrite the comments association macro with:
+
+ class Comment < ActiveRecord::Base
+ belongs_to :post, :counter_cache => true
+ end
+
+ Those 100 SQL count queries will be reduced to zero. Beware that counter caching is only appropriate for objects that begin life
+ with the object it's specified to belong with and is destroyed like that as well. Typically objects where you would also specify
+ :dependent => true. If your objects switch from one belonging to another (like a post that can be move from one category to another),
+ you'll have to manage the counter yourself.
+
+* Added natural object-style assignment for has_one and belongs_to associations. Consider the following model:
+
+ class Project < ActiveRecord::Base
+ has_one :manager
+ end
+
+ class Manager < ActiveRecord::Base
+ belongs_to :project
+ end
+
+ Earlier, assignments would work like following regardless of which way the assignment told the best story:
+
+ active_record.manager_id = david.id
+
+ Now you can do it either from the belonging side:
+
+ david.project = active_record
+
+ ...or from the having side:
+
+ active_record.manager = david
+
+ If the assignment happens from the having side, the assigned object is automatically saved. So in the example above, the
+ project_id attribute on david would be set to the id of active_record, then david would be saved.
+
+* Added natural object-style assignment for has_many associations [Florian Weber]. Consider the following model:
+
+ class Project < ActiveRecord::Base
+ has_many :milestones
+ end
+
+ class Milestone < ActiveRecord::Base
+ belongs_to :project
+ end
+
+ Earlier, assignments would work like following regardless of which way the assignment told the best story:
+
+ deadline.project_id = active_record.id
+
+ Now you can do it either from the belonging side:
+
+ deadline.project = active_record
+
+ ...or from the having side:
+
+ active_record.milestones << deadline
+
+ The milestone is automatically saved with the new foreign key.
+
+* API CHANGE: Attributes for text (or blob or similar) columns will now have unknown classes stored using YAML instead of using
+ to_s. (Known classes that won't be yamelized are: String, NilClass, TrueClass, FalseClass, Fixnum, Date, and Time).
+ Likewise, data pulled out of text-based attributes will be attempted converged using Yaml if they have the "--- " header.
+ This was primarily done to be enable the storage of hashes and arrays without wrapping them in aggregations, so now you can do:
+
+ user = User.find(1)
+ user.preferences = { "background" => "black", "display" => large }
+ user.save
+
+ User.find(1).preferences # => { "background" => "black", "display" => large }
+
+ Please note that this method should only be used when you don't care about representing the object in proper columns in
+ the database. A money object consisting of an amount and a currency is still a much better fit for a value object done through
+ aggregations than this new option.
+
+* POSSIBLE CODE BREAKAGE: As a consequence of the lazy type conversions, it's a bad idea to reference the @attributes hash
+ directly (it always was, but now it's paramount that you don't). If you do, you won't get the type conversion. So to implement
+ new accessors for existing attributes, use read_attribute(attr_name) and write_attribute(attr_name, value) instead. Like this:
+
+ class Song < ActiveRecord::Base
+ # Uses an integer of seconds to hold the length of the song
+
+ def length=(minutes)
+ write_attribute("length", minutes * 60)
+ end
+
+ def length
+ read_attribute("length") / 60
+ end
+ end
+
+ The clever kid will notice that this opens a door to sidestep the automated type conversion by using @attributes directly.
+ This is not recommended as read/write_attribute may be granted additional responsibilities in the future, but if you think
+ you know what you're doing and aren't afraid of future consequences, this is an option.
+
+* Applied a few minor bug fixes reported by Daniel Von Fange.
+
+
+## 0.8.4 ##
+
+ _Reflection_
+* Added ActiveRecord::Reflection with a bunch of methods and classes for reflecting in aggregations and associations.
+
+* Added Base.columns and Base.content_columns which returns arrays of column description (type, default, etc) objects.
+
+* Added Base#attribute_names which returns an array of names for the attributes available on the object.
+
+* Added Base#column_for_attribute(name) which returns the column description object for the named attribute.
+
+
+ _Misc_
+* Added multi-parameter assignment:
+
+ # Instantiate objects for all attribute classes that needs more than one constructor parameter. This is done
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
+ # parenteses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
+ # s for String, and a for Array.
+
+ This is incredibly useful for assigning dates from HTML drop-downs of month, year, and day.
+
+* Fixed bug with custom primary key column name and Base.find on multiple parameters.
+
+* Fixed bug with dependent option on has_one associations if there was no associated object.
+
+
+## 0.8.3 ##
+
+ _Transactions_
+* Added transactional protection for destroy (important for the new :dependent option) *Suggested by Carl Youngblood*
+
+* Fixed so transactions are ignored on MyISAM tables for MySQL (use InnoDB to get transactions)
+
+* Changed transactions so only exceptions will cause a rollback, not returned false.
+
+
+ _Mapping_
+* Added support for non-integer primary keys *Aredridel/earlier work by Michael Neumann*
+
+ User.find "jdoe"
+ Product.find "PDKEY-INT-12"
+
+* Added option to specify naming method for primary key column. ActiveRecord::Base.primary_key_prefix_type can either
+ be set to nil, :table_name, or :table_name_with_underscore. :table_name will assume that Product class has a primary key
+ of "productid" and :table_name_with_underscore will assume "product_id". The default nil will just give "id".
+
+* Added an overwriteable primary_key method that'll instruct AR to the name of the
+ id column *Aredridele/earlier work by Guan Yang*
+
+ class Project < ActiveRecord::Base
+ def self.primary_key() "project_id" end
+ end
+
+* Fixed that Active Records can safely associate inside and out of modules.
+
+ class MyApplication::Account < ActiveRecord::Base
+ has_many :clients # will look for MyApplication::Client
+ has_many :interests, :class_name => "Business::Interest" # will look for Business::Interest
+ end
+
+* Fixed that Active Records can safely live inside modules *Aredridel*
+
+ class MyApplication::Account < ActiveRecord::Base
+ end
+
+
+ _Misc_
+* Added freeze call to value object assignments to ensure they remain immutable *Spotted by Gavin Sinclair*
+
+* Changed interface for specifying observed class in observers. Was OBSERVED_CLASS constant, now is
+ observed_class() class method. This is more consistant with things like self.table_name(). Works like this:
+
+ class AuditObserver < ActiveRecord::Observer
+ def self.observed_class() Account end
+ def after_update(account)
+ AuditTrail.new(account, "UPDATED")
+ end
+ end
+
+ *Suggested by Gavin Sinclair*
+
+* Create new Active Record objects by setting the attributes through a block. Like this:
+
+ person = Person.new do |p|
+ p.name = 'Freddy'
+ p.age = 19
+ end
+
+ *Suggested by Gavin Sinclair*
+
+
+## 0.8.2 ##
+
+* Added inheritable callback queues that can ensure that certain callback methods or inline fragments are
+ run throughout the entire inheritance hierarchy. Regardless of whether a descendant overwrites the callback
+ method:
+
+ class Topic < ActiveRecord::Base
+ before_destroy :destroy_author, 'puts "I'm an inline fragment"'
+ end
+
+ Learn more in link:classes/ActiveRecord/Callbacks.html
+
+* Added :dependent option to has_many and has_one, which will automatically destroy associated objects when
+ the holder is destroyed:
+
+ class Album < ActiveRecord::Base
+ has_many :tracks, :dependent => true
+ end
+
+ All the associated tracks are destroyed when the album is.
+
+* Added Base.create as a factory that'll create, save, and return a new object in one step.
+
+* Automatically convert strings in config hashes to symbols for the _connection methods. This allows you
+ to pass the argument hashes directly from yaml. (Luke)
+
+* Fixed the install.rb to include simple.rb *Spotted by Kevin Bullock*
+
+* Modified block syntax to better follow our code standards outlined in
+ http://www.rubyonrails.org/CodingStandards
+
+
+## 0.8.1 ##
+
+* Added object-level transactions *Austin Ziegler*
+
+* Changed adapter-specific connection methods to use centralized ActiveRecord::Base.establish_connection,
+ which is parametized through a config hash with symbol keys instead of a regular parameter list.
+ This will allow for database connections to be opened in a more generic fashion. (Luke)
+
+ NOTE: This requires all *_connections to be updated! Read more in:
+ http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000081
+
+* Fixed SQLite adapter so objects fetched from has_and_belongs_to_many have proper attributes
+ (t.name is now name). *Spotted by Garrett Rooney*
+
+* Fixed SQLite adapter so dates are returned as Date objects, not Time objects *Spotted by Gavin Sinclair*
+
+* Fixed requirement of date class, so date conversions are succesful regardless of whether you
+ manually require date or not.
+
+
+## 0.8.0 ##
+
+* Added transactions
+
+* Changed Base.find to also accept either a list (1, 5, 6) or an array of ids ([5, 7])
+ as parameter and then return an array of objects instead of just an object
+
+* Fixed method has_collection? for has_and_belongs_to_many macro to behave as a
+ collection, not an association
+
+* Fixed SQLite adapter so empty or nil values in columns of datetime, date, or time type
+ aren't treated as current time *Spotted by Gavin Sinclair*
+
+
+## 0.7.6 ##
+
+* Fixed the install.rb to create the lib/active_record/support directory *Spotted by Gavin Sinclair*
+* Fixed that has_association? would always return true *Daniel Von Fange*
diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE
index c73d1af096..03bde18130 100644
--- a/activerecord/MIT-LICENSE
+++ b/activerecord/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2011 David Heinemeier Hansson
+Copyright (c) 2004-2012 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
index b5db57569c..30a66ff5f0 100644
--- a/activerecord/README.rdoc
+++ b/activerecord/README.rdoc
@@ -143,14 +143,14 @@ A short rundown of some of the major features:
* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc].
- ActiveRecord::Base.logger = Logger.new(STDOUT)
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT)
ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
* Database agnostic schema management with Migrations.
class AddSystemSettings < ActiveRecord::Migration
- def self.up
+ def up
create_table :system_settings do |t|
t.string :name
t.string :label
@@ -162,7 +162,7 @@ A short rundown of some of the major features:
SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
end
- def self.down
+ def down
drop_table :system_settings
end
end
@@ -208,7 +208,9 @@ Source code can be downloaded as part of the Rails project on GitHub
== License
-Active Record is released under the MIT license.
+Active Record is released under the MIT license:
+
+* http://www.opensource.org/licenses/MIT
== Support
diff --git a/activerecord/RUNNING_UNIT_TESTS b/activerecord/RUNNING_UNIT_TESTS
index 6a2e23b01f..2c310e7ac3 100644
--- a/activerecord/RUNNING_UNIT_TESTS
+++ b/activerecord/RUNNING_UNIT_TESTS
@@ -3,7 +3,7 @@
Copy test/config.example.yml to test/config.yml and edit as needed. Or just run the tests for
the first time, which will do the copy automatically and use the default (sqlite3).
-You can build postgres and mysql databases using the build_postgresql and build_mysql rake tasks.
+You can build postgres and mysql databases using the postgresql:build_databases and mysql:build_databases rake tasks.
== Running the tests
@@ -26,13 +26,6 @@ You can run all the tests for a given database via rake:
The 'rake test' task will run all the tests for mysql, mysql2, sqlite3 and postgresql.
-== Identity Map
-
-By default the tests run with the Identity Map turned off. But all tests should pass whether or
-not the identity map is on or off. You can turn it on using the IM env variable:
-
- $ IM=true ruby -Itest test/case/base_test.rb
-
== Config file
By default, the config file is expected to be at the path test/config.yml. You can specify a
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index d769a73dba..7feb0b75a0 100755..100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rake/packagetask'
require 'rubygems/package_task'
@@ -48,8 +47,8 @@ end
|x| x =~ /\/adapters\//
} + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort
- t.verbose = true
t.warning = true
+ t.verbose = true
}
task "isolated_test_#{adapter}" do
@@ -187,15 +186,15 @@ end
task :lines do
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
- for file_name in FileList["lib/active_record/**/*.rb"]
+ FileList["lib/active_record/**/*.rb"].each do |file_name|
next if file_name =~ /vendor/
- f = File.open(file_name)
-
- while line = f.gets
- lines += 1
- next if line =~ /^\s*$/
- next if line =~ /^\s*#/
- codelines += 1
+ File.open(file_name, 'r') do |f|
+ while line = f.gets
+ lines += 1
+ next if line =~ /^\s*$/
+ next if line =~ /^\s*#/
+ codelines += 1
+ end
end
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 2de81c31a3..e8e5f4adfe 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -7,13 +7,13 @@ Gem::Specification.new do |s|
s.summary = 'Object-relational mapper framework (part of Rails).'
s.description = 'Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in.'
- s.required_ruby_version = '>= 1.8.7'
+ s.required_ruby_version = '>= 1.9.3'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*']
+ s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
s.extra_rdoc_files = %w( README.rdoc )
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('arel', '~> 2.2.1')
- s.add_dependency('tzinfo', '~> 0.3.29')
+ s.add_dependency('arel', '~> 3.0.2')
+
+ s.add_dependency('active_record_deprecated_finders', '0.0.1')
end
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index 63822731d5..31f3e02bb8 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -1,6 +1,6 @@
TIMES = (ENV['N'] || 10000).to_i
-require 'rubygems'
+require File.expand_path('../../../load_paths', __FILE__)
require "active_record"
conn = { :adapter => 'sqlite3', :database => ':memory:' }
@@ -29,6 +29,14 @@ class Exhibit < ActiveRecord::Base
def look; attributes end
def feel; look; user.name end
+ def self.with_name
+ where("name IS NOT NULL")
+ end
+
+ def self.with_notes
+ where("notes IS NOT NULL")
+ end
+
def self.look(exhibits) exhibits.each { |e| e.look } end
def self.feel(exhibits) exhibits.each { |e| e.feel } end
end
@@ -37,7 +45,14 @@ puts 'Generating data...'
module ActiveRecord
class Faker
- LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit. Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem. Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim, tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae, varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero. Praesent varius tincidunt commodo".split
+ LOREM = %Q{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit.
+ Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem.
+ Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim,
+ tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae,
+ varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum
+ tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero.
+ Praesent varius tincidunt commodo}.split
+
def self.name
LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join ' '
end
@@ -102,6 +117,10 @@ Benchmark.bm(46) do |x|
TIMES.times { Exhibit.first.look }
end
+ x.report 'Model.named_scope' do
+ TIMES.times { Exhibit.limit(10).with_name.with_notes }
+ end
+
x.report("Model.all limit(100) (x#{(TIMES / 10).ceil})") do
(TIMES / 10).ceil.times { Exhibit.look Exhibit.limit(100) }
end
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 3572c640eb..210820062b 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2011 David Heinemeier Hansson
+# Copyright (c) 2004-2012 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -22,9 +22,9 @@
#++
require 'active_support'
-require 'active_support/i18n'
require 'active_model'
require 'arel'
+require 'active_record_deprecated_finders'
require 'active_record/version'
@@ -39,9 +39,11 @@ module ActiveRecord
autoload :Aggregations
autoload :Associations
autoload :AttributeMethods
+ autoload :AttributeAssignment
autoload :AutosaveAssociation
autoload :Relation
+ autoload :NullRelation
autoload_under 'relation' do
autoload :QueryMethods
@@ -50,31 +52,42 @@ module ActiveRecord
autoload :PredicateBuilder
autoload :SpawnMethods
autoload :Batches
+ autoload :Explain
+ autoload :Delegation
end
autoload :Base
autoload :Callbacks
+ autoload :Core
autoload :CounterCache
- autoload :DynamicFinderMatch
- autoload :DynamicScopeMatch
+ autoload :ConnectionHandling
+ autoload :DynamicMatchers
+ autoload :Explain
+ autoload :Inheritance
+ autoload :Integration
autoload :Migration
autoload :Migrator, 'active_record/migration'
- autoload :NamedScope
+ autoload :Model
+ autoload :ModelSchema
autoload :NestedAttributes
autoload :Observer
autoload :Persistence
autoload :QueryCache
+ autoload :Querying
+ autoload :ReadonlyAttributes
autoload :Reflection
autoload :Result
+ autoload :Sanitization
autoload :Schema
autoload :SchemaDumper
+ autoload :Scoping
autoload :Serialization
- autoload :Store
autoload :SessionStore
+ autoload :Store
autoload :Timestamp
autoload :Transactions
+ autoload :Translation
autoload :Validations
- autoload :IdentityMap
end
module Coders
@@ -92,6 +105,7 @@ module ActiveRecord
autoload :Read
autoload :TimeZoneConversion
autoload :Write
+ autoload :Serialization
end
end
@@ -113,6 +127,15 @@ module ActiveRecord
end
end
+ module Scoping
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :Named
+ autoload :Default
+ end
+ end
+
autoload :TestCase
autoload :TestFixtures, 'active_record/fixtures'
end
@@ -121,4 +144,6 @@ ActiveSupport.on_load(:active_record) do
Arel::Table.engine = self
end
-I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
+ActiveSupport.on_load(:i18n) do
+ I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
+end
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 81ddbba51e..c7a329d74d 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -46,7 +46,7 @@ module ActiveRecord
#
# def <=>(other_money)
# if currency == other_money.currency
- # amount <=> amount
+ # amount <=> other_money.amount
# else
# amount <=> other_money.exchange_to(currency).amount
# end
@@ -71,7 +71,7 @@ module ActiveRecord
# Now it's possible to access attributes from the database through the value objects instead. If
# you choose to name the composition the same as the attribute's name, it will be the only way to
# access that attribute. That's the case with our +balance+ attribute. You interact with the value
- # objects just like you would any other attribute, though:
+ # objects just like you would with any other attribute:
#
# customer.balance = Money.new(20) # sets the Money value object and the attribute
# customer.balance # => Money value object
@@ -86,6 +86,12 @@ module ActiveRecord
# customer.address_street = "Hyancintvej"
# customer.address_city = "Copenhagen"
# customer.address # => Address.new("Hyancintvej", "Copenhagen")
+ #
+ # customer.address_street = "Vesterbrogade"
+ # customer.address # => Address.new("Hyancintvej", "Copenhagen")
+ # customer.clear_aggregation_cache
+ # customer.address # => Address.new("Vesterbrogade", "Copenhagen")
+ #
# customer.address = Address.new("May Street", "Chicago")
# customer.address_street # => "May Street"
# customer.address_city # => "Chicago"
@@ -101,8 +107,8 @@ module ActiveRecord
# ActiveRecord::Base classes are entity objects.
#
# It's also important to treat the value objects as immutable. Don't allow the Money object to have
- # its amount changed after creation. Create a new Money object with the new value instead. This
- # is exemplified by the Money#exchange_to method that returns a new value object instead of changing
+ # its amount changed after creation. Create a new Money object with the new value instead. The
+ # Money#exchange_to method is an example of this. It returns a new value object instead of changing
# its own values. Active Record won't persist value objects that have been changed through means
# other than the writer method.
#
@@ -119,7 +125,7 @@ module ActiveRecord
# option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
# a custom constructor to be specified.
#
- # When a new value is assigned to the value object the default assumption is that the new value
+ # When a new value is assigned to the value object, the default assumption is that the new value
# is an instance of the value class. Specifying a custom converter allows the new value to be automatically
# converted to an instance of value class if necessary.
#
@@ -172,8 +178,8 @@ module ActiveRecord
# with this option.
# * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
# object. Each mapping is represented as an array where the first item is the name of the
- # entity attribute and the second item is the name the attribute in the value object. The
- # order in which mappings are defined determine the order in which attributes are sent to the
+ # entity attribute and the second item is the name of the attribute in the value object. The
+ # order in which mappings are defined determines the order in which attributes are sent to the
# value class constructor.
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
# attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
@@ -191,7 +197,8 @@ module ActiveRecord
#
# Option examples:
# composed_of :temperature, :mapping => %w(reading celsius)
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money }
+ # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount),
+ # :converter => Proc.new { |balance| balance.to_money }
# composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
# composed_of :gps_location
# composed_of :gps_location, :allow_nil => true
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 0952ea2829..68f8bbeb1c 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
@@ -70,7 +69,7 @@ module ActiveRecord
end
end
- class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc
+ class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
def initialize(owner, reflection)
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
end
@@ -196,6 +195,26 @@ module ActiveRecord
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
# <tt>Project#categories.delete(category1)</tt>
#
+ # === Overriding generated methods
+ #
+ # Association methods are generated in a module that is included into the model class,
+ # which allows you to easily override with your own methods and call the original
+ # generated method with +super+. For example:
+ #
+ # class Car < ActiveRecord::Base
+ # belongs_to :owner
+ # belongs_to :old_owner
+ # def owner=(new_owner)
+ # self.old_owner = self.owner
+ # super
+ # end
+ # end
+ #
+ # If your model class is <tt>Project</tt>, the module is
+ # named <tt>Project::GeneratedFeatureMethods</tt>. The GeneratedFeatureMethods module is
+ # included in the model class immediately after the (anonymous) generated attributes methods
+ # module, meaning an association will override the methods for an attribute with the same name.
+ #
# === A word of warning
#
# Don't create associations that have the same name as instance methods of
@@ -524,7 +543,7 @@ module ActiveRecord
# end
#
# @group = Group.first
- # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
+ # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group
# @group.avatars # selects all avatars by going through the User join model.
#
# An important caveat with going through +has_one+ or +has_many+ associations on the
@@ -1078,8 +1097,8 @@ module ActiveRecord
# alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
# objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
# objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. If set to
- # <tt>:restrict</tt> this object raises an <tt>ActiveRecord::DeleteRestrictionError</tt> exception and
- # cannot be deleted if it has any associated objects.
+ # <tt>:restrict</tt> an error will be added to the object, preventing its deletion, if any associated
+ # objects are present.
#
# If using with the <tt>:through</tt> option, the association on the join model must be
# a +belongs_to+, and the records which get deleted are the join records, rather than
@@ -1110,7 +1129,7 @@ module ActiveRecord
# it would skip the first 4 rows.
# [:select]
# By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
- # you, for example, want to do a join but not include the joined columns. Do not forget
+ # you want to do a join but not include the joined columns, for example. Do not forget
# to include the primary and foreign keys, otherwise it will raise an error.
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
@@ -1165,7 +1184,7 @@ module ActiveRecord
# has_many :subscribers, :through => :subscriptions, :source => :user
# has_many :subscribers, :class_name => "Person", :finder_sql => Proc.new {
# %Q{
- # SELECT DISTINCT people.*
+ # SELECT DISTINCT *
# FROM people p, post_subscriptions ps
# WHERE ps.post_id = #{id} AND ps.person_id = p.id
# ORDER BY p.first_name
@@ -1232,8 +1251,8 @@ module ActiveRecord
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
# If set to <tt>:nullify</tt>, the associated object's foreign key is set to +NULL+.
- # Also, association is assigned. If set to <tt>:restrict</tt> this object raises an
- # <tt>ActiveRecord::DeleteRestrictionError</tt> exception and cannot be deleted if it has any associated object.
+ # If set to <tt>:restrict</tt>, an error will be added to the object, preventing its deletion, if an
+ # associated object is present.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
@@ -1245,8 +1264,8 @@ module ActiveRecord
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
- # you want to do a join but not include the joined columns. Do not forget to include the
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
+ # you want to do a join but not include the joined columns, for example. Do not forget to include the
# primary and foreign keys, otherwise it will raise an error.
# [:through]
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
@@ -1336,7 +1355,7 @@ module ActiveRecord
# SQL fragment, such as <tt>authorized = 1</tt>.
# [:select]
# By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed
- # if, for example, you want to do a join but not include the joined columns. Do not
+ # if you want to do a join but not include the joined columns, for example. Do not
# forget to include the primary and foreign keys, otherwise it will raise an error.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
@@ -1363,7 +1382,9 @@ module ActiveRecord
# and +decrement_counter+. The counter cache is incremented when an object of this
# class is created and decremented when it's destroyed. This requires that a column
# named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
- # is used on the associate class (such as a Post class). You can also specify a custom counter
+ # is used on the associate class (such as a Post class) - that is the migration for
+ # <tt>#{table_name}_count</tt> is created on the associate class (such that Post.comments_count will
+ # return the count cached, see note below). You can also specify a custom counter
# cache column by providing a column name instead of a +true+/+false+ value to this
# option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
# Note: Specifying a counter cache will add it to that model's list of readonly attributes
@@ -1411,7 +1432,7 @@ module ActiveRecord
# Specifies a many-to-many relationship with another class. This associates two classes via an
# intermediate join table. Unless the join table is explicitly specified as an option, it is
# guessed using the lexical order of the class names. So a join between Developer and Project
- # will give the default join table name of "developers_projects" because "D" outranks "P".
+ # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically.
# Note that this precedence is calculated using the <tt><</tt> operator for String. This
# means that if the strings are of different lengths, and the strings are equal when compared
# up to the shortest length, then the longer string is considered of higher
@@ -1424,18 +1445,18 @@ module ActiveRecord
# join table with a migration such as this:
#
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
- # def self.up
+ # def change
# create_table :developers_projects, :id => false do |t|
# t.integer :developer_id
# t.integer :project_id
# end
# end
- #
- # def self.down
- # drop_table :developers_projects
- # end
# end
#
+ # It's also a good idea to add indexes to each of those columns to speed up the joins process.
+ # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only
+ # uses one index per table during the lookup.
+ #
# Adds the following methods for retrieval and query:
#
# [collection(force_reload = false)]
@@ -1493,8 +1514,8 @@ module ActiveRecord
# * <tt>Developer#projects.size</tt>
# * <tt>Developer#projects.find(id)</tt>
# * <tt>Developer#projects.exists?(...)</tt>
- # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>)
- # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>)
+ # * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("developer_id" => id)</tt>)
+ # * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("developer_id" => id); c.save; c</tt>)
# The declaration may include an options hash to specialize the behavior of the association.
#
# === Options
@@ -1555,8 +1576,8 @@ module ActiveRecord
# An integer determining the offset from where the rows should be fetched. So at 5,
# it would skip the first 4 rows.
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
- # you want to do a join but not include the joined columns. Do not forget to include the primary
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
+ # you want to do a join but exclude the joined columns, for example. Do not forget to include the primary
# and foreign keys, otherwise it will raise an error.
# [:readonly]
# If true, all the associated objects are readonly through the association.
@@ -1575,7 +1596,7 @@ module ActiveRecord
# has_and_belongs_to_many :categories, :join_table => "prods_cats"
# has_and_belongs_to_many :categories, :readonly => true
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
- # "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}"
+ # proc { |record| "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}" }
def has_and_belongs_to_many(name, options = {}, &extension)
Builder::HasAndBelongsToMany.build(self, name, options, &extension)
end
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 0248c7483c..84540a7000 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -5,12 +5,13 @@ module ActiveRecord
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
# ActiveRecord::Associations::ThroughAssociationScope
class AliasTracker # :nodoc:
- attr_reader :aliases, :table_joins
+ attr_reader :aliases, :table_joins, :connection
# table_joins is an array of arel joins which might conflict with the aliases we assign here
- def initialize(table_joins = [])
+ def initialize(connection = ActiveRecord::Model.connection, table_joins = [])
@aliases = Hash.new { |h,k| h[k] = initial_count_for(k) }
@table_joins = table_joins
+ @connection = connection
end
def aliased_table_for(table_name, aliased_name = nil)
@@ -70,10 +71,6 @@ module ActiveRecord
def truncate(name)
name.slice(0, connection.table_alias_length - 2)
end
-
- def connection
- ActiveRecord::Base.connection
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index d1e3ff8e38..e75003f261 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -25,9 +25,7 @@ module ActiveRecord
def initialize(owner, reflection)
reflection.check_validity!
- @target = nil
@owner, @reflection = owner, reflection
- @updated = false
reset
reset_scope
@@ -38,14 +36,14 @@ module ActiveRecord
# post.comments.aliased_table_name # => "comments"
#
def aliased_table_name
- reflection.klass.table_name
+ klass.table_name
end
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
def reset
@loaded = false
- IdentityMap.remove(target) if IdentityMap.enabled? && target
@target = nil
+ @stale_state = nil
end
# Reloads the \target and returns +self+ on success.
@@ -134,17 +132,8 @@ module ActiveRecord
# ActiveRecord::RecordNotFound is rescued within the method, and it is
# not reraised. The proxy is \reset and +nil+ is the return value.
def load_target
- if find_target?
- begin
- if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
- @target = IdentityMap.get(association_class, owner[reflection.foreign_key])
- end
- rescue NameError
- nil
- ensure
- @target ||= find_target
- end
- end
+ @target = find_target if (@stale_state && stale_target?) || find_target?
+
loaded! unless loaded?
target
rescue ActiveRecord::RecordNotFound
@@ -225,16 +214,10 @@ module ActiveRecord
def stale_state
end
- def association_class
- @reflection.klass
- end
-
def build_record(attributes, options)
reflection.build_association(attributes, options) do |record|
- record.assign_attributes(
- create_scope.except(*record.changed),
- :without_protection => true
- )
+ attributes = create_scope.except(*(record.changed - [reflection.foreign_key]))
+ record.assign_attributes(attributes, :without_protection => true)
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index 6cc401e6cc..5a44d3a156 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -10,24 +10,23 @@ module ActiveRecord
def initialize(association)
@association = association
- @alias_tracker = AliasTracker.new
+ @alias_tracker = AliasTracker.new klass.connection
end
def scope
scope = klass.unscoped
- scope = scope.extending(*Array.wrap(options[:extend]))
+
+ scope.extending!(*Array(options[:extend]))
# It's okay to just apply all these like this. The options will only be present if the
# association supports that option; this is enforced by the association builder.
- scope = scope.apply_finder_options(options.slice(
- :readonly, :include, :order, :limit, :joins, :group, :having, :offset))
-
- if options[:through] && !options[:include]
- scope = scope.includes(source_options[:include])
- end
+ scope.merge!(options.slice(
+ :readonly, :references, :order, :limit, :joins, :group, :having, :offset, :select, :uniq))
- if select = select_value
- scope = scope.select(select)
+ if options[:include]
+ scope.includes! options[:include]
+ elsif options[:through]
+ scope.includes! source_options[:include]
end
add_constraints(scope)
@@ -35,14 +34,21 @@ module ActiveRecord
private
- def select_value
- select_value = options[:select]
+ def column_for(table_name, column_name)
+ columns = alias_tracker.connection.schema_cache.columns_hash[table_name]
+ columns[column_name]
+ end
- if reflection.collection?
- select_value ||= options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*"
- end
+ def bind_value(scope, column, value)
+ substitute = alias_tracker.connection.substitute_at(
+ column, scope.bind_values.length)
+ scope.bind_values += [[column, value]]
+ substitute
+ end
- select_value
+ def bind(scope, table_name, column_name, value)
+ column = column_for table_name, column_name
+ bind_value scope, column, value
end
def add_constraints(scope)
@@ -79,10 +85,13 @@ module ActiveRecord
conditions = self.conditions[i]
if reflection == chain.last
- scope = scope.where(table[key].eq(owner[foreign_key]))
+ bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
+ scope = scope.where(table[key].eq(bind_val))
if reflection.type
- scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
+ value = owner.class.base_class.name
+ bind_val = bind scope, table.table_name, reflection.type.to_s, value
+ scope = scope.where(table[reflection.type].eq(bind_val))
end
conditions.each do |condition|
@@ -102,8 +111,11 @@ module ActiveRecord
scope = scope.joins(join(foreign_table, constraint))
- unless conditions.empty?
- scope = scope.where(sanitize(conditions, table))
+ conditions.each do |condition|
+ condition = interpolate(condition)
+ condition = { (table.table_alias || table.name) => condition } unless i == 0
+
+ scope = scope.where(condition)
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 97f531d064..ddfc6f6c05 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -14,6 +14,11 @@ module ActiveRecord
self.target = record
end
+ def reset
+ super
+ @updated = false
+ end
+
def updated?
@updated
end
@@ -72,7 +77,7 @@ module ActiveRecord
end
def stale_state
- owner[reflection.foreign_key].to_s
+ owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 2ee5dbbd70..88ce03a3cd 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -27,7 +27,8 @@ module ActiveRecord
end
def stale_state
- [super, owner[reflection.foreign_type].to_s]
+ foreign_key = super
+ foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb
index 96fca97440..9a6896dd55 100644
--- a/activerecord/lib/active_record/associations/builder/association.rb
+++ b/activerecord/lib/active_record/associations/builder/association.rb
@@ -1,7 +1,7 @@
module ActiveRecord::Associations::Builder
class Association #:nodoc:
class_attribute :valid_options
- self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :extend, :readonly, :validate]
+ self.valid_options = [:class_name, :foreign_key, :select, :conditions, :include, :extend, :readonly, :validate, :references]
# Set by subclasses
class_attribute :macro
@@ -16,6 +16,10 @@ module ActiveRecord::Associations::Builder
@model, @name, @options = model, name, options
end
+ def mixin
+ @model.generated_feature_methods
+ end
+
def build
validate_options
reflection = model.create_reflection(self.class.macro, name, options, model)
@@ -36,18 +40,47 @@ module ActiveRecord::Associations::Builder
def define_readers
name = self.name
-
- model.redefine_method(name) do |*params|
+ mixin.redefine_method(name) do |*params|
association(name).reader(*params)
end
end
def define_writers
name = self.name
-
- model.redefine_method("#{name}=") do |value|
+ mixin.redefine_method("#{name}=") do |value|
association(name).writer(value)
end
end
+
+ def dependent_restrict_raises?
+ ActiveRecord::Base.dependent_restrict_raises == true
+ end
+
+ def dependent_restrict_deprecation_warning
+ if dependent_restrict_raises?
+ msg = "In the next release, `:dependent => :restrict` will not raise a `DeleteRestrictionError`. "\
+ "Instead, it will add an error on the model. To fix this warning, make sure your code " \
+ "isn't relying on a `DeleteRestrictionError` and then add " \
+ "`config.active_record.dependent_restrict_raises = false` to your application config."
+ ActiveSupport::Deprecation.warn msg
+ end
+ end
+
+ def define_restrict_dependency_method
+ name = self.name
+ mixin.redefine_method(dependency_method_name) do
+ has_one_macro = association(name).reflection.macro == :has_one
+ if has_one_macro ? !send(name).nil? : send(name).exists?
+ if dependent_restrict_raises?
+ raise ActiveRecord::DeleteRestrictionError.new(name)
+ else
+ key = has_one_macro ? "one" : "many"
+ errors.add(:base, :"restrict_dependent_destroy.#{key}",
+ :record => self.class.human_attribute_name(name).downcase)
+ return false
+ end
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index f6d26840c2..4183c222de 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -25,16 +25,18 @@ module ActiveRecord::Associations::Builder
name = self.name
method_name = "belongs_to_counter_cache_after_create_for_#{name}"
- model.redefine_method(method_name) do
+ mixin.redefine_method(method_name) do
record = send(name)
record.class.increment_counter(cache_column, record.id) unless record.nil?
end
model.after_create(method_name)
method_name = "belongs_to_counter_cache_before_destroy_for_#{name}"
- model.redefine_method(method_name) do
- record = send(name)
- record.class.decrement_counter(cache_column, record.id) unless record.nil?
+ mixin.redefine_method(method_name) do
+ unless marked_for_destruction?
+ record = send(name)
+ record.class.decrement_counter(cache_column, record.id) unless record.nil?
+ end
end
model.before_destroy(method_name)
@@ -48,7 +50,7 @@ module ActiveRecord::Associations::Builder
method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}"
touch = options[:touch]
- model.redefine_method(method_name) do
+ mixin.redefine_method(method_name) do
record = send(name)
unless record.nil?
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index f62209a226..768f70b6c9 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -32,7 +32,7 @@ module ActiveRecord::Associations::Builder
private
def wrap_block_extension
- options[:extend] = Array.wrap(options[:extend])
+ options[:extend] = Array(options[:extend])
if block_extension
silence_warnings do
@@ -51,14 +51,14 @@ module ActiveRecord::Associations::Builder
# TODO : why do i need method_defined? I think its because of the inheritance chain
model.class_attribute full_callback_name.to_sym unless model.method_defined?(full_callback_name)
- model.send("#{full_callback_name}=", Array.wrap(options[callback_name.to_sym]))
+ model.send("#{full_callback_name}=", Array(options[callback_name.to_sym]))
end
def define_readers
super
name = self.name
- model.redefine_method("#{name.to_s.singularize}_ids") do
+ mixin.redefine_method("#{name.to_s.singularize}_ids") do
association(name).ids_reader
end
end
@@ -67,7 +67,7 @@ module ActiveRecord::Associations::Builder
super
name = self.name
- model.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
+ mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids|
association(name).ids_writer(ids)
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index ecbc70888f..d37d4e9d33 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -21,6 +21,7 @@ module ActiveRecord::Associations::Builder
":nullify or :restrict (#{options[:dependent].inspect})"
end
+ dependent_restrict_deprecation_warning if options[:dependent] == :restrict
send("define_#{options[:dependent]}_dependency_method")
model.before_destroy dependency_method_name
end
@@ -28,15 +29,10 @@ module ActiveRecord::Associations::Builder
def define_destroy_dependency_method
name = self.name
- model.send(:define_method, dependency_method_name) do
+ mixin.redefine_method(dependency_method_name) do
send(name).each do |o|
# No point in executing the counter update since we're going to destroy the parent anyway
- counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
- if o.respond_to?(counter_method)
- class << o
- self
- end.send(:define_method, counter_method, Proc.new {})
- end
+ o.mark_for_destruction
end
send(name).delete_all
@@ -45,16 +41,15 @@ module ActiveRecord::Associations::Builder
def define_delete_all_dependency_method
name = self.name
- model.send(:define_method, dependency_method_name) do
- send(name).delete_all
+ mixin.redefine_method(dependency_method_name) do
+ association(name).delete_all
end
end
- alias :define_nullify_dependency_method :define_delete_all_dependency_method
- def define_restrict_dependency_method
+ def define_nullify_dependency_method
name = self.name
- model.send(:define_method, dependency_method_name) do
- raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty?
+ mixin.redefine_method(dependency_method_name) do
+ send(name).delete_all
end
end
diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb
index 88c0d3e90f..bc8a212bee 100644
--- a/activerecord/lib/active_record/associations/builder/has_one.rb
+++ b/activerecord/lib/active_record/associations/builder/has_one.rb
@@ -34,30 +34,23 @@ module ActiveRecord::Associations::Builder
":nullify or :restrict (#{options[:dependent].inspect})"
end
+ dependent_restrict_deprecation_warning if options[:dependent] == :restrict
send("define_#{options[:dependent]}_dependency_method")
model.before_destroy dependency_method_name
end
end
- def dependency_method_name
- "has_one_dependent_#{options[:dependent]}_for_#{name}"
- end
-
def define_destroy_dependency_method
- model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1)
- def #{dependency_method_name}
- association(#{name.to_sym.inspect}).delete
- end
- eoruby
+ name = self.name
+ mixin.redefine_method(dependency_method_name) do
+ association(name).delete
+ end
end
alias :define_delete_dependency_method :define_destroy_dependency_method
alias :define_nullify_dependency_method :define_destroy_dependency_method
- def define_restrict_dependency_method
- name = self.name
- model.redefine_method(dependency_method_name) do
- raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil?
- end
+ def dependency_method_name
+ "has_one_dependent_#{options[:dependent]}_for_#{name}"
end
end
end
diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb
index 0cbbba041a..436b6c1524 100644
--- a/activerecord/lib/active_record/associations/builder/singular_association.rb
+++ b/activerecord/lib/active_record/associations/builder/singular_association.rb
@@ -16,15 +16,15 @@ module ActiveRecord::Associations::Builder
def define_constructors
name = self.name
- model.redefine_method("build_#{name}") do |*params, &block|
+ mixin.redefine_method("build_#{name}") do |*params, &block|
association(name).build(*params, &block)
end
- model.redefine_method("create_#{name}") do |*params, &block|
+ mixin.redefine_method("create_#{name}") do |*params, &block|
association(name).create(*params, &block)
end
- model.redefine_method("create_#{name}!") do |*params, &block|
+ mixin.redefine_method("create_#{name}!") do |*params, &block|
association(name).create!(*params, &block)
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index cec876149c..4ec176e641 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/array/wrap'
-
module ActiveRecord
module Associations
# = Active Record Association Collection
@@ -8,6 +6,15 @@ module ActiveRecord
# ease the implementation of association proxies that represent
# collections. See the class hierarchy in AssociationProxy.
#
+ # CollectionAssociation:
+ # HasAndBelongsToManyAssociation => has_and_belongs_to_many
+ # HasManyAssociation => has_many
+ # HasManyThroughAssociation + ThroughAssociation => has_many :through
+ #
+ # CollectionAssociation class provides common methods to the collections
+ # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
+ # +:through association+ option.
+ #
# You need to be careful with assumptions regarding the target: The proxy
# does not fetch records from the database until it needs them, but new
# ones created with +build+ are added to the target. So, the target may be
@@ -18,12 +25,6 @@ module ActiveRecord
# If you need to work on all current children, new and existing records,
# +load_target+ and the +loaded+ flag are your friends.
class CollectionAssociation < Association #:nodoc:
- attr_reader :proxy
-
- def initialize(owner, reflection)
- super
- @proxy = CollectionProxy.new(self)
- end
# Implements the reader method, e.g. foo.items for Foo.has_many :items
def reader(force_reload = false)
@@ -33,7 +34,7 @@ module ActiveRecord
reload
end
- proxy
+ CollectionProxy.new(self)
end
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
@@ -49,23 +50,31 @@ module ActiveRecord
end
else
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
+ relation = scoped
- scoped.select(column).map! do |record|
- record.send(reflection.association_primary_key)
+ including = (relation.eager_load_values + relation.includes_values).uniq
+
+ if including.any?
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, [])
+ relation = join_dependency.join_associations.inject(relation) do |r, association|
+ association.join_relation(r)
+ end
end
+
+ relation.pluck(column)
end
end
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
pk_column = reflection.primary_key_column
- ids = Array.wrap(ids).reject { |id| id.blank? }
+ ids = Array(ids).reject { |id| id.blank? }
ids.map! { |i| pk_column.type_cast(i) }
replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
end
def reset
- @loaded = false
+ super
@target = []
end
@@ -115,8 +124,9 @@ module ActiveRecord
create_record(attributes, options, true, &block)
end
- # Add +records+ to this association. Returns +self+ so method calls may be chained.
- # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
+ # Add +records+ to this association. Returns +self+ so method calls may
+ # be chained. Since << flattens its argument list and inserts each record,
+ # +push+ and +concat+ behave identically.
def concat(*records)
load_target if owner.new_record?
@@ -142,11 +152,11 @@ module ActiveRecord
end
end
- # Remove all records from this association
+ # Remove all records from this association.
#
# See delete for more info.
def delete_all
- delete(load_target).tap do
+ delete(:all).tap do
reset
loaded!
end
@@ -162,7 +172,7 @@ module ActiveRecord
end
end
- # Calculate sum using SQL, not Enumerable
+ # Calculate sum using SQL, not Enumerable.
def sum(*args)
if block_given?
scoped.sum(*args) { |*block_args| yield(*block_args) }
@@ -211,7 +221,17 @@ module ActiveRecord
# are actually removed from the database, that depends precisely on
# +delete_records+. They are in any case removed from the collection.
def delete(*records)
- delete_or_destroy(records, options[:dependent])
+ dependent = options[:dependent]
+
+ if records.first == :all
+ if loaded? || dependent == :destroy
+ delete_or_destroy(load_target, dependent)
+ else
+ delete_records(:all, dependent)
+ end
+ else
+ delete_or_destroy(records, dependent)
+ end
end
# Destroy +records+ and remove them from this association calling
@@ -235,8 +255,12 @@ module ActiveRecord
# This method is abstract in the sense that it relies on
# +count_records+, which is a method descendants have to provide.
def size
- if owner.new_record? || (loaded? && !options[:uniq])
- target.size
+ if !find_target? || loaded?
+ if options[:uniq]
+ target.uniq.size
+ else
+ target.size
+ end
elsif !loaded? && options[:group]
load_target.size
elsif !loaded? && !options[:uniq] && target.is_a?(Array)
@@ -256,13 +280,16 @@ module ActiveRecord
load_target.size
end
- # Equivalent to <tt>collection.size.zero?</tt>. If the collection has
- # not been already loaded and you are going to fetch the records anyway
- # it is better to check <tt>collection.length.zero?</tt>.
+ # Returns true if the collection is empty. Equivalent to
+ # <tt>collection.size.zero?</tt>. If the collection has not been already
+ # loaded and you are going to fetch the records anyway it is better to
+ # check <tt>collection.length.zero?</tt>.
def empty?
size.zero?
end
+ # Returns true if the collections is not empty.
+ # Equivalent to +!collection.empty?+.
def any?
if block_given?
load_target.any? { |*block_args| yield(*block_args) }
@@ -271,7 +298,8 @@ module ActiveRecord
end
end
- # Returns true if the collection has more than 1 record. Equivalent to collection.size > 1.
+ # Returns true if the collection has more than 1 record.
+ # Equivalent to +collection.size > 1+.
def many?
if block_given?
load_target.many? { |*block_args| yield(*block_args) }
@@ -287,8 +315,8 @@ module ActiveRecord
end
end
- # Replace this collection with +other_array+
- # This will perform a diff and delete/add only records that have changed.
+ # Replace this collection with +other_array+. This will perform a diff
+ # and delete/add only records that have changed.
def replace(other_array)
other_array.each { |val| raise_on_type_mismatch(val) }
original_target = load_target.dup
@@ -344,8 +372,12 @@ module ActiveRecord
if options[:counter_sql]
interpolate(options[:counter_sql])
else
- # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
- interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
+ # replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
+ interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
+ count_with = $2.to_s
+ count_with = '*' if count_with.blank? || count_with =~ /,/
+ "SELECT #{$1}COUNT(#{count_with}) FROM"
+ end
end
end
@@ -381,12 +413,7 @@ module ActiveRecord
return memory if persisted.empty?
persisted.map! do |record|
- # Unfortunately we cannot simply do memory.delete(record) since on 1.8 this returns
- # record rather than memory.at(memory.index(record)). The behavior is fixed in 1.9.
- mem_index = memory.index(record)
-
- if mem_index
- mem_record = memory.delete_at(mem_index)
+ if mem_record = memory.delete(record)
(record.attribute_names - mem_record.changes.keys).each do |name|
mem_record[name] = record[name]
@@ -462,6 +489,8 @@ module ActiveRecord
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
"new records could not be saved."
end
+
+ target
end
def concat_records(records)
@@ -532,7 +561,7 @@ module ActiveRecord
# If using a custom finder_sql, #find scans the entire collection.
def find_by_scan(*args)
expects_array = args.first.kind_of?(Array)
- ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
+ ids = args.flatten.compact.map{ |arg| arg.to_i }.uniq
if ids.size == 1
id = ids.first
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 3181ca9a32..fa316a8c9d 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -33,17 +33,231 @@ module ActiveRecord
#
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
- class CollectionProxy # :nodoc:
- alias :proxy_extend :extend
+ class CollectionProxy < Relation
+ delegate :target, :load_target, :loaded?, :to => :@association
- instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
+ ##
+ # :method: first
+ # Returns the first record, or the first +n+ records, from the collection.
+ # If the collection is empty, the first form returns nil, and the second
+ # form returns an empty array.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
+ #
+ # person.pets.first(2)
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
+ # # ]
+ #
+ # another_person_without.pets # => []
+ # another_person_without.pets.first # => nil
+ # another_person_without.pets.first(3) # => []
- delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from,
- :lock, :readonly, :having, :to => :scoped
+ ##
+ # :method: last
+ # Returns the last record, or the last +n+ records, from the collection.
+ # If the collection is empty, the first form returns nil, and the second
+ # form returns an empty array.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ #
+ # person.pets.last(2)
+ # # => [
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # another_person_without.pets # => []
+ # another_person_without.pets.last # => nil
+ # another_person_without.pets.last(3) # => []
- delegate :target, :load_target, :loaded?, :scoped,
- :to => :@association
+ ##
+ # :method: concat
+ # Add one or more records to the collection by setting their foreign keys
+ # to the association's primary key. Since << flattens its argument list and
+ # inserts each record, +push+ and +concat+ behave identically. Returns +self+
+ # so method calls may be chained.
+ #
+ # class Person < ActiveRecord::Base
+ # pets :has_many
+ # end
+ #
+ # person.pets.size # => 0
+ # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
+ # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
+ # person.pets.size # => 3
+ #
+ # person.id # => 1
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
+ #
+ # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
+ # person.pets.size # => 5
+
+ ##
+ # :method: replace
+ # Replace this collection with +other_array+. This will perform a diff
+ # and delete/add only records that have changed.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets
+ # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
+ #
+ # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
+ #
+ # person.pets.replace(other_pets)
+ #
+ # person.pets
+ # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
+ #
+ # If the supplied array has an incorrect association type, it raises
+ # an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
+ #
+ # person.pets.replace(["doo", "ggie", "gaga"])
+ # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
+
+ ##
+ # :method: destroy_all
+ # Destroy all the records from this association.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.size # => 3
+ #
+ # person.pets.destroy_all
+ #
+ # person.pets.size # => 0
+ # person.pets # => []
+
+ ##
+ # :method: empty?
+ # Returns true if the collection is empty.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.count # => 1
+ # person.pets.empty? # => false
+ #
+ # person.pets.delete_all
+ #
+ # person.pets.count # => 0
+ # person.pets.empty? # => true
+
+ ##
+ # :method: any?
+ # Returns true if the collection is not empty.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.count # => 0
+ # person.pets.any? # => false
+ #
+ # person.pets << Pet.new(name: 'Snoop')
+ # person.pets.count # => 0
+ # person.pets.any? # => true
+ #
+ # You can also pass a block to define criteria. The behaviour
+ # is the same, it returns true if the collection based on the
+ # criteria is not empty.
+ #
+ # person.pets
+ # # => [#<Pet name: "Snoop", group: "dogs">]
+ #
+ # person.pets.any? do |pet|
+ # pet.group == 'cats'
+ # end
+ # # => false
+ #
+ # person.pets.any? do |pet|
+ # pet.group == 'dogs'
+ # end
+ # # => true
+ ##
+ # :method: many?
+ # Returns true if the collection has more than one record.
+ # Equivalent to +collection.size > 1+.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.count #=> 1
+ # person.pets.many? #=> false
+ #
+ # person.pets << Pet.new(name: 'Snoopy')
+ # person.pets.count #=> 2
+ # person.pets.many? #=> true
+ #
+ # You can also pass a block to define criteria. The
+ # behaviour is the same, it returns true if the collection
+ # based on the criteria has more than one record.
+ #
+ # person.pets
+ # # => [
+ # # #<Pet name: "Gorby", group: "cats">,
+ # # #<Pet name: "Puff", group: "cats">,
+ # # #<Pet name: "Snoop", group: "dogs">
+ # # ]
+ #
+ # person.pets.many? do |pet|
+ # pet.group == 'dogs'
+ # end
+ # # => false
+ #
+ # person.pets.many? do |pet|
+ # pet.group == 'cats'
+ # end
+ # # => true
+
+ ##
+ # :method: include?
+ # Returns true if the given object is present in the collection.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets # => [#<Pet id: 20, name: "Snoop">]
+ #
+ # person.pets.include?(Pet.find(20)) # => true
+ # person.pets.include?(Pet.find(21)) # => false
delegate :select, :find, :first, :last,
:build, :create, :create!,
:concat, :replace, :delete_all, :destroy_all, :delete, :destroy, :uniq,
@@ -53,7 +267,8 @@ module ActiveRecord
def initialize(association)
@association = association
- Array.wrap(association.options[:extend]).each { |ext| proxy_extend(ext) }
+ super association.klass, association.klass.arel_table
+ merge! association.scoped
end
alias_method :new, :build
@@ -62,44 +277,28 @@ module ActiveRecord
@association
end
- def respond_to?(name, include_private = false)
- super ||
- (load_target && target.respond_to?(name, include_private)) ||
- proxy_association.klass.respond_to?(name, include_private)
+ # We don't want this object to be put on the scoping stack, because
+ # that could create an infinite loop where we call an @association
+ # method, which gets the current scope, which is this object, which
+ # delegates to @association, and so on.
+ def scoping
+ @association.scoped.scoping { yield }
end
- def method_missing(method, *args, &block)
- match = DynamicFinderMatch.match(method)
- if match && match.instantiator?
- send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
- proxy_association.send :set_owner_attributes, r
- proxy_association.send :add_to_target, r
- yield(r) if block_given?
- end
- end
+ def spawn
+ scoped
+ end
+
+ def scoped(options = nil)
+ association = @association
- if target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
- if load_target
- if target.respond_to?(method)
- target.send(method, *args, &block)
- else
- begin
- super
- rescue NoMethodError => e
- raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}")
- end
- end
- end
-
- else
- scoped.readonly(nil).send(method, *args, &block)
+ super.extending! do
+ define_method(:proxy_association) { association }
end
end
- # Forwards <tt>===</tt> explicitly to the \target because the instance method
- # removal above doesn't catch it. Loads the \target if needed.
- def ===(other)
- other === load_target
+ def ==(other)
+ load_target == other
end
def to_ary
@@ -107,11 +306,57 @@ module ActiveRecord
end
alias_method :to_a, :to_ary
+ # Adds one or more +records+ to the collection by setting their foreign keys
+ # to the association‘s primary key. Returns +self+, so several appends may be
+ # chained together.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets.size # => 0
+ # person.pets << Pet.new(name: 'Fancy-Fancy')
+ # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
+ # person.pets.size # => 3
+ #
+ # person.id # => 1
+ # person.pets
+ # # => [
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
+ # # ]
def <<(*records)
proxy_association.concat(records) && self
end
alias_method :push, :<<
+ # Removes every object from the collection. This does not destroy
+ # the objects, it sets their foreign keys to +NULL+. Returns +self+
+ # so methods can be chained.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # person.pets # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
+ # person.pets.clear # => []
+ # person.pets.size # => 0
+ #
+ # Pet.find(1) # => #<Pet id: 1, name: "Snoop", group: "dogs", person_id: nil>
+ #
+ # If they are associated with +dependent: :destroy+ option, it deletes
+ # them directly from the database.
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :pets, dependent: :destroy
+ # end
+ #
+ # person.pets # => [#<Pet id: 2, name: "Gorby", group: "cats", person_id: 2>]
+ # person.pets.clear # => []
+ # person.pets.size # => 0
+ #
+ # Pet.find(2) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=2
def clear
delete_all
self
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 1f917f58f2..58d041ec1d 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -40,13 +40,20 @@ module ActiveRecord
def delete_records(records, method)
if sql = options[:delete_sql]
+ records = load_target if records == :all
records.each { |record| owner.connection.delete(interpolate(sql, record)) }
else
- relation = join_table
- stmt = relation.where(relation[reflection.foreign_key].eq(owner.id).
- and(relation[reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
- ).compile_delete
- owner.connection.delete stmt
+ relation = join_table
+ condition = relation[reflection.foreign_key].eq(owner.id)
+
+ unless records == :all
+ condition = condition.and(
+ relation[reflection.association_foreign_key]
+ .in(records.map { |x| x.id }.compact)
+ )
+ end
+
+ owner.connection.delete(relation.where(condition).compile_delete)
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 50ee60284c..e631579087 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -89,8 +89,12 @@ module ActiveRecord
records.each { |r| r.destroy }
update_counter(-records.length) unless inverse_updates_counter_cache?
else
- keys = records.map { |r| r[reflection.association_primary_key] }
- scope = scoped.where(reflection.association_primary_key => keys)
+ if records == :all
+ scope = scoped
+ else
+ keys = records.map { |r| r[reflection.association_primary_key] }
+ scope = scoped.where(reflection.association_primary_key => keys)
+ end
if method == :delete_all
update_counter(-scope.delete_all)
@@ -99,6 +103,10 @@ module ActiveRecord
end
end
end
+
+ def foreign_key_present?
+ owner.attribute_present?(reflection.association_primary_key)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 2e818dca5d..2683aaf5da 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -6,6 +6,13 @@ module ActiveRecord
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
include ThroughAssociation
+ def initialize(owner, reflection)
+ super
+
+ @through_records = {}
+ @through_association = nil
+ end
+
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
# loaded and calling collection.size if it has. If it's more likely than not that the collection does
# have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
@@ -42,27 +49,38 @@ module ActiveRecord
end
end
- through_record(record).save!
+ save_through_record(record)
update_counter(1)
record
end
private
- def through_record(record)
- through_association = owner.association(through_reflection.name)
- attributes = construct_join_attributes(record)
-
- through_record = Array.wrap(through_association.target).find { |candidate|
- candidate.attributes.slice(*attributes.keys) == attributes
- }
+ def through_association
+ @through_association ||= owner.association(through_reflection.name)
+ end
- unless through_record
- through_record = through_association.build(attributes)
+ # We temporarily cache through record that has been build, because if we build a
+ # through record in build_record and then subsequently call insert_record, then we
+ # want to use the exact same object.
+ #
+ # However, after insert_record has been called, we clear the cache entry because
+ # we want it to be possible to have multiple instances of the same record in an
+ # association
+ def build_through_record(record)
+ @through_records[record.object_id] ||= begin
+ ensure_mutable
+
+ through_record = through_association.build
through_record.send("#{source_reflection.name}=", record)
+ through_record
end
+ end
- through_record
+ def save_through_record(record)
+ build_through_record(record).save!
+ ensure
+ @through_records.delete(record.object_id)
end
def build_record(attributes, options = {})
@@ -73,9 +91,9 @@ module ActiveRecord
inverse = source_reflection.inverse_of
if inverse
if inverse.macro == :has_many
- record.send(inverse.name) << through_record(record)
+ record.send(inverse.name) << build_through_record(record)
elsif inverse.macro == :has_one
- record.send("#{inverse.name}=", through_record(record))
+ record.send("#{inverse.name}=", build_through_record(record))
end
end
@@ -104,8 +122,12 @@ module ActiveRecord
def delete_records(records, method)
ensure_not_nested
- through = owner.association(through_reflection.name)
- scope = through.scoped.where(construct_join_attributes(*records))
+ # This is unoptimised; it will load all the target records
+ # even when we just want to delete everything.
+ records = load_target if records == :all
+
+ scope = through_association.scoped
+ scope.where! construct_join_attributes(*records)
case method
when :destroy
@@ -116,7 +138,7 @@ module ActiveRecord
count = scope.delete_all
end
- delete_through_records(through, records)
+ delete_through_records(records)
if through_reflection.macro == :has_many && update_through_counter?(method)
update_counter(-count, through_reflection)
@@ -125,15 +147,25 @@ module ActiveRecord
update_counter(-count)
end
- def delete_through_records(through, records)
- if through_reflection.macro == :has_many
- records.each do |record|
- through.target.delete(through_record(record))
- end
- else
- records.each do |record|
- through.target = nil if through.target == through_record(record)
+ def through_records_for(record)
+ attributes = construct_join_attributes(record)
+ candidates = Array.wrap(through_association.target)
+ candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes }
+ end
+
+ def delete_through_records(records)
+ records.each do |record|
+ through_records = through_records_for(record)
+
+ if through_reflection.macro == :has_many
+ through_records.each { |r| through_association.target.delete(r) }
+ else
+ if through_records.include?(through_association.target)
+ through_association.target = nil
+ end
end
+
+ @through_records.delete(record.object_id)
end
end
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 6c878f0f00..cd366ac8b7 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -13,7 +13,7 @@ module ActiveRecord
@join_parts = [JoinBase.new(base)]
@associations = {}
@reflections = []
- @alias_tracker = AliasTracker.new(joins)
+ @alias_tracker = AliasTracker.new(base.connection, joins)
@alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
build(associations)
end
@@ -184,7 +184,7 @@ module ActiveRecord
macro = join_part.reflection.macro
if macro == :has_one
- return if record.association_cache.key?(join_part.reflection.name)
+ return record.association(join_part.reflection.name).target if record.association_cache.key?(join_part.reflection.name)
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
set_target_and_inverse(join_part, association, record)
else
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 03963ab060..0d7d28e458 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -95,8 +95,11 @@ module ActiveRecord
conditions = self.conditions[i].dup
conditions << { reflection.type => foreign_klass.base_class.name } if reflection.type
- unless conditions.empty?
- constraint = constraint.and(sanitize(conditions, table))
+ conditions.each do |condition|
+ condition = active_record.send(:sanitize_sql, interpolate(condition), table.table_alias || table.name)
+ condition = Arel.sql(condition) unless condition.is_a?(Arel::Node)
+
+ constraint = constraint.and(condition)
end
relation.from(join(table, constraint))
diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb
index f83138195c..cea6ad6944 100644
--- a/activerecord/lib/active_record/associations/join_helper.rb
+++ b/activerecord/lib/active_record/associations/join_helper.rb
@@ -40,16 +40,6 @@ module ActiveRecord
def join(table, constraint)
table.create_join(table, table.create_on(constraint), join_type)
end
-
- def sanitize(conditions, table)
- conditions = conditions.map do |condition|
- condition = active_record.send(:sanitize_sql, interpolate(condition), table.table_alias || table.name)
- condition = Arel.sql(condition) unless condition.is_a?(Arel::Node)
- condition
- end
-
- conditions.length == 1 ? conditions.first : Arel::Nodes::And.new(conditions)
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 779f8164cc..b4c3908b10 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -77,7 +77,7 @@ module ActiveRecord
# Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
# Make several smaller queries if necessary or make one query if the adapter supports it
sliced = owner_keys.each_slice(model.connection.in_clause_length || owner_keys.size)
- records = sliced.map { |slice| records_for(slice) }.flatten
+ records = sliced.map { |slice| records_for(slice).to_a }.flatten
end
# Each record may have multiple owners, and vice-versa
@@ -93,10 +93,11 @@ module ActiveRecord
end
def build_scope
- scope = klass.scoped
+ scope = klass.unscoped
+ scope.default_scoped = true
- scope = scope.where(process_conditions(options[:conditions]))
- scope = scope.where(process_conditions(preload_options[:conditions]))
+ scope = scope.where(interpolate(options[:conditions]))
+ scope = scope.where(interpolate(preload_options[:conditions]))
scope = scope.select(preload_options[:select] || options[:select] || table[Arel.star])
scope = scope.includes(preload_options[:include] || options[:include])
@@ -112,13 +113,11 @@ module ActiveRecord
scope
end
- def process_conditions(conditions)
+ def interpolate(conditions)
if conditions.respond_to?(:to_proc)
- conditions = klass.send(:instance_eval, &conditions)
- end
-
- if conditions
- klass.send(:sanitize_sql, conditions)
+ klass.send(:instance_eval, &conditions)
+ else
+ conditions
end
end
end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index b347a94978..be890e5767 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -37,14 +37,12 @@ module ActiveRecord
# situation it is more natural for the user to just create or modify their join records
# directly as required.
def construct_join_attributes(*records)
- if source_reflection.macro != :belongs_to
- raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
- end
+ ensure_mutable
join_attributes = {
source_reflection.foreign_key =>
records.map { |record|
- record.send(source_reflection.association_primary_key)
+ record.send(source_reflection.association_primary_key(reflection.klass))
}
}
@@ -64,7 +62,7 @@ module ActiveRecord
# properly support stale-checking for nested associations.
def stale_state
if through_reflection.macro == :belongs_to
- owner[through_reflection.foreign_key].to_s
+ owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s
end
end
@@ -73,6 +71,12 @@ module ActiveRecord
!owner[through_reflection.foreign_key].nil?
end
+ def ensure_mutable
+ if source_reflection.macro != :belongs_to
+ raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection)
+ end
+ end
+
def ensure_not_nested
if reflection.nested?
raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection)
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
new file mode 100644
index 0000000000..bf9fe70b31
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -0,0 +1,221 @@
+require 'active_support/concern'
+
+module ActiveRecord
+ module AttributeAssignment
+ extend ActiveSupport::Concern
+ include ActiveModel::MassAssignmentSecurity
+
+ module ClassMethods
+ private
+
+ # The primary key and inheritance column can never be set by mass-assignment for security reasons.
+ def attributes_protected_by_default
+ default = [ primary_key, inheritance_column ]
+ default << 'id' unless primary_key.eql? 'id'
+ default
+ end
+ end
+
+ # Allows you to set all the attributes at once by passing in a hash with keys
+ # matching the attribute names (which again matches the column names).
+ #
+ # If any attributes are protected by either +attr_protected+ or
+ # +attr_accessible+ then only settable attributes will be assigned.
+ #
+ # class User < ActiveRecord::Base
+ # attr_protected :is_admin
+ # end
+ #
+ # user = User.new
+ # user.attributes = { :username => 'Phusion', :is_admin => true }
+ # user.username # => "Phusion"
+ # user.is_admin? # => false
+ def attributes=(new_attributes)
+ return unless new_attributes.is_a?(Hash)
+
+ assign_attributes(new_attributes)
+ end
+
+ # Allows you to set all the attributes for a particular mass-assignment
+ # security role by passing in a hash of attributes with keys matching
+ # the attribute names (which again matches the column names) and the role
+ # name using the :as option.
+ #
+ # To bypass mass-assignment security you can use the :without_protection => true
+ # option.
+ #
+ # class User < ActiveRecord::Base
+ # attr_accessible :name
+ # attr_accessible :name, :is_admin, :as => :admin
+ # end
+ #
+ # user = User.new
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true })
+ # user.name # => "Josh"
+ # user.is_admin? # => false
+ #
+ # user = User.new
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
+ # user.name # => "Josh"
+ # user.is_admin? # => true
+ #
+ # user = User.new
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
+ # user.name # => "Josh"
+ # user.is_admin? # => true
+ def assign_attributes(new_attributes, options = {})
+ return unless new_attributes
+
+ attributes = new_attributes.stringify_keys
+ multi_parameter_attributes = []
+ nested_parameter_attributes = []
+ @mass_assignment_options = options
+
+ unless options[:without_protection]
+ attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
+ end
+
+ attributes.each do |k, v|
+ if k.include?("(")
+ multi_parameter_attributes << [ k, v ]
+ elsif respond_to?("#{k}=")
+ if v.is_a?(Hash)
+ nested_parameter_attributes << [ k, v ]
+ else
+ send("#{k}=", v)
+ end
+ else
+ raise(UnknownAttributeError, "unknown attribute: #{k}")
+ end
+ end
+
+ # assign any deferred nested attributes after the base attributes have been set
+ nested_parameter_attributes.each do |k,v|
+ send("#{k}=", v)
+ end
+
+ @mass_assignment_options = nil
+ assign_multiparameter_attributes(multi_parameter_attributes)
+ end
+
+ protected
+
+ def mass_assignment_options
+ @mass_assignment_options ||= {}
+ end
+
+ def mass_assignment_role
+ mass_assignment_options[:as] || :default
+ end
+
+ private
+
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
+ # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
+ # attribute will be set to nil.
+ def assign_multiparameter_attributes(pairs)
+ execute_callstack_for_multiparameter_attributes(
+ extract_callstack_for_multiparameter_attributes(pairs)
+ )
+ end
+
+ def instantiate_time_object(name, values)
+ if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
+ Time.zone.local(*values)
+ else
+ Time.time_with_datetime_fallback(self.class.default_timezone, *values)
+ end
+ end
+
+ def execute_callstack_for_multiparameter_attributes(callstack)
+ errors = []
+ callstack.each do |name, values_with_empty_parameters|
+ begin
+ send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
+ rescue => ex
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
+ end
+ end
+ unless errors.empty?
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
+ end
+ end
+
+ def read_value_from_parameter(name, values_hash_from_param)
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ if values_hash_from_param.values.all?{|v|v.nil?}
+ nil
+ elsif klass == Time
+ read_time_parameter_value(name, values_hash_from_param)
+ elsif klass == Date
+ read_date_parameter_value(name, values_hash_from_param)
+ else
+ read_other_parameter_value(klass, name, values_hash_from_param)
+ end
+ end
+
+ def read_time_parameter_value(name, values_hash_from_param)
+ # If Date bits were not provided, error
+ raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
+ # If Date bits were provided but blank, then return nil
+ return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
+
+ set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
+ # If Time bits are not there, then default to 0
+ (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
+ instantiate_time_object(name, set_values)
+ end
+
+ def read_date_parameter_value(name, values_hash_from_param)
+ return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
+ set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[3]]
+ begin
+ Date.new(*set_values)
+ rescue ArgumentError # if Date.new raises an exception on an invalid date
+ instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
+ end
+ end
+
+ def read_other_parameter_value(klass, name, values_hash_from_param)
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
+ values = (1..max_position).collect do |position|
+ raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
+ values_hash_from_param[position]
+ end
+ klass.new(*values)
+ end
+
+ def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
+ [values_hash_from_param.keys.max,upper_cap].min
+ end
+
+ def extract_callstack_for_multiparameter_attributes(pairs)
+ attributes = { }
+
+ pairs.each do |pair|
+ multiparameter_name, value = pair
+ attribute_name = multiparameter_name.split("(").first
+ attributes[attribute_name] = {} unless attributes.include?(attribute_name)
+
+ parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
+ end
+
+ attributes
+ end
+
+ def type_cast_attribute_value(multiparameter_name, value)
+ multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
+ end
+
+ def find_parameter_position(multiparameter_name)
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
+ end
+
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index d7bfaa5655..39ea885246 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -7,35 +7,51 @@ module ActiveRecord
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
+ included do
+ include Read
+ include Write
+ include BeforeTypeCast
+ include Query
+ include PrimaryKey
+ include TimeZoneConversion
+ include Dirty
+ include Serialization
+
+ # 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)).
+ # (Alias for the protected read_attribute method).
+ def [](attr_name)
+ read_attribute(attr_name)
+ end
+
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
+ # (Alias for the protected write_attribute method).
+ def []=(attr_name, value)
+ write_attribute(attr_name, value)
+ end
+ end
+
module ClassMethods
# Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods.
def define_attribute_methods
- return if attribute_methods_generated?
-
- if base_class == self
+ # Use a mutex; we don't want two thread simaltaneously trying to define
+ # attribute methods.
+ @attribute_methods_mutex.synchronize do
+ return if attribute_methods_generated?
+ superclass.define_attribute_methods unless self == base_class
super(column_names)
@attribute_methods_generated = true
- else
- base_class.define_attribute_methods
end
end
def attribute_methods_generated?
- if base_class == self
- @attribute_methods_generated ||= false
- else
- base_class.attribute_methods_generated?
- end
+ @attribute_methods_generated ||= false
end
- def undefine_attribute_methods(*args)
- if base_class == self
- super
- @attribute_methods_generated = false
- else
- base_class.undefine_attribute_methods(*args)
- end
+ def undefine_attribute_methods
+ super if attribute_methods_generated?
+ @attribute_methods_generated = false
end
def instance_method_already_implemented?(method_name)
@@ -43,19 +59,46 @@ module ActiveRecord
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord"
end
- super
+ if [Base, Model].include?(active_record_super)
+ super
+ else
+ # If B < A and A defines its own attribute method, then we don't want to overwrite that.
+ defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods)
+ defined && !ActiveRecord::Base.method_defined?(method_name) || super
+ end
end
# A method name is 'dangerous' if it is already defined by Active Record, but
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
- def dangerous_attribute_method?(method_name)
- active_record = ActiveRecord::Base
- superclass = ActiveRecord::Base.superclass
+ def dangerous_attribute_method?(name)
+ method_defined_within?(name, Base)
+ end
+
+ def method_defined_within?(name, klass, sup = klass.superclass)
+ if klass.method_defined?(name) || klass.private_method_defined?(name)
+ if sup.method_defined?(name) || sup.private_method_defined?(name)
+ klass.instance_method(name).owner != sup.instance_method(name).owner
+ else
+ true
+ end
+ else
+ false
+ end
+ end
- (active_record.method_defined?(method_name) ||
- active_record.private_method_defined?(method_name)) &&
- !superclass.method_defined?(method_name) &&
- !superclass.private_method_defined?(method_name)
+ def attribute_method?(attribute)
+ super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
+ end
+
+ # Returns an array of column names as strings if it's not
+ # an abstract class and table exists.
+ # Otherwise it returns an empty array.
+ def attribute_names
+ @attribute_names ||= if !abstract_class? && table_exists?
+ column_names
+ else
+ []
+ end
end
end
@@ -94,9 +137,133 @@ module ActiveRecord
super
end
+ # Returns true if the given attribute is in the attributes hash
+ def has_attribute?(attr_name)
+ @attributes.has_key?(attr_name.to_s)
+ end
+
+ # Returns an array of names for the attributes available on this object.
+ def attribute_names
+ @attributes.keys
+ end
+
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
+ def attributes
+ Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
+ end
+
+ # Returns an <tt>#inspect</tt>-like string for the value of the
+ # attribute +attr_name+. String attributes are truncated upto 50
+ # characters, and Date and Time attributes are returned in the
+ # <tt>:db</tt> format. Other attributes return the value of
+ # <tt>#inspect</tt> without modification.
+ #
+ # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
+ #
+ # person.attribute_for_inspect(:name)
+ # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
+ #
+ # person.attribute_for_inspect(:created_at)
+ # # => '"2009-01-12 04:48:57"'
+ def attribute_for_inspect(attr_name)
+ value = read_attribute(attr_name)
+
+ if value.is_a?(String) && value.length > 50
+ "#{value[0..50]}...".inspect
+ elsif value.is_a?(Date) || value.is_a?(Time)
+ %("#{value.to_s(:db)}")
+ else
+ value.inspect
+ end
+ end
+
+ # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
+ # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
+ def attribute_present?(attribute)
+ value = read_attribute(attribute)
+ !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
+ end
+
+ # Returns the column object for the named attribute.
+ def column_for_attribute(name)
+ # FIXME: should this return a null object for columns that don't exist?
+ self.class.columns_hash[name.to_s]
+ end
+
protected
- def attribute_method?(attr_name)
- attr_name == 'id' || (defined?(@attributes) && @attributes.include?(attr_name))
+
+ def clone_attributes(reader_method = :read_attribute, attributes = {})
+ attribute_names.each do |name|
+ attributes[name] = clone_attribute_value(reader_method, name)
+ end
+ attributes
+ end
+
+ def clone_attribute_value(reader_method, attribute_name)
+ value = send(reader_method, attribute_name)
+ value.duplicable? ? value.clone : value
+ rescue TypeError, NoMethodError
+ value
+ end
+
+ def arel_attributes_with_values_for_create(pk_attribute_allowed)
+ arel_attributes_with_values(attributes_for_create(pk_attribute_allowed))
+ end
+
+ def arel_attributes_with_values_for_update(attribute_names)
+ arel_attributes_with_values(attributes_for_update(attribute_names))
+ end
+
+ def attribute_method?(attr_name)
+ defined?(@attributes) && @attributes.include?(attr_name)
+ end
+
+ private
+
+ # Returns a Hash of the Arel::Attributes and attribute values that have been
+ # type casted for use in an Arel insert/update method.
+ def arel_attributes_with_values(attribute_names)
+ attrs = {}
+ arel_table = self.class.arel_table
+
+ attribute_names.each do |name|
+ attrs[arel_table[name]] = typecasted_attribute_value(name)
+ end
+ attrs
+ end
+
+ # Filters the primary keys and readonly attributes from the attribute names.
+ def attributes_for_update(attribute_names)
+ attribute_names.select do |name|
+ column_for_attribute(name) && !pk_attribute?(name) && !readonly_attribute?(name)
end
+ end
+
+ # Filters out the primary keys, from the attribute names, when the primary
+ # key is to be generated (e.g. the id attribute has no value).
+ def attributes_for_create(pk_attribute_allowed)
+ @attributes.keys.select do |name|
+ column_for_attribute(name) && (pk_attribute_allowed || !pk_attribute?(name))
+ end
+ end
+
+ def readonly_attribute?(name)
+ self.class.readonly_attributes.include?(name)
+ end
+
+ def pk_attribute?(name)
+ column_for_attribute(name).primary
+ end
+
+ def typecasted_attribute_value(name)
+ if self.class.serialized_attributes.include?(name)
+ @attributes[name].serialized_value
+ else
+ # FIXME: we need @attributes to be used consistently.
+ # If the values stored in @attributes were already typecasted, this code
+ # could be simplified
+ read_attribute(name)
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
index bde11d0494..d4f529acbf 100644
--- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -20,11 +20,7 @@ module ActiveRecord
# Handle *_before_type_cast for method_missing.
def attribute_before_type_cast(attribute_name)
- if attribute_name == 'id'
- read_attribute_before_type_cast(self.class.primary_key)
- else
- read_attribute_before_type_cast(attribute_name)
- end
+ read_attribute_before_type_cast(attribute_name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 3eff3d54e3..11c63591e3 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -13,7 +13,7 @@ module ActiveRecord
raise "You cannot include Dirty after Timestamp"
end
- class_attribute :partial_updates
+ config_attribute :partial_updates
self.partial_updates = true
end
@@ -22,8 +22,6 @@ module ActiveRecord
if status = super
@previously_changed = changes
@changed_attributes.clear
- elsif IdentityMap.enabled?
- IdentityMap.remove(self)
end
status
end
@@ -34,9 +32,6 @@ module ActiveRecord
@previously_changed = changes
@changed_attributes.clear
end
- rescue
- IdentityMap.remove(self) if IdentityMap.enabled?
- raise
end
# <tt>reload</tt> the record and clears changed attributes.
@@ -55,12 +50,10 @@ module ActiveRecord
# The attribute already has an unsaved change.
if attribute_changed?(attr)
old = @changed_attributes[attr]
- @changed_attributes.delete(attr) unless field_changed?(attr, old, value)
+ @changed_attributes.delete(attr) unless _field_changed?(attr, old, value)
else
old = clone_attribute_value(:read_attribute, attr)
- # Save Time objects as TimeWithZone if time_zone_aware_attributes == true
- old = old.in_time_zone if clone_with_time_zone_conversion_attribute?(attr, old)
- @changed_attributes[attr] = old if field_changed?(attr, old, value)
+ @changed_attributes[attr] = old if _field_changed?(attr, old, value)
end
# Carry on.
@@ -77,7 +70,7 @@ module ActiveRecord
end
end
- def field_changed?(attr, old, value)
+ def _field_changed?(attr, old, value)
if column = column_for_attribute(attr)
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
@@ -92,10 +85,6 @@ module ActiveRecord
old != value
end
-
- def clone_with_time_zone_conversion_attribute?(attr, old)
- old.class.name == "Time" && time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(attr.to_sym)
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index a404a5edd7..7b7811a706 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -1,3 +1,5 @@
+require 'set'
+
module ActiveRecord
module AttributeMethods
module PrimaryKey
@@ -5,15 +7,57 @@ module ActiveRecord
# Returns this record's primary key value wrapped in an Array if one is available
def to_key
- key = send(self.class.primary_key)
+ key = self.id
[key] if key
end
+ # Returns the primary key value
+ def id
+ read_attribute(self.class.primary_key)
+ end
+
+ # Sets the primary key value
+ def id=(value)
+ write_attribute(self.class.primary_key, value)
+ end
+
+ # Queries the primary key value
+ def id?
+ query_attribute(self.class.primary_key)
+ end
+
+ # Returns the primary key value before type cast
+ def id_before_type_cast
+ read_attribute_before_type_cast(self.class.primary_key)
+ end
+
+ protected
+
+ def attribute_method?(attr_name)
+ attr_name == 'id' || super
+ end
+
module ClassMethods
+ def define_method_attribute(attr_name)
+ super
+
+ if attr_name == primary_key && attr_name != 'id'
+ generated_attribute_methods.send(:alias_method, :id, primary_key)
+ end
+ end
+
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast).to_set
+
+ def dangerous_attribute_method?(method_name)
+ super && !ID_ATTRIBUTE_METHODS.include?(method_name)
+ end
+
# Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
- # primary_key_prefix_type setting, though.
+ # primary_key_prefix_type setting, though. Since primary keys are usually protected from mass assignment,
+ # remember to let your database generate them or include the key in +attr_accessible+.
def primary_key
- @primary_key ||= reset_primary_key
+ @primary_key = reset_primary_key unless defined? @primary_key
+ @primary_key
end
# Returns a quoted version of the primary key name, used to construct SQL statements.
@@ -22,11 +66,11 @@ module ActiveRecord
end
def reset_primary_key #:nodoc:
- key = self == base_class ? get_primary_key(base_class.name) :
- base_class.primary_key
-
- set_primary_key(key)
- key
+ if self == base_class
+ self.primary_key = get_primary_key(base_class.name)
+ else
+ self.primary_key = base_class.primary_key
+ end
end
def get_primary_key(base_name) #:nodoc:
@@ -38,35 +82,31 @@ module ActiveRecord
when :table_name_with_underscore
base_name.foreign_key
else
- if ActiveRecord::Base != self && connection.table_exists?(table_name)
- connection.primary_key(table_name)
+ if ActiveRecord::Base != self && table_exists?
+ connection.schema_cache.primary_keys[table_name]
else
'id'
end
end
end
- attr_accessor :original_primary_key
-
- # Attribute writer for the primary key column
- def primary_key=(value)
- @quoted_primary_key = nil
- @primary_key = value
- end
-
- # Sets the name of the primary key column to use to the given value,
- # or (if the value is nil or false) to the value returned by the given
- # block.
+ # Sets the name of the primary key column.
+ #
+ # class Project < ActiveRecord::Base
+ # self.primary_key = "sysid"
+ # end
+ #
+ # You can also define the primary_key method yourself:
#
# class Project < ActiveRecord::Base
- # set_primary_key "sysid"
+ # def self.primary_key
+ # "foo_" + super
+ # end
# end
- def set_primary_key(value = nil, &block)
+ # Project.primary_key # => "foo_id"
+ def primary_key=(value)
+ @primary_key = value && value.to_s
@quoted_primary_key = nil
- @primary_key ||= ''
- self.original_primary_key = @primary_key
- value &&= value.to_s
- self.primary_key = block_given? ? instance_eval(&block) : value
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb
index 948809c65a..1e841dc8e0 100644
--- a/activerecord/lib/active_record/attribute_methods/query.rb
+++ b/activerecord/lib/active_record/attribute_methods/query.rb
@@ -10,8 +10,11 @@ module ActiveRecord
end
def query_attribute(attr_name)
- unless value = read_attribute(attr_name)
- false
+ value = read_attribute(attr_name)
+
+ case value
+ when true then true
+ when false, nil then false
else
column = self.class.columns_hash[attr_name]
if column.nil?
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 4174e4da09..dcc3d79de9 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -6,11 +6,8 @@ module ActiveRecord
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
included do
- cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
+ config_attribute :attribute_types_cached_by_default, :global => true
self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
-
- # Undefine id so it can be used as an attribute name
- undef_method(:id) if method_defined?(:id)
end
module ClassMethods
@@ -33,112 +30,66 @@ module ActiveRecord
end
protected
- def define_method_attribute(attr_name)
- if serialized_attributes.include?(attr_name)
- define_read_method_for_serialized_attribute(attr_name)
- else
- define_read_method(attr_name, attr_name, columns_hash[attr_name])
- end
- if attr_name == primary_key && attr_name != "id"
- define_read_method('id', attr_name, columns_hash[attr_name])
+ # We want to generate the methods via module_eval rather than define_method,
+ # because define_method is slower on dispatch and uses more memory (because it
+ # creates a closure).
+ #
+ # But sometimes the database might return columns with characters that are not
+ # allowed in normal method names (like 'my_column(omg)'. So to work around this
+ # we first define with the __temp__ identifier, and then use alias method to
+ # rename it to what we want.
+ def define_method_attribute(attr_name)
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
+ def __temp__
+ read_attribute('#{attr_name}') { |n| missing_attribute(n, caller) }
end
- end
+ alias_method '#{attr_name}', :__temp__
+ undef_method :__temp__
+ STR
+ end
private
- def cacheable_column?(column)
- serialized_attributes.include?(column.name) || attribute_types_cached_by_default.include?(column.type)
- end
-
- # Define read method for serialized attribute.
- def define_read_method_for_serialized_attribute(attr_name)
- access_code = "@attributes_cache['#{attr_name}'] ||= @attributes['#{attr_name}']"
- generated_attribute_methods.module_eval("def _#{attr_name}; #{access_code}; end; alias #{attr_name} _#{attr_name}", __FILE__, __LINE__)
- end
-
- # Define an attribute reader method. Cope with nil 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}"
- unless attr_name.to_s == self.primary_key.to_s
- access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
- end
-
- if cache_attribute?(attr_name)
- access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
- end
-
- # 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 =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
- 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("_#{method_name}") { eval(access_code) }
- alias_method(method_name, "_#{method_name}")
- end
- end
+ def cacheable_column?(column)
+ if attribute_types_cached_by_default == ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
+ ! serialized_attributes.include? column.name
+ else
+ attribute_types_cached_by_default.include?(column.type)
end
+ end
end
# Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name)
- method = "_#{attr_name}"
- if respond_to? method
- send method if @attributes.has_key?(attr_name.to_s)
- else
- _read_attribute attr_name
- end
- end
+ # If it's cached, just return it
+ @attributes_cache.fetch(attr_name.to_s) { |name|
+ column = @columns_hash.fetch(name) {
+ return @attributes.fetch(name) {
+ if name == 'id' && self.class.primary_key != name
+ read_attribute(self.class.primary_key)
+ end
+ }
+ }
- def _read_attribute(attr_name)
- attr_name = attr_name.to_s
- attr_name = self.class.primary_key if attr_name == 'id'
- value = @attributes[attr_name]
- unless value.nil?
- if column = column_for_attribute(attr_name)
- if unserializable_attribute?(attr_name, column)
- unserialize_attribute(attr_name)
- else
- column.type_cast(value)
- end
+ value = @attributes.fetch(name) {
+ return block_given? ? yield(name) : nil
+ }
+
+ if self.class.cache_attribute?(name)
+ @attributes_cache[name] = column.type_cast(value)
else
- value
+ column.type_cast value
end
- end
- end
-
- # Returns true if the attribute is of a text column and marked for serialization.
- def unserializable_attribute?(attr_name, column)
- column.text? && self.class.serialized_attributes.include?(attr_name)
+ }
end
- # Returns the unserialized object of the attribute.
- def unserialize_attribute(attr_name)
- coder = self.class.serialized_attributes[attr_name]
- unserialized_object = coder.load(@attributes[attr_name])
+ private
- @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
+ def attribute(attribute_name)
+ read_attribute(attribute_name)
end
-
- private
- def attribute(attribute_name)
- read_attribute(attribute_name)
- end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
new file mode 100644
index 0000000000..165785c8fb
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -0,0 +1,115 @@
+module ActiveRecord
+ module AttributeMethods
+ module Serialization
+ extend ActiveSupport::Concern
+
+ included do
+ # Returns a hash of all the attributes that have been specified for serialization as
+ # keys and their class restriction as values.
+ config_attribute :serialized_attributes
+ self.serialized_attributes = {}
+ end
+
+ class Type # :nodoc:
+ def initialize(column)
+ @column = column
+ end
+
+ def type_cast(value)
+ value.unserialized_value
+ end
+
+ def type
+ @column.type
+ end
+ end
+
+ class Attribute < Struct.new(:coder, :value, :state)
+ def unserialized_value
+ state == :serialized ? unserialize : value
+ end
+
+ def serialized_value
+ state == :unserialized ? serialize : value
+ end
+
+ def unserialize
+ self.state = :unserialized
+ self.value = coder.load(value)
+ end
+
+ def serialize
+ self.state = :serialized
+ self.value = coder.dump(value)
+ end
+ end
+
+ module ClassMethods
+ # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
+ # then specify the name of that attribute using this method and it will be handled automatically.
+ # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
+ # class on retrieval or SerializationTypeMismatch will be raised.
+ #
+ # ==== Parameters
+ #
+ # * +attr_name+ - The field name that should be serialized.
+ # * +class_name+ - Optional, class name that the object type should be equal to.
+ #
+ # ==== Example
+ # # Serialize a preferences attribute
+ # class User < ActiveRecord::Base
+ # serialize :preferences
+ # end
+ def serialize(attr_name, class_name = Object)
+ coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
+ class_name
+ else
+ Coders::YAMLColumn.new(class_name)
+ end
+
+ # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
+ # has its own hash of own serialized attributes
+ self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
+ end
+
+ def initialize_attributes(attributes) #:nodoc:
+ super
+
+ serialized_attributes.each do |key, coder|
+ if attributes.key?(key)
+ attributes[key] = Attribute.new(coder, attributes[key], :serialized)
+ end
+ end
+
+ attributes
+ end
+
+ private
+
+ def attribute_cast_code(attr_name)
+ if serialized_attributes.include?(attr_name)
+ "v.unserialized_value"
+ else
+ super
+ end
+ end
+ end
+
+ def type_cast_attribute_for_write(column, value)
+ if column && coder = self.class.serialized_attributes[column.name]
+ Attribute.new(coder, value, :unserialized)
+ else
+ super
+ end
+ end
+
+ def read_attribute_before_type_cast(attr_name)
+ if serialized_attributes.include?(attr_name)
+ super.unserialized_value
+ else
+ super
+ end
+ end
+ end
+ end
+end
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 62a3cfa9a5..ac31b636db 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -4,63 +4,76 @@ require 'active_support/core_ext/object/inclusion'
module ActiveRecord
module AttributeMethods
module TimeZoneConversion
+ class Type # :nodoc:
+ def initialize(column)
+ @column = column
+ end
+
+ def type_cast(value)
+ value = @column.type_cast(value)
+ value.acts_like?(:time) ? value.in_time_zone : value
+ end
+
+ def type
+ @column.type
+ end
+ end
+
extend ActiveSupport::Concern
included do
- cattr_accessor :time_zone_aware_attributes, :instance_writer => false
+ config_attribute :time_zone_aware_attributes, :global => true
self.time_zone_aware_attributes = false
- class_attribute :skip_time_zone_conversion_for_attributes, :instance_writer => false
+ config_attribute :skip_time_zone_conversion_for_attributes
self.skip_time_zone_conversion_for_attributes = []
end
module ClassMethods
protected
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
- # This enhanced read method automatically converts the UTC time stored in the database to the time
- # zone stored in Time.zone.
- def define_method_attribute(attr_name)
- if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
- method_body, line = <<-EOV, __LINE__ + 1
- def _#{attr_name}
- cached = @attributes_cache['#{attr_name}']
- return cached if cached
- time = _read_attribute('#{attr_name}')
- @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
- end
- alias #{attr_name} _#{attr_name}
- EOV
- generated_attribute_methods.module_eval(method_body, __FILE__, line)
- else
- super
- end
+ # The enhanced read method automatically converts the UTC time stored in the database to the time
+ # zone stored in Time.zone.
+ def attribute_cast_code(attr_name)
+ column = columns_hash[attr_name]
+
+ if create_time_zone_conversion_attribute?(attr_name, column)
+ typecast = "v = #{super}"
+ time_zone_conversion = "v.acts_like?(:time) ? v.in_time_zone : v"
+
+ "((#{typecast}) && (#{time_zone_conversion}))"
+ else
+ super
end
+ end
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
- # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
- def define_method_attribute=(attr_name)
- if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
- method_body, line = <<-EOV, __LINE__ + 1
- def #{attr_name}=(original_time)
- time = original_time
- unless time.acts_like?(:time)
- time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
- end
- time = time.in_time_zone rescue nil if time
- write_attribute(:#{attr_name}, original_time)
- @attributes_cache["#{attr_name}"] = time
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
+ def define_method_attribute=(attr_name)
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
+ method_body, line = <<-EOV, __LINE__ + 1
+ def #{attr_name}=(original_time)
+ time = original_time
+ unless time.acts_like?(:time)
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
- EOV
- generated_attribute_methods.module_eval(method_body, __FILE__, line)
- else
- super
- end
+ time = time.in_time_zone rescue nil if time
+ write_attribute(:#{attr_name}, original_time)
+ #{attr_name}_will_change!
+ @attributes_cache["#{attr_name}"] = time
+ end
+ EOV
+ generated_attribute_methods.module_eval(method_body, __FILE__, line)
+ else
+ super
end
+ end
private
- def create_time_zone_conversion_attribute?(name, column)
- time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp])
- end
+ 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)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index e9cdb130db..50435921b1 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -10,17 +10,13 @@ module ActiveRecord
module ClassMethods
protected
def define_method_attribute=(attr_name)
- if attr_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
+ if attr_name =~ ActiveModel::AttributeMethods::NAME_COMPILABLE_REGEXP
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
else
generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
write_attribute(attr_name, new_value)
end
end
-
- if attr_name == primary_key && attr_name != "id"
- generated_attribute_methods.module_eval("alias :id= :'#{primary_key}='")
- end
end
end
@@ -32,10 +28,14 @@ module ActiveRecord
@attributes_cache.delete(attr_name)
column = column_for_attribute(attr_name)
- if column && column.number?
- @attributes[attr_name] = convert_number_column_value(value)
- elsif column || @attributes.has_key?(attr_name)
- @attributes[attr_name] = value
+ # If we're dealing with a binary column, write the data to the cache
+ # so we don't attempt to typecast multiple times.
+ if column && column.binary?
+ @attributes_cache[attr_name] = value
+ end
+
+ if column || @attributes.has_key?(attr_name)
+ @attributes[attr_name] = type_cast_attribute_for_write(column, value)
else
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
end
@@ -43,10 +43,16 @@ module ActiveRecord
alias_method :raw_write_attribute, :write_attribute
private
- # Handle *= for method_missing.
- def attribute=(attribute_name, value)
- write_attribute(attribute_name, value)
- end
+ # Handle *= for method_missing.
+ def attribute=(attribute_name, value)
+ write_attribute(attribute_name, value)
+ end
+
+ def type_cast_attribute_for_write(column, value)
+ return value unless column
+
+ column.type_cast_for_write value
+ end
end
end
end
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 056170d82a..d545e7799d 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/array/wrap'
-
module ActiveRecord
# = Active Record Autosave Association
#
@@ -21,6 +19,21 @@ module ActiveRecord
# Note that <tt>:autosave => false</tt> is not same as not declaring <tt>:autosave</tt>.
# When the <tt>:autosave</tt> option is not present new associations are saved.
#
+ # == Validation
+ #
+ # Children records are validated unless <tt>:validate</tt> is +false+.
+ #
+ # == Callbacks
+ #
+ # Association with autosave option defines several callbacks on your
+ # model (before_save, after_create, after_update). Please note that
+ # callbacks are executed in the order they were defined in
+ # model. You should avoid modifying the association content, before
+ # autosave callbacks are executed. Placing your callbacks after
+ # associations is usually a good practice.
+ #
+ # == Examples
+ #
# === One-to-one Example
#
# class Post
@@ -65,7 +78,7 @@ module ActiveRecord
# When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
#
# class Post
- # has_many :comments # :autosave option is no declared
+ # has_many :comments # :autosave option is not declared
# end
#
# post = Post.new(:title => 'ruby rocks')
@@ -80,7 +93,8 @@ module ActiveRecord
# post.comments.create(:body => 'hello world')
# post.save # => saves both post and comment
#
- # When <tt>:autosave</tt> is true all children is saved, no matter whether they are new records:
+ # When <tt>:autosave</tt> is true all children are saved, no matter whether they
+ # are new records or not:
#
# class Post
# has_many :comments, :autosave => true
@@ -109,10 +123,7 @@ module ActiveRecord
# Now it _is_ removed from the database:
#
# Comment.find_by_id(id).nil? # => true
- #
- # === Validation
- #
- # Children records are validated unless <tt>:validate</tt> is +false+.
+
module AutosaveAssociation
extend ActiveSupport::Concern
@@ -180,23 +191,21 @@ module ActiveRecord
# Doesn't use after_save as that would save associations added in after_create/after_update twice
after_create save_method
after_update save_method
+ elsif reflection.macro == :has_one
+ define_method(save_method) { save_has_one_association(reflection) }
+ # Configures two callbacks instead of a single after_save so that
+ # the model may rely on their execution order relative to its
+ # own callbacks.
+ #
+ # For example, given that after_creates run before after_saves, if
+ # we configured instead an after_save there would be no way to fire
+ # a custom after_create callback after the child association gets
+ # created.
+ after_create save_method
+ after_update save_method
else
- if reflection.macro == :has_one
- define_method(save_method) { save_has_one_association(reflection) }
- # Configures two callbacks instead of a single after_save so that
- # the model may rely on their execution order relative to its
- # own callbacks.
- #
- # For example, given that after_creates run before after_saves, if
- # we configured instead an after_save there would be no way to fire
- # a custom after_create callback after the child association gets
- # created.
- after_create save_method
- after_update save_method
- else
- define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
- before_save save_method
- end
+ define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
+ before_save save_method
end
end
@@ -264,7 +273,7 @@ module ActiveRecord
# turned on for the association.
def validate_single_association(reflection)
association = association_instance_get(reflection.name)
- record = association && association.target
+ record = association && association.reader
association_valid?(reflection, record) if record
end
@@ -285,7 +294,7 @@ module ActiveRecord
def association_valid?(reflection, record)
return true if record.destroyed? || record.marked_for_destruction?
- unless valid = record.valid?
+ unless valid = record.valid?(validation_context)
if reflection.options[:autosave]
record.errors.each do |attribute, message|
attribute = "#{reflection.name}.#{attribute}"
@@ -319,19 +328,19 @@ module ActiveRecord
autosave = reflection.options[:autosave]
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
- begin
+ records_to_destroy = []
records.each do |record|
next if record.destroyed?
saved = true
if autosave && record.marked_for_destruction?
- association.proxy.destroy(record)
+ records_to_destroy << record
elsif autosave != false && (@new_record_before_save || record.new_record?)
if autosave
saved = association.insert_record(record, false)
else
- association.insert_record(record)
+ association.insert_record(record) unless reflection.nested?
end
elsif autosave
saved = record.save(:validate => false)
@@ -339,11 +348,10 @@ module ActiveRecord
raise ActiveRecord::Rollback unless saved
end
- rescue
- records.each {|x| IdentityMap.remove(x) } if IdentityMap.enabled?
- raise
- end
+ records_to_destroy.each do |record|
+ association.destroy(record)
+ end
end
# reconstruct the scope now that we know the owner's id
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 360e494af1..189985b671 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1,8 +1,3 @@
-begin
- require 'psych'
-rescue LoadError
-end
-
require 'yaml'
require 'set'
require 'active_support/benchmarkable'
@@ -12,7 +7,6 @@ require 'active_support/time'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/delegating_attributes'
-require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
require 'active_support/core_ext/hash/indifferent_access'
@@ -23,9 +17,11 @@ require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/blank'
+require 'active_support/deprecation'
require 'arel'
require 'active_record/errors'
require 'active_record/log_subscriber'
+require 'active_record/explain_subscriber'
module ActiveRecord #:nodoc:
# = Active Record
@@ -205,6 +201,9 @@ module ActiveRecord #:nodoc:
# # Now 'Bob' exist and is an 'admin'
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
#
+ # Adding an exclamation point (!) on to the end of <tt>find_or_create_by_</tt> will
+ # raise an <tt>ActiveRecord::RecordInvalid</tt> error if the new record is invalid.
+ #
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without
# saving it first. Protected attributes won't be set unless they are given in a block.
#
@@ -305,1862 +304,29 @@ module ActiveRecord #:nodoc:
# (or a bad spelling of an existing one).
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
# specified in the association definition.
- # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
- # * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt>
+ # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
+ # <tt>attributes=</tt> method.
+ # You can inspect the +attribute+ property of the exception object to determine which attribute
+ # triggered the error.
+ # * ConnectionNotEstablished - No connection has been established. Use <tt>establish_connection</tt>
# before querying.
- # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
- # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
- # nothing was found, please check its documentation for further details.
- # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
# AttributeAssignmentError
# objects that should be inspected to determine which attributes triggered the errors.
- # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
- # <tt>attributes=</tt> method.
- # You can inspect the +attribute+ property of the exception object to determine which attribute
- # triggered the error.
+ # * RecordInvalid - raised by save! and create! when the record is invalid.
+ # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
+ # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
+ # nothing was found, please check its documentation for further details.
+ # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
+ # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
#
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
# instances in the current object space.
class Base
- ##
- # :singleton-method:
- # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
- # which is then passed on to any new database connections made and which can be retrieved on both
- # a class and instance level by calling +logger+.
- cattr_accessor :logger, :instance_writer => false
-
- ##
- # :singleton-method:
- # Contains the database configuration - as is typically stored in config/database.yml -
- # as a Hash.
- #
- # For example, the following database.yml...
- #
- # development:
- # adapter: sqlite3
- # database: db/development.sqlite3
- #
- # production:
- # adapter: sqlite3
- # database: db/production.sqlite3
- #
- # ...would result in ActiveRecord::Base.configurations to look like this:
- #
- # {
- # 'development' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/development.sqlite3'
- # },
- # 'production' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/production.sqlite3'
- # }
- # }
- cattr_accessor :configurations, :instance_writer => false
- @@configurations = {}
-
- ##
- # :singleton-method:
- # Accessor for the prefix type that will be prepended to every primary key column name.
- # The options are :table_name and :table_name_with_underscore. If the first is specified,
- # the Product class will look for "productid" instead of "id" as the primary column. If the
- # latter is specified, the Product class will look for "product_id" instead of "id". Remember
- # that this is a global setting for all Active Records.
- cattr_accessor :primary_key_prefix_type, :instance_writer => false
- @@primary_key_prefix_type = nil
-
- ##
- # :singleton-method:
- # Accessor for the name of the prefix string to prepend to every table name. So if set
- # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
- # etc. This is a convenient way of creating a namespace for tables in a shared database.
- # By default, the prefix is the empty string.
- #
- # If you are organising your models within modules you can add a prefix to the models within
- # a namespace by defining a singleton method in the parent module called table_name_prefix which
- # returns your chosen prefix.
- class_attribute :table_name_prefix, :instance_writer => false
- self.table_name_prefix = ""
-
- ##
- # :singleton-method:
- # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
- # "people_basecamp"). By default, the suffix is the empty string.
- class_attribute :table_name_suffix, :instance_writer => false
- self.table_name_suffix = ""
-
- ##
- # :singleton-method:
- # Indicates whether table names should be the pluralized versions of the corresponding class names.
- # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
- # See table_name for the full rules on table/class naming. This is true, by default.
- class_attribute :pluralize_table_names, :instance_writer => false
- self.pluralize_table_names = true
-
- ##
- # :singleton-method:
- # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
- # dates and times from the database. This is set to :local by default.
- cattr_accessor :default_timezone, :instance_writer => false
- @@default_timezone = :local
-
- ##
- # :singleton-method:
- # Specifies the format to use when dumping the database schema with Rails'
- # Rakefile. If :sql, the schema is dumped as (potentially database-
- # specific) SQL statements. If :ruby, the schema is dumped as an
- # ActiveRecord::Schema file which can be loaded into any database that
- # supports migrations. Use :ruby if you want to have different database
- # adapters for, e.g., your development and test environments.
- cattr_accessor :schema_format , :instance_writer => false
- @@schema_format = :ruby
-
- ##
- # :singleton-method:
- # Specify whether or not to use timestamps for migration versions
- cattr_accessor :timestamped_migrations , :instance_writer => false
- @@timestamped_migrations = true
-
- # Determine whether to store the full constant name including namespace when using STI
- class_attribute :store_full_sti_class
- self.store_full_sti_class = true
-
- # Stores the default scope for the class
- 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.
- class_attribute :serialized_attributes
- self.serialized_attributes = {}
-
- class_attribute :_attr_readonly, :instance_writer => false
- self._attr_readonly = []
-
- class << self # Class methods
- delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
- delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped
- delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
- delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
- delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
-
- # Executes a custom SQL query against your database and returns all the results. The results will
- # be returned as an array with columns requested encapsulated as attributes of the model you call
- # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
- # a Product object with the attributes you specified in the SQL query.
- #
- # If you call a complicated SQL query which spans multiple tables the columns specified by the
- # SELECT will be attributes of the model, whether or not they are columns of the corresponding
- # table.
- #
- # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
- # no database agnostic conversions performed. This should be a last resort because using, for example,
- # MySQL specific terms will lock you to using that particular database engine or require you to
- # change your call if you switch engines.
- #
- # ==== Examples
- # # A simple SQL query spanning multiple tables
- # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
- # > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
- #
- # # You can use the same string replacement techniques as you can with ActiveRecord#find
- # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
- # > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
- def find_by_sql(sql, binds = [])
- connection.select_all(sanitize_sql(sql), "#{name} Load", binds).collect! { |record| instantiate(record) }
- end
-
- # Creates an object (or multiple objects) and saves it to the database, if validations pass.
- # The resulting object is returned whether the object was saved successfully to the database or not.
- #
- # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
- # attributes on the objects that are to be created.
- #
- # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
- # in the +options+ parameter.
- #
- # ==== Examples
- # # Create a single new object
- # User.create(:first_name => 'Jamie')
- #
- # # Create a single new object using the :admin mass-assignment security role
- # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
- #
- # # Create a single new object bypassing mass-assignment security
- # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
- #
- # # Create an Array of new objects
- # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
- #
- # # Create a single object and pass it into a block to set other attributes.
- # User.create(:first_name => 'Jamie') do |u|
- # u.is_admin = false
- # end
- #
- # # Creating an Array of new objects using a block, where the block is executed for each object:
- # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
- # u.is_admin = false
- # end
- def create(attributes = nil, options = {}, &block)
- if attributes.is_a?(Array)
- attributes.collect { |attr| create(attr, options, &block) }
- else
- object = new(attributes, options, &block)
- object.save
- object
- end
- end
-
- # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
- # The use of this method should be restricted to complicated SQL queries that can't be executed
- # using the ActiveRecord::Calculations class methods. Look into those before using this.
- #
- # ==== Parameters
- #
- # * +sql+ - An SQL statement which should return a count query from the database, see the example below.
- #
- # ==== Examples
- #
- # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
- def count_by_sql(sql)
- sql = sanitize_conditions(sql)
- connection.select_value(sql, "#{name} Count").to_i
- end
-
- # Attributes listed as readonly will be used to create a new record but update operations will
- # ignore these fields.
- def attr_readonly(*attributes)
- self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
- end
-
- # Returns an array of all the attributes that have been specified as readonly.
- def readonly_attributes
- self._attr_readonly
- end
-
- # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
- # then specify the name of that attribute using this method and it will be handled automatically.
- # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
- # class on retrieval or SerializationTypeMismatch will be raised.
- #
- # ==== Parameters
- #
- # * +attr_name+ - The field name that should be serialized.
- # * +class_name+ - Optional, class name that the object type should be equal to.
- #
- # ==== Example
- # # Serialize a preferences attribute
- # class User < ActiveRecord::Base
- # serialize :preferences
- # end
- def serialize(attr_name, class_name = Object)
- coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) }
- class_name
- else
- Coders::YAMLColumn.new(class_name)
- end
-
- # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy
- # has its own hash of own serialized attributes
- self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder)
- end
-
- # Guesses the table name (in forced lower-case) based on the name of the class in the
- # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
- # looks like: Reply < Message < ActiveRecord::Base, then Message is used
- # to guess the table name even when called on Reply. The rules used to do the guess
- # are handled by the Inflector class in Active Support, which knows almost all common
- # English inflections. You can add new inflections in config/initializers/inflections.rb.
- #
- # Nested classes are given table names prefixed by the singular form of
- # the parent's table name. Enclosing modules are not considered.
- #
- # ==== Examples
- #
- # class Invoice < ActiveRecord::Base
- # end
- #
- # file class table_name
- # invoice.rb Invoice invoices
- #
- # class Invoice < ActiveRecord::Base
- # class Lineitem < ActiveRecord::Base
- # end
- # end
- #
- # file class table_name
- # invoice.rb Invoice::Lineitem invoice_lineitems
- #
- # module Invoice
- # class Lineitem < ActiveRecord::Base
- # end
- # end
- #
- # file class table_name
- # invoice/lineitem.rb Invoice::Lineitem lineitems
- #
- # Additionally, the class-level +table_name_prefix+ is prepended and the
- # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
- # the table name guess for an Invoice class becomes "myapp_invoices".
- # Invoice::Lineitem becomes "myapp_invoice_lineitems".
- #
- # You can also overwrite this class method to allow for unguessable
- # links, such as a Mouse class with a link to a "mice" table. Example:
- #
- # class Mouse < ActiveRecord::Base
- # set_table_name "mice"
- # end
- def table_name
- reset_table_name
- end
-
- # Returns a quoted version of the table name, used to construct SQL statements.
- def quoted_table_name
- @quoted_table_name ||= connection.quote_table_name(table_name)
- end
-
- # Computes the table name, (re)sets it internally, and returns it.
- def reset_table_name #:nodoc:
- return if abstract_class?
-
- self.table_name = compute_table_name
- end
-
- def full_table_name_prefix #:nodoc:
- (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
- end
-
- # Defines the column name for use with single table inheritance. Use
- # <tt>set_inheritance_column</tt> to set a different value.
- def inheritance_column
- @inheritance_column ||= "type"
- end
-
- # Lazy-set the sequence name to the connection's default. This method
- # is only ever called once since set_sequence_name overrides it.
- def sequence_name #:nodoc:
- reset_sequence_name
- end
-
- def reset_sequence_name #:nodoc:
- default = connection.default_sequence_name(table_name, primary_key)
- set_sequence_name(default)
- default
- end
-
- # Sets the table name. If the value is nil or false then the value returned by the given
- # block is used.
- #
- # class Project < ActiveRecord::Base
- # set_table_name "project"
- # end
- def set_table_name(value = nil, &block)
- @quoted_table_name = nil
- define_attr_method :table_name, value, &block
- @arel_table = nil
-
- @relation = Relation.new(self, arel_table)
- end
- alias :table_name= :set_table_name
-
- # Sets the name of the inheritance column to use to the given value,
- # or (if the value # is nil or false) to the value returned by the
- # given block.
- #
- # class Project < ActiveRecord::Base
- # set_inheritance_column do
- # original_inheritance_column + "_id"
- # end
- # end
- def set_inheritance_column(value = nil, &block)
- define_attr_method :inheritance_column, value, &block
- end
- alias :inheritance_column= :set_inheritance_column
-
- # Sets the name of the sequence to use when generating ids to the given
- # value, or (if the value is nil or false) to the value returned by the
- # given block. This is required for Oracle and is useful for any
- # database which relies on sequences for primary key generation.
- #
- # If a sequence name is not explicitly set when using Oracle or Firebird,
- # it will default to the commonly used pattern of: #{table_name}_seq
- #
- # If a sequence name is not explicitly set when using PostgreSQL, it
- # will discover the sequence corresponding to your primary key for you.
- #
- # class Project < ActiveRecord::Base
- # set_sequence_name "projectseq" # default would have been "project_seq"
- # end
- def set_sequence_name(value = nil, &block)
- define_attr_method :sequence_name, value, &block
- end
- alias :sequence_name= :set_sequence_name
-
- # Indicates whether the table associated with this class exists
- def table_exists?
- connection.table_exists?(table_name)
- end
-
- # Returns an array of column objects for the table associated with this class.
- def columns
- if defined?(@primary_key)
- connection_pool.primary_keys[table_name] ||= primary_key
- end
-
- connection_pool.columns[table_name]
- end
-
- # Returns a hash of column objects for the table associated with this class.
- def columns_hash
- connection_pool.columns_hash[table_name]
- end
-
- # Returns a hash where the keys are column names and the values are
- # default values when instantiating the AR object for this table.
- def column_defaults
- connection_pool.column_defaults[table_name]
- end
-
- # Returns an array of column names as strings.
- def column_names
- @column_names ||= columns.map { |column| column.name }
- end
-
- # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
- # and columns used for single table inheritance have been removed.
- def content_columns
- @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
- end
-
- # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
- # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
- # is available.
- def column_methods_hash #:nodoc:
- @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
- attr_name = attr.to_s
- methods[attr.to_sym] = attr_name
- methods["#{attr}=".to_sym] = attr_name
- methods["#{attr}?".to_sym] = attr_name
- methods["#{attr}_before_type_cast".to_sym] = attr_name
- methods
- end
- end
-
- # Resets all the cached information about columns, which will cause them
- # to be reloaded on the next request.
- #
- # The most common usage pattern for this method is probably in a migration,
- # when just after creating a table you want to populate it with some default
- # values, eg:
- #
- # class CreateJobLevels < ActiveRecord::Migration
- # def self.up
- # create_table :job_levels do |t|
- # t.integer :id
- # t.string :name
- #
- # t.timestamps
- # end
- #
- # JobLevel.reset_column_information
- # %w{assistant executive manager director}.each do |type|
- # JobLevel.create(:name => type)
- # end
- # end
- #
- # def self.down
- # drop_table :job_levels
- # end
- # end
- def reset_column_information
- connection.clear_cache!
- undefine_attribute_methods
- connection_pool.clear_table_cache!(table_name) if table_exists?
-
- @column_names = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
- @arel_engine = @relation = nil
- end
-
- def clear_cache! # :nodoc:
- connection_pool.clear_cache!
- end
-
- def attribute_method?(attribute)
- super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, '')))
- end
-
- # Returns an array of column names as strings if it's not
- # an abstract class and table exists.
- # Otherwise it returns an empty array.
- def attribute_names
- @attribute_names ||= if !abstract_class? && table_exists?
- column_names
- else
- []
- end
- end
-
- # Set the lookup ancestors for ActiveModel.
- def lookup_ancestors #:nodoc:
- klass = self
- classes = [klass]
- return classes if klass == ActiveRecord::Base
-
- while klass != klass.base_class
- classes << klass = klass.superclass
- end
- classes
- end
-
- # Set the i18n scope to overwrite ActiveModel.
- def i18n_scope #:nodoc:
- :activerecord
- end
-
- # True if this isn't a concrete subclass needing a STI type condition.
- def descends_from_active_record?
- if superclass.abstract_class?
- superclass.descends_from_active_record?
- else
- superclass == Base || !columns_hash.include?(inheritance_column)
- end
- end
-
- def finder_needs_type_condition? #:nodoc:
- # This is like this because benchmarking justifies the strange :false stuff
- :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
- end
-
- # Returns a string like 'Post(id:integer, title:string, body:text)'
- def inspect
- if self == Base
- super
- elsif abstract_class?
- "#{super}(abstract)"
- elsif table_exists?
- attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
- "#{super}(#{attr_list})"
- else
- "#{super}(Table doesn't exist)"
- end
- end
-
- def quote_value(value, column = nil) #:nodoc:
- connection.quote(value,column)
- end
-
- # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
- def sanitize(object) #:nodoc:
- connection.quote(object)
- end
-
- # Overwrite the default class equality method to provide support for association proxies.
- def ===(object)
- object.is_a?(self)
- end
-
- def symbolized_base_class
- @symbolized_base_class ||= base_class.to_s.to_sym
- end
-
- def symbolized_sti_name
- @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
- end
-
- # Returns the base AR subclass that this class descends from. If A
- # extends AR::Base, A.base_class will return A. If B descends from A
- # through some arbitrarily deep hierarchy, B.base_class will return A.
- #
- # If B < A and C < B and if A is an abstract_class then both B.base_class
- # and C.base_class would return B as the answer since A is an abstract_class.
- def base_class
- class_of_active_record_descendant(self)
- end
-
- # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
- attr_accessor :abstract_class
-
- # Returns whether this class is an abstract class or not.
- def abstract_class?
- defined?(@abstract_class) && @abstract_class == true
- end
-
- def respond_to?(method_id, include_private = false)
- if match = DynamicFinderMatch.match(method_id)
- return true if all_attributes_exists?(match.attribute_names)
- elsif match = DynamicScopeMatch.match(method_id)
- return true if all_attributes_exists?(match.attribute_names)
- end
-
- super
- end
-
- def sti_name
- store_full_sti_class ? name : name.demodulize
- end
-
- def arel_table
- @arel_table ||= Arel::Table.new(table_name, arel_engine)
- end
-
- def arel_engine
- @arel_engine ||= begin
- if self == ActiveRecord::Base
- ActiveRecord::Base
- else
- connection_handler.connection_pools[name] ? self : superclass.arel_engine
- end
- end
- end
-
- # Returns a scope for this class without taking into account the default_scope.
- #
- # class Post < ActiveRecord::Base
- # def self.default_scope
- # where :published => true
- # end
- # end
- #
- # Post.all # Fires "SELECT * FROM posts WHERE published = true"
- # Post.unscoped.all # Fires "SELECT * FROM posts"
- #
- # This method also accepts a block meaning that all queries inside the block will
- # not use the default_scope:
- #
- # Post.unscoped {
- # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
- # }
- #
- # It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
- # does not work. Assuming that <tt>published</tt> is a <tt>scope</tt> following two statements are same.
- #
- # Post.unscoped.published
- # Post.published
- def unscoped #:nodoc:
- block_given? ? relation.scoping { yield } : relation
- end
-
- def before_remove_const #:nodoc:
- self.current_scope = nil
- end
-
- # Finder methods must instantiate through this method to work with the
- # single-table inheritance model that makes it possible to create
- # objects of different types from the same table.
- def instantiate(record)
- sti_class = find_sti_class(record[inheritance_column])
- record_id = sti_class.primary_key && record[sti_class.primary_key]
-
- if ActiveRecord::IdentityMap.enabled? && record_id
- if (column = sti_class.columns_hash[sti_class.primary_key]) && column.number?
- record_id = record_id.to_i
- end
- if instance = IdentityMap.get(sti_class, record_id)
- instance.reinit_with('attributes' => record)
- else
- instance = sti_class.allocate.init_with('attributes' => record)
- IdentityMap.add(instance)
- end
- else
- instance = sti_class.allocate.init_with('attributes' => record)
- end
-
- instance
- end
-
- private
-
- def relation #:nodoc:
- @relation ||= Relation.new(self, arel_table)
-
- if finder_needs_type_condition?
- @relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
- else
- @relation
- end
- end
-
- def find_sti_class(type_name)
- if type_name.blank? || !columns_hash.include?(inheritance_column)
- self
- else
- begin
- if store_full_sti_class
- ActiveSupport::Dependencies.constantize(type_name)
- else
- compute_type(type_name)
- end
- rescue NameError
- raise SubclassNotFound,
- "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
- "or overwrite #{name}.inheritance_column to use another column for that information."
- end
- end
- end
-
- def construct_finder_arel(options = {}, scope = nil)
- relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : options
- relation = scope.merge(relation) if scope
- relation
- end
-
- def type_condition(table = arel_table)
- sti_column = table[inheritance_column.to_sym]
- sti_names = ([self] + descendants).map { |model| model.sti_name }
-
- sti_column.in(sti_names)
- end
-
- # Guesses the table name, but does not decorate it with prefix and suffix information.
- def undecorated_table_name(class_name = base_class.name)
- table_name = class_name.to_s.demodulize.underscore
- table_name = table_name.pluralize if pluralize_table_names
- table_name
- end
-
- # Computes and returns a table name according to default conventions.
- def compute_table_name
- base = base_class
- if self == base
- # Nested classes are prefixed with singular parent table name.
- if parent < ActiveRecord::Base && !parent.abstract_class?
- contained = parent.table_name
- contained = contained.singularize if parent.pluralize_table_names
- contained += '_'
- end
- "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
- else
- # STI subclasses always use their superclass' table.
- base.table_name
- end
- end
-
- # Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
- # <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
- # section at the top of this file for more detailed information.
- #
- # It's even possible to use all the additional parameters to +find+. For example, the
- # full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
- #
- # Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
- # is first invoked, so that future attempts to use it do not run through method_missing.
- def method_missing(method_id, *arguments, &block)
- if match = (DynamicFinderMatch.match(method_id) || DynamicScopeMatch.match(method_id))
- attribute_names = match.attribute_names
- super unless all_attributes_exists?(attribute_names)
- if arguments.size < attribute_names.size
- method_trace = "#{__FILE__}:#{__LINE__}:in `#{method_id}'"
- backtrace = [method_trace] + caller
- raise ArgumentError, "wrong number of arguments (#{arguments.size} for #{attribute_names.size})", backtrace
- end
- if match.respond_to?(:scope?) && match.scope?
- self.class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
- attributes = Hash[[:#{attribute_names.join(',:')}].zip(args)] # attributes = Hash[[:user_name, :password].zip(args)]
- #
- scoped(:conditions => attributes) # scoped(:conditions => attributes)
- end # end
- METHOD
- send(method_id, *arguments)
- elsif match.finder?
- options = arguments.extract_options!
- relation = options.any? ? scoped(options) : scoped
- relation.send :find_by_attributes, match, attribute_names, *arguments, &block
- elsif match.instantiator?
- scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block
- end
- else
- super
- end
- end
-
- # Similar in purpose to +expand_hash_conditions_for_aggregates+.
- def expand_attribute_names_for_aggregates(attribute_names)
- attribute_names.map { |attribute_name|
- unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
- aggregate_mapping(aggregation).map do |field_attr, _|
- field_attr.to_sym
- end
- else
- attribute_name.to_sym
- end
- }.flatten
- end
-
- def all_attributes_exists?(attribute_names)
- (expand_attribute_names_for_aggregates(attribute_names) -
- column_methods_hash.keys).empty?
- end
-
- protected
- # with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
- # <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
- # <tt>:create</tt> parameters are an attributes hash.
- #
- # class Article < ActiveRecord::Base
- # def self.create_with_scope
- # with_scope(:find => where(:blog_id => 1), :create => { :blog_id => 1 }) do
- # find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
- # a = create(1)
- # a.blog_id # => 1
- # end
- # end
- # end
- #
- # In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
- # <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
- #
- # <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
- # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
- # array of strings format for your joins.
- #
- # class Article < ActiveRecord::Base
- # def self.find_with_scope
- # with_scope(:find => where(:blog_id => 1).limit(1), :create => { :blog_id => 1 }) do
- # with_scope(:find => limit(10)) do
- # all # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
- # end
- # with_scope(:find => where(:author_id => 3)) do
- # all # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
- # end
- # end
- # end
- # end
- #
- # You can ignore any previous scopings by using the <tt>with_exclusive_scope</tt> method.
- #
- # class Article < ActiveRecord::Base
- # def self.find_with_exclusive_scope
- # with_scope(:find => where(:blog_id => 1).limit(1)) do
- # with_exclusive_scope(:find => limit(10)) do
- # all # => SELECT * from articles LIMIT 10
- # end
- # end
- # end
- # end
- #
- # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
- 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 scope.is_a?(Hash)
- # Dup first and second level of hash (method and params).
- scope = scope.dup
- scope.each do |method, params|
- scope[method] = params.dup unless params == true
- end
-
- scope.assert_valid_keys([ :find, :create ])
- relation = construct_finder_arel(scope[:find] || {})
- relation.default_scoped = true unless action == :overwrite
-
- if previous_scope && previous_scope.create_with_value && scope[:create]
- scope_for_create = if action == :merge
- previous_scope.create_with_value.merge(scope[:create])
- else
- scope[:create]
- end
-
- relation = relation.create_with(scope_for_create)
- else
- 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
-
- scope = relation
- end
-
- scope = previous_scope.merge(scope) if previous_scope && action == :merge
-
- self.current_scope = scope
- begin
- yield
- ensure
- self.current_scope = previous_scope
- end
- end
-
- # Works like with_scope, but discards any nested properties.
- def with_exclusive_scope(method_scoping = {}, &block)
- if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
- raise ArgumentError, <<-MSG
-New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
-
- User.unscoped.where(:active => true)
-
-Or call unscoped with a block:
-
- User.unscoped do
- User.where(:active => true).all
- end
-
-MSG
- end
- with_scope(method_scoping, :overwrite, &block)
- end
-
- def current_scope #:nodoc:
- Thread.current["#{self}_current_scope"]
- end
-
- def current_scope=(scope) #:nodoc:
- Thread.current["#{self}_current_scope"] = scope
- end
-
- # Use this macro in your model to set a default scope for all operations on
- # the model.
- #
- # class Article < ActiveRecord::Base
- # default_scope where(:published => true)
- # end
- #
- # Article.all # => SELECT * FROM articles WHERE published = true
- #
- # The <tt>default_scope</tt> is also applied while creating/building a record. It is not
- # applied while updating a record.
- #
- # Article.new.published # => true
- # Article.create.published # => true
- #
- # You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
- #
- # class Article < ActiveRecord::Base
- # default_scope { where(:published_at => Time.now - 1.week) }
- # end
- #
- # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
- # macro, and it will be called when building the default scope.)
- #
- # If you use multiple <tt>default_scope</tt> declarations in your model then they will
- # be merged together:
- #
- # class Article < ActiveRecord::Base
- # default_scope where(:published => true)
- # default_scope where(:rating => 'G')
- # end
- #
- # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
- #
- # This is also the case with inheritance and module includes where the parent or module
- # defines a <tt>default_scope</tt> and the child or including class defines a second one.
- #
- # If you need to do more complex things with a default scope, you can alternatively
- # define it as a class method:
- #
- # class Article < ActiveRecord::Base
- # def self.default_scope
- # # Should return a scope, you can call 'super' here etc.
- # end
- # end
- def default_scope(scope = {})
- scope = Proc.new if block_given?
- self.default_scopes = default_scopes + [scope]
- end
-
- def build_default_scope #:nodoc:
- if method(:default_scope).owner != Base.singleton_class
- evaluate_default_scope { default_scope }
- elsif default_scopes.any?
- evaluate_default_scope do
- default_scopes.inject(relation) do |default_scope, scope|
- if scope.is_a?(Hash)
- default_scope.apply_finder_options(scope)
- elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
- default_scope.merge(scope.call)
- else
- default_scope.merge(scope)
- end
- end
- end
- end
- end
-
- def ignore_default_scope? #:nodoc:
- Thread.current["#{self}_ignore_default_scope"]
- end
-
- def ignore_default_scope=(ignore) #:nodoc:
- Thread.current["#{self}_ignore_default_scope"] = ignore
- end
-
- # The ignore_default_scope flag is used to prevent an infinite recursion situation where
- # a default scope references a scope which has a default scope which references a scope...
- def evaluate_default_scope
- return if ignore_default_scope?
-
- begin
- self.ignore_default_scope = true
- yield
- ensure
- self.ignore_default_scope = false
- end
- end
-
- # Returns the class type of the record using the current module as a prefix. So descendants of
- # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
- def compute_type(type_name)
- if type_name.match(/^::/)
- # If the type is prefixed with a scope operator then we assume that
- # the type_name is an absolute reference.
- ActiveSupport::Dependencies.constantize(type_name)
- else
- # Build a list of candidates to search for
- candidates = []
- name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
- candidates << type_name
-
- candidates.each do |candidate|
- begin
- constant = ActiveSupport::Dependencies.constantize(candidate)
- return constant if candidate == constant.to_s
- rescue NameError => e
- # We don't want to swallow NoMethodError < NameError errors
- raise e unless e.instance_of?(NameError)
- end
- end
-
- raise NameError, "uninitialized constant #{candidates.first}"
- end
- end
-
- # Returns the class descending directly from ActiveRecord::Base or an
- # abstract class, if any, in the inheritance hierarchy.
- def class_of_active_record_descendant(klass)
- if klass == Base || klass.superclass == Base || klass.superclass.abstract_class?
- klass
- elsif klass.superclass.nil?
- raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
- else
- class_of_active_record_descendant(klass.superclass)
- end
- end
-
- # Accepts an array, hash, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a WHERE clause.
- # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
- # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
- # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
- def sanitize_sql_for_conditions(condition, table_name = self.table_name)
- return nil if condition.blank?
-
- case condition
- when Array; sanitize_sql_array(condition)
- when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
- else condition
- end
- end
- alias_method :sanitize_sql, :sanitize_sql_for_conditions
-
- # Accepts an array, hash, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a SET clause.
- # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
- def sanitize_sql_for_assignment(assignments)
- case assignments
- when Array; sanitize_sql_array(assignments)
- when Hash; sanitize_sql_hash_for_assignment(assignments)
- else assignments
- end
- end
-
- def aggregate_mapping(reflection)
- mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
- mapping.first.is_a?(Array) ? mapping : [mapping]
- end
-
- # Accepts a hash of SQL conditions and replaces those attributes
- # that correspond to a +composed_of+ relationship with their expanded
- # aggregate attribute values.
- # Given:
- # class Person < ActiveRecord::Base
- # composed_of :address, :class_name => "Address",
- # :mapping => [%w(address_street street), %w(address_city city)]
- # end
- # Then:
- # { :address => Address.new("813 abc st.", "chicago") }
- # # => { :address_street => "813 abc st.", :address_city => "chicago" }
- def expand_hash_conditions_for_aggregates(attrs)
- expanded_attrs = {}
- attrs.each do |attr, value|
- unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
- mapping = aggregate_mapping(aggregation)
- mapping.each do |field_attr, aggregate_attr|
- if mapping.size == 1 && !value.respond_to?(aggregate_attr)
- expanded_attrs[field_attr] = value
- else
- expanded_attrs[field_attr] = value.send(aggregate_attr)
- end
- end
- else
- expanded_attrs[attr] = value
- end
- end
- expanded_attrs
- end
-
- # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
- # { :name => "foo'bar", :group_id => 4 }
- # # => "name='foo''bar' and group_id= 4"
- # { :status => nil, :group_id => [1,2,3] }
- # # => "status IS NULL and group_id IN (1,2,3)"
- # { :age => 13..18 }
- # # => "age BETWEEN 13 AND 18"
- # { 'other_records.id' => 7 }
- # # => "`other_records`.`id` = 7"
- # { :other_records => { :id => 7 } }
- # # => "`other_records`.`id` = 7"
- # And for value objects on a composed_of relationship:
- # { :address => Address.new("123 abc st.", "chicago") }
- # # => "address_street='123 abc st.' and address_city='chicago'"
- def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
- attrs = expand_hash_conditions_for_aggregates(attrs)
-
- table = Arel::Table.new(table_name).alias(default_table_name)
- PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
- connection.visitor.accept b
- }.join(' AND ')
- end
- alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
-
- # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
- # { :status => nil, :group_id => 1 }
- # # => "status = NULL , group_id = 1"
- def sanitize_sql_hash_for_assignment(attrs)
- attrs.map do |attr, value|
- "#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
- end.join(', ')
- end
-
- # Accepts an array of conditions. The array has each value
- # sanitized and interpolated into the SQL statement.
- # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
- def sanitize_sql_array(ary)
- statement, *values = ary
- if values.first.is_a?(Hash) && statement =~ /:\w+/
- replace_named_bind_variables(statement, values.first)
- elsif statement.include?('?')
- replace_bind_variables(statement, values)
- elsif statement.blank?
- statement
- else
- statement % values.collect { |value| connection.quote_string(value.to_s) }
- end
- end
-
- alias_method :sanitize_conditions, :sanitize_sql
-
- def replace_bind_variables(statement, values) #:nodoc:
- raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
- bound = values.dup
- c = connection
- statement.gsub('?') { quote_bound_value(bound.shift, c) }
- end
-
- def replace_named_bind_variables(statement, bind_vars) #:nodoc:
- statement.gsub(/(:?):([a-zA-Z]\w*)/) do
- if $1 == ':' # skip postgresql casts
- $& # return the whole match
- elsif bind_vars.include?(match = $2.to_sym)
- quote_bound_value(bind_vars[match])
- else
- raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
- end
- end
- end
-
- def expand_range_bind_variables(bind_vars) #:nodoc:
- expanded = []
-
- bind_vars.each do |var|
- next if var.is_a?(Hash)
-
- if var.is_a?(Range)
- expanded << var.first
- expanded << var.last
- else
- expanded << var
- end
- end
-
- expanded
- end
-
- def quote_bound_value(value, c = connection) #:nodoc:
- if value.respond_to?(:map) && !value.acts_like?(:string)
- if value.respond_to?(:empty?) && value.empty?
- c.quote(nil)
- else
- value.map { |v| c.quote(v) }.join(',')
- end
- else
- c.quote(value)
- end
- end
-
- def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
- unless expected == provided
- raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
- end
- end
-
- def encode_quoted_value(value) #:nodoc:
- quoted_value = connection.quote(value)
- quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "
- quoted_value
- end
- end
-
- public
- # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
- # attributes but not yet saved (pass a hash with key names matching the associated table column names).
- # In both instances, valid attribute keys are determined by the column names of the associated table --
- # hence you can't have attributes that aren't part of the table columns.
- #
- # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
- # in the +options+ parameter.
- #
- # ==== Examples
- # # Instantiates a single new object
- # User.new(:first_name => 'Jamie')
- #
- # # Instantiates a single new object using the :admin mass-assignment security role
- # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
- #
- # # Instantiates a single new object bypassing mass-assignment security
- # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
- def initialize(attributes = nil, options = {})
- @attributes = attributes_from_column_definition
- @association_cache = {}
- @aggregation_cache = {}
- @attributes_cache = {}
- @new_record = true
- @readonly = false
- @destroyed = false
- @marked_for_destruction = false
- @previously_changed = {}
- @changed_attributes = {}
- @relation = nil
-
- ensure_proper_type
- set_serialized_attributes
-
- populate_with_current_scope_attributes
-
- assign_attributes(attributes, options) if attributes
-
- yield self if block_given?
- run_callbacks :initialize
- end
-
- # Populate +coder+ with attributes about this record that should be
- # serialized. The structure of +coder+ defined in this method is
- # guaranteed to match the structure of +coder+ passed to the +init_with+
- # method.
- #
- # Example:
- #
- # class Post < ActiveRecord::Base
- # end
- # coder = {}
- # Post.new.encode_with(coder)
- # coder # => { 'id' => nil, ... }
- def encode_with(coder)
- coder['attributes'] = attributes
- end
-
- # Initialize an empty model object from +coder+. +coder+ must contain
- # the attributes necessary for initializing an empty model object. For
- # example:
- #
- # class Post < ActiveRecord::Base
- # end
- #
- # post = Post.allocate
- # post.init_with('attributes' => { 'title' => 'hello world' })
- # post.title # => 'hello world'
- def init_with(coder)
- @attributes = coder['attributes']
- @relation = nil
-
- set_serialized_attributes
-
- @attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
- @association_cache = {}
- @aggregation_cache = {}
- @readonly = @destroyed = @marked_for_destruction = false
- @new_record = false
- run_callbacks :find
- run_callbacks :initialize
-
- self
- end
-
- # Returns a String, which Action Pack uses for constructing an URL to this
- # object. The default implementation returns this record's id as a String,
- # or nil if this record's unsaved.
- #
- # For example, suppose that you have a User model, and that you have a
- # <tt>resources :users</tt> route. Normally, +user_path+ will
- # construct a path with the user object's 'id' in it:
- #
- # user = User.find_by_name('Phusion')
- # user_path(user) # => "/users/1"
- #
- # You can override +to_param+ in your model to make +user_path+ construct
- # a path using the user's name instead of the user's id:
- #
- # class User < ActiveRecord::Base
- # def to_param # overridden
- # name
- # end
- # end
- #
- # user = User.find_by_name('Phusion')
- # user_path(user) # => "/users/Phusion"
- def to_param
- # We can't use alias_method here, because method 'id' optimizes itself on the fly.
- id && id.to_s # Be sure to stringify the id for routes
- end
-
- # Returns a cache key that can be used to identify this record.
- #
- # ==== Examples
- #
- # Product.new.cache_key # => "products/new"
- # Product.find(5).cache_key # => "products/5" (updated_at not available)
- # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
- def cache_key
- case
- when new_record?
- "#{self.class.model_name.cache_key}/new"
- when timestamp = self[:updated_at]
- timestamp = timestamp.utc.to_s(:number)
- "#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
- else
- "#{self.class.model_name.cache_key}/#{id}"
- end
- end
-
- def quoted_id #:nodoc:
- quote_value(id, column_for_attribute(self.class.primary_key))
- end
-
- # Returns true if the given attribute is in the attributes hash
- def has_attribute?(attr_name)
- @attributes.has_key?(attr_name.to_s)
- end
-
- # Returns an array of names for the attributes available on this object.
- def attribute_names
- @attributes.keys
- end
-
- # Allows you to set all the attributes at once by passing in a hash with keys
- # matching the attribute names (which again matches the column names).
- #
- # If any attributes are protected by either +attr_protected+ or
- # +attr_accessible+ then only settable attributes will be assigned.
- #
- # class User < ActiveRecord::Base
- # attr_protected :is_admin
- # end
- #
- # user = User.new
- # user.attributes = { :username => 'Phusion', :is_admin => true }
- # user.username # => "Phusion"
- # user.is_admin? # => false
- def attributes=(new_attributes)
- return unless new_attributes.is_a?(Hash)
-
- assign_attributes(new_attributes)
- end
-
- # Allows you to set all the attributes for a particular mass-assignment
- # security role by passing in a hash of attributes with keys matching
- # the attribute names (which again matches the column names) and the role
- # name using the :as option.
- #
- # To bypass mass-assignment security you can use the :without_protection => true
- # option.
- #
- # class User < ActiveRecord::Base
- # attr_accessible :name
- # attr_accessible :name, :is_admin, :as => :admin
- # end
- #
- # user = User.new
- # user.assign_attributes({ :name => 'Josh', :is_admin => true })
- # user.name # => "Josh"
- # user.is_admin? # => false
- #
- # user = User.new
- # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
- # user.name # => "Josh"
- # user.is_admin? # => true
- #
- # user = User.new
- # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
- # user.name # => "Josh"
- # user.is_admin? # => true
- def assign_attributes(new_attributes, options = {})
- return unless new_attributes
-
- attributes = new_attributes.stringify_keys
- multi_parameter_attributes = []
- @mass_assignment_options = options
-
- unless options[:without_protection]
- attributes = sanitize_for_mass_assignment(attributes, mass_assignment_role)
- end
-
- attributes.each do |k, v|
- if k.include?("(")
- multi_parameter_attributes << [ k, v ]
- elsif respond_to?("#{k}=")
- send("#{k}=", v)
- else
- raise(UnknownAttributeError, "unknown attribute: #{k}")
- end
- end
-
- @mass_assignment_options = nil
- assign_multiparameter_attributes(multi_parameter_attributes)
- end
-
- # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
- def attributes
- Hash[@attributes.map { |name, _| [name, read_attribute(name)] }]
- end
-
- # Returns an <tt>#inspect</tt>-like string for the value of the
- # attribute +attr_name+. String attributes are truncated upto 50
- # characters, and Date and Time attributes are returned in the
- # <tt>:db</tt> format. Other attributes return the value of
- # <tt>#inspect</tt> without modification.
- #
- # person = Person.create!(:name => "David Heinemeier Hansson " * 3)
- #
- # person.attribute_for_inspect(:name)
- # # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
- #
- # person.attribute_for_inspect(:created_at)
- # # => '"2009-01-12 04:48:57"'
- def attribute_for_inspect(attr_name)
- value = read_attribute(attr_name)
-
- if value.is_a?(String) && value.length > 50
- "#{value[0..50]}...".inspect
- elsif value.is_a?(Date) || value.is_a?(Time)
- %("#{value.to_s(:db)}")
- else
- value.inspect
- end
- end
-
- # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
- # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
- def attribute_present?(attribute)
- !_read_attribute(attribute).blank?
- end
-
- # Returns the column object for the named attribute.
- def column_for_attribute(name)
- self.class.columns_hash[name.to_s]
- end
-
- # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
- # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
- #
- # Note that new records are different from any other record by definition, unless the
- # other record is the receiver itself. Besides, if you fetch existing records with
- # +select+ and leave the ID out, you're on your own, this predicate will return false.
- #
- # Note also that destroying a record preserves its ID in the model instance, so deleted
- # models are still comparable.
- def ==(comparison_object)
- super ||
- comparison_object.instance_of?(self.class) &&
- id.present? &&
- comparison_object.id == id
- end
- alias :eql? :==
-
- # Delegates to id in order to allow two records of the same type and id to work with something like:
- # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
- def hash
- id.hash
- end
-
- # Freeze the attributes hash such that associations are still accessible, even on destroyed records.
- def freeze
- @attributes.freeze; self
- end
-
- # Returns +true+ if the attributes hash has been frozen.
- def frozen?
- @attributes.frozen?
- end
-
- # Allows sort on objects
- def <=>(other_object)
- if other_object.is_a?(self.class)
- self.to_key <=> other_object.to_key
- else
- nil
- end
- end
-
- # Backport dup from 1.9 so that initialize_dup() gets called
- unless Object.respond_to?(:initialize_dup)
- def dup # :nodoc:
- copy = super
- copy.initialize_dup(self)
- copy
- end
- end
-
- # Duped objects have no id assigned and are treated as new records. Note
- # that this is a "shallow" copy as it copies the object's attributes
- # only, not its associations. The extent of a "deep" copy is application
- # specific and is therefore left to the application to implement according
- # to its need.
- # The dup method does not preserve the timestamps (created|updated)_(at|on).
- def initialize_dup(other)
- cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
- cloned_attributes.delete(self.class.primary_key)
-
- @attributes = cloned_attributes
-
- _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
-
- @changed_attributes = {}
- attributes_from_column_definition.each do |attr, orig_value|
- @changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
- end
-
- @aggregation_cache = {}
- @association_cache = {}
- @attributes_cache = {}
- @new_record = true
-
- ensure_proper_type
- populate_with_current_scope_attributes
- super
- end
-
- # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
- # attributes will be marked as read only since they cannot be saved.
- def readonly?
- @readonly
- end
-
- # Marks this record as read only.
- def readonly!
- @readonly = true
- end
-
- # Returns the contents of the record as a nicely formatted string.
- def inspect
- inspection = if @attributes
- self.class.column_names.collect { |name|
- if has_attribute?(name)
- "#{name}: #{attribute_for_inspect(name)}"
- end
- }.compact.join(", ")
- else
- "not initialized"
- end
- "#<#{self.class} #{inspection}>"
- end
-
- protected
- def clone_attributes(reader_method = :read_attribute, attributes = {})
- attribute_names.each do |name|
- attributes[name] = clone_attribute_value(reader_method, name)
- end
- attributes
- end
-
- def clone_attribute_value(reader_method, attribute_name)
- value = send(reader_method, attribute_name)
- value.duplicable? ? value.clone : value
- rescue TypeError, NoMethodError
- value
- end
-
- def mass_assignment_options
- @mass_assignment_options ||= {}
- end
-
- def mass_assignment_role
- mass_assignment_options[:as] || :default
- end
-
- private
-
- # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
- # of the array, and then rescues from the possible NoMethodError. If those elements are
- # ActiveRecord::Base's, then this triggers the various method_missing's that we have,
- # which significantly impacts upon performance.
- #
- # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
- #
- # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/
- def to_ary # :nodoc:
- nil
- end
-
- def set_serialized_attributes
- sattrs = self.class.serialized_attributes
-
- sattrs.each do |key, coder|
- @attributes[key] = coder.load @attributes[key] if @attributes.key?(key)
- end
- end
-
- # Sets the attribute used for single table inheritance to this class name if this is not the
- # ActiveRecord::Base descendant.
- # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
- # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
- # No such attribute would be set for objects of the Message class in that example.
- def ensure_proper_type
- klass = self.class
- if klass.finder_needs_type_condition?
- write_attribute(klass.inheritance_column, klass.sti_name)
- end
- end
-
- # The primary key and inheritance column can never be set by mass-assignment for security reasons.
- def self.attributes_protected_by_default
- default = [ primary_key, inheritance_column ]
- default << 'id' unless primary_key.eql? 'id'
- default
- end
-
- # Returns a copy of the attributes hash where all the values have been safely quoted for use in
- # an Arel insert/update method.
- def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
- attrs = {}
- klass = self.class
- arel_table = klass.arel_table
-
- attribute_names.each do |name|
- if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
-
- if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
-
- value = if coder = klass.serialized_attributes[name]
- coder.dump @attributes[name]
- else
- # FIXME: we need @attributes to be used consistently.
- # If the values stored in @attributes were already type
- # casted, this code could be simplified
- read_attribute(name)
- end
-
- attrs[arel_table[name]] = value
- end
- end
- end
- attrs
- end
-
- # Quote strings appropriately for SQL statements.
- def quote_value(value, column = nil)
- self.class.connection.quote(value, column)
- end
-
- # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
- # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
- # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
- # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
- # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
- # attribute will be set to nil.
- def assign_multiparameter_attributes(pairs)
- execute_callstack_for_multiparameter_attributes(
- extract_callstack_for_multiparameter_attributes(pairs)
- )
- end
-
- def instantiate_time_object(name, values)
- if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
- Time.zone.local(*values)
- else
- Time.time_with_datetime_fallback(@@default_timezone, *values)
- end
- end
-
- def execute_callstack_for_multiparameter_attributes(callstack)
- errors = []
- callstack.each do |name, values_with_empty_parameters|
- begin
- send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
- rescue => ex
- errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
- end
- end
- unless errors.empty?
- raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
- end
- end
-
- def read_value_from_parameter(name, values_hash_from_param)
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
- if values_hash_from_param.values.all?{|v|v.nil?}
- nil
- elsif klass == Time
- read_time_parameter_value(name, values_hash_from_param)
- elsif klass == Date
- read_date_parameter_value(name, values_hash_from_param)
- else
- read_other_parameter_value(klass, name, values_hash_from_param)
- end
- end
-
- def read_time_parameter_value(name, values_hash_from_param)
- # If Date bits were not provided, error
- raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
- # If Date bits were provided but blank, then return nil
- return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
-
- set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
- # If Time bits are not there, then default to 0
- (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]}
- instantiate_time_object(name, set_values)
- end
-
- def read_date_parameter_value(name, values_hash_from_param)
- return nil if (1..3).any? {|position| values_hash_from_param[position].blank?}
- set_values = [values_hash_from_param[1], values_hash_from_param[2], values_hash_from_param[3]]
- begin
- Date.new(*set_values)
- rescue ArgumentError # if Date.new raises an exception on an invalid date
- instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
- end
- end
-
- def read_other_parameter_value(klass, name, values_hash_from_param)
- max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
- values = (1..max_position).collect do |position|
- raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
- values_hash_from_param[position]
- end
- klass.new(*values)
- end
-
- def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
- [values_hash_from_param.keys.max,upper_cap].min
- end
-
- def extract_callstack_for_multiparameter_attributes(pairs)
- attributes = { }
-
- pairs.each do |pair|
- multiparameter_name, value = pair
- attribute_name = multiparameter_name.split("(").first
- attributes[attribute_name] = {} unless attributes.include?(attribute_name)
-
- parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
- attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
- end
-
- attributes
- end
-
- def type_cast_attribute_value(multiparameter_name, value)
- multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value
- end
-
- def find_parameter_position(multiparameter_name)
- multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
- end
-
- # Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
- def comma_pair_list(hash)
- hash.map { |k,v| "#{k} = #{v}" }.join(", ")
- end
-
- def quote_columns(quoter, hash)
- Hash[hash.map { |name, value| [quoter.quote_column_name(name), value] }]
- end
-
- def quoted_comma_pair_list(quoter, hash)
- comma_pair_list(quote_columns(quoter, hash))
- end
-
- def convert_number_column_value(value)
- if value == false
- 0
- elsif value == true
- 1
- elsif value.is_a?(String) && value.blank?
- nil
- else
- value
- end
- end
-
- def populate_with_current_scope_attributes
- return unless self.class.scope_attributes?
-
- self.class.scope_attributes.each do |att,value|
- send("#{att}=", value) if respond_to?("#{att}=")
- end
- end
- end
-
- Base.class_eval do
- include ActiveRecord::Persistence
- extend ActiveModel::Naming
- extend QueryCache::ClassMethods
- extend ActiveSupport::Benchmarkable
- extend ActiveSupport::DescendantsTracker
-
- include ActiveModel::Conversion
- include Validations
- extend CounterCache
- include Locking::Optimistic, Locking::Pessimistic
- include AttributeMethods
- include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query
- include AttributeMethods::PrimaryKey
- include AttributeMethods::TimeZoneConversion
- include AttributeMethods::Dirty
- include ActiveModel::MassAssignmentSecurity
- include Callbacks, ActiveModel::Observing, Timestamp
- include Associations, NamedScope
- include IdentityMap
- include ActiveModel::SecurePassword
-
- # AutosaveAssociation needs to be included before Transactions, because we want
- # #save_with_autosave_associations to be wrapped inside a transaction.
- include AutosaveAssociation, NestedAttributes
- include Aggregations, Transactions, Reflection, Serialization, Store
-
- NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner)
-
- # 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)).
- # (Alias for the protected read_attribute method).
- alias [] read_attribute
-
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
- # (Alias for the protected write_attribute method).
- alias []= write_attribute
-
- public :[], :[]=
+ include ActiveRecord::Model
end
end
-require 'active_record/connection_adapters/abstract/connection_specification'
-ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
+ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Model::DeprecationProxy)
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index a175bf003c..a050fabf35 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/array/wrap'
-
module ActiveRecord
# = Active Record Callbacks
#
@@ -25,7 +23,7 @@ module ActiveRecord
# Check out <tt>ActiveRecord::Transactions</tt> for more details about <tt>after_commit</tt> and
# <tt>after_rollback</tt>.
#
- # Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
+ # Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
# is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
# are instantiated as well.
#
@@ -215,24 +213,48 @@ module ActiveRecord
# instead of quietly returning +false+.
#
# == Debugging callbacks
- #
- # The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. ActiveModel Callbacks support
+ #
+ # 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:
- #
+ #
+ # 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
+ # We can't define callbacks directly on ActiveRecord::Model because
+ # it is a module. So we queue up the definitions and execute them
+ # when ActiveRecord::Model is included.
+ module Register #:nodoc:
+ def self.extended(base)
+ base.config_attribute :_callbacks_register
+ base._callbacks_register = []
+ end
+
+ def self.setup(base)
+ base._callbacks_register.each do |item|
+ base.send(*item)
+ end
+ end
+
+ def define_callbacks(*args)
+ self._callbacks_register << [:define_callbacks, *args]
+ end
+
+ def define_model_callbacks(*args)
+ self._callbacks_register << [:define_model_callbacks, *args]
+ end
+ end
+
extend ActiveSupport::Concern
CALLBACKS = [
@@ -242,8 +264,11 @@ module ActiveRecord
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
]
+ module ClassMethods
+ include ActiveModel::Callbacks
+ end
+
included do
- extend ActiveModel::Callbacks
include ActiveModel::Validations::Callbacks
define_model_callbacks :initialize, :find, :touch, :only => :after
diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb
index fb59d9fb07..66a0c83c41 100644
--- a/activerecord/lib/active_record/coders/yaml_column.rb
+++ b/activerecord/lib/active_record/coders/yaml_column.rb
@@ -15,6 +15,12 @@ module ActiveRecord
end
def dump(obj)
+ return if obj.nil?
+
+ unless obj.is_a?(object_class)
+ raise SerializationTypeMismatch,
+ "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
+ end
YAML.dump obj
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 77a5fe1efb..46c7fc71ac 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -1,7 +1,8 @@
require 'thread'
require 'monitor'
require 'set'
-require 'active_support/core_ext/module/synchronization'
+require 'active_support/core_ext/module/deprecation'
+require 'timeout'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@@ -9,6 +10,10 @@ module ActiveRecord
class ConnectionTimeoutError < ConnectionNotEstablished
end
+ # Raised when a connection pool is full and another connection is requested
+ class PoolFullError < ConnectionNotEstablished
+ end
+
module ConnectionAdapters
# Connection pool base class for managing Active Record database
# connections.
@@ -57,10 +62,50 @@ module ActiveRecord
# * +wait_timeout+: number of seconds to block and wait for a connection
# before giving up and raising a timeout error (default 5 seconds).
class ConnectionPool
- attr_accessor :automatic_reconnect
- attr_reader :spec, :connections
- attr_reader :columns, :columns_hash, :primary_keys, :tables
- attr_reader :column_defaults
+ # Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
+ # A reaper instantiated with a nil frequency will never reap the
+ # connection pool.
+ #
+ # Configure the frequency by setting "reaping_frequency" in your
+ # database yaml file.
+ class Reaper
+ attr_reader :pool, :frequency
+
+ def initialize(pool, frequency)
+ @pool = pool
+ @frequency = frequency
+ end
+
+ def run
+ return unless frequency
+ Thread.new(frequency, pool) { |t, p|
+ while true
+ sleep t
+ p.reap
+ end
+ }
+ end
+ end
+
+ include MonitorMixin
+
+ attr_accessor :automatic_reconnect, :timeout
+ attr_reader :spec, :connections, :size, :reaper
+
+ class Latch # :nodoc:
+ def initialize
+ @mutex = Mutex.new
+ @cond = ConditionVariable.new
+ end
+
+ def release
+ @mutex.synchronize { @cond.broadcast }
+ end
+
+ def await
+ @mutex.synchronize { @cond.wait @mutex }
+ end
+ end
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
# object which describes database connection information (e.g. adapter,
@@ -69,88 +114,23 @@ module ActiveRecord
#
# The default ConnectionPool maximum size is 5.
def initialize(spec)
+ super()
+
@spec = spec
# The cache of reserved connections mapped to threads
@reserved_connections = {}
- # The mutex used to synchronize pool access
- @connection_mutex = Monitor.new
- @queue = @connection_mutex.new_cond
@timeout = spec.config[:wait_timeout] || 5
+ @reaper = Reaper.new self, spec.config[:reaping_frequency]
+ @reaper.run
# default max pool size to 5
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
+ @latch = Latch.new
@connections = []
- @checked_out = []
@automatic_reconnect = true
- @tables = {}
- @visitor = nil
-
- @columns = Hash.new do |h, table_name|
- h[table_name] = with_connection do |conn|
-
- # Fetch a list of columns
- conn.columns(table_name, "#{table_name} Columns").tap do |columns|
-
- # set primary key information
- columns.each do |column|
- column.primary = column.name == primary_keys[table_name]
- end
- end
- end
- end
-
- @columns_hash = Hash.new do |h, table_name|
- h[table_name] = Hash[columns[table_name].map { |col|
- [col.name, col]
- }]
- end
-
- @column_defaults = Hash.new do |h, table_name|
- h[table_name] = Hash[columns[table_name].map { |col|
- [col.name, col.default]
- }]
- end
-
- @primary_keys = Hash.new do |h, table_name|
- h[table_name] = with_connection do |conn|
- table_exists?(table_name) ? conn.primary_key(table_name) : 'id'
- end
- end
- end
-
- # A cached lookup for table existence.
- def table_exists?(name)
- return true if @tables.key? name
-
- with_connection do |conn|
- conn.tables.each { |table| @tables[table] = true }
- @tables[name] = true if !@tables.key?(name) && conn.table_exists?(name)
- end
-
- @tables.key? name
- end
-
- # Clears out internal caches:
- #
- # * columns
- # * columns_hash
- # * tables
- def clear_cache!
- @columns.clear
- @columns_hash.clear
- @column_defaults.clear
- @tables.clear
- end
-
- # Clear out internal caches for table with +table_name+.
- def clear_table_cache!(table_name)
- @columns.delete table_name
- @columns_hash.delete table_name
- @column_defaults.delete table_name
- @primary_keys.delete table_name
end
# Retrieve the connection associated with the current thread, or call
@@ -162,18 +142,21 @@ module ActiveRecord
@reserved_connections[current_connection_id] ||= checkout
end
- # Check to see if there is an active connection in this connection
- # pool.
+ # Is there an open connection that is being used for the current thread?
def active_connection?
- @reserved_connections.key? current_connection_id
+ @reserved_connections.fetch(current_connection_id) {
+ return false
+ }.in_use?
end
# Signal that the thread is finished with the current connection.
# #release_connection releases the connection-thread association
# and returns the connection to the pool.
def release_connection(with_id = current_connection_id)
- conn = @reserved_connections.delete(with_id)
- checkin conn if conn
+ synchronize do
+ conn = @reserved_connections.delete(with_id)
+ checkin conn if conn
+ end
end
# If a connection already exists yield it to the block. If no connection
@@ -181,7 +164,7 @@ module ActiveRecord
# connection when finished.
def with_connection
connection_id = current_connection_id
- fresh_connection = true unless @reserved_connections[connection_id]
+ fresh_connection = true unless active_connection?
yield connection
ensure
release_connection(connection_id) if fresh_connection
@@ -189,95 +172,72 @@ module ActiveRecord
# Returns true if a connection has already been opened.
def connected?
- !@connections.empty?
+ synchronize { @connections.any? }
end
# Disconnects all connections in the pool, and clears the pool.
def disconnect!
- @reserved_connections.each do |name,conn|
- checkin conn
- end
- @reserved_connections = {}
- @connections.each do |conn|
- conn.disconnect!
+ synchronize do
+ @reserved_connections = {}
+ @connections.each do |conn|
+ checkin conn
+ conn.disconnect!
+ end
+ @connections = []
end
- @connections = []
end
# Clears the cache which maps classes.
def clear_reloadable_connections!
- @reserved_connections.each do |name, conn|
- checkin conn
- end
- @reserved_connections = {}
- @connections.each do |conn|
- conn.disconnect! if conn.requires_reloading?
- end
- @connections.delete_if do |conn|
- conn.requires_reloading?
- end
- end
-
- # Verify active connections and remove and disconnect connections
- # associated with stale threads.
- def verify_active_connections! #:nodoc:
- clear_stale_cached_connections!
- @connections.each do |connection|
- connection.verify!
+ synchronize do
+ @reserved_connections = {}
+ @connections.each do |conn|
+ checkin conn
+ conn.disconnect! if conn.requires_reloading?
+ end
+ @connections.delete_if do |conn|
+ conn.requires_reloading?
+ end
end
end
- # Return any checked-out connections back to the pool by threads that
- # are no longer alive.
- def clear_stale_cached_connections!
- keys = @reserved_connections.keys - Thread.list.find_all { |t|
- t.alive?
- }.map { |thread| thread.object_id }
- keys.each do |key|
- checkin @reserved_connections[key]
- @reserved_connections.delete(key)
- end
+ def clear_stale_cached_connections! # :nodoc:
+ reap
end
+ deprecate :clear_stale_cached_connections! => "Please use #reap instead"
# Check-out a database connection from the pool, indicating that you want
# to use it. You should call #checkin when you no longer need this.
#
- # This is done by either returning an existing connection, or by creating
- # a new connection. If the maximum number of connections for this pool has
- # already been reached, but the pool is empty (i.e. they're all being used),
- # then this method will wait until a thread has checked in a connection.
- # The wait time is bounded however: if no connection can be checked out
- # within the timeout specified for this pool, then a ConnectionTimeoutError
- # exception will be raised.
+ # This is done by either returning and leasing existing connection, or by
+ # creating a new connection and leasing it.
+ #
+ # If all connections are leased and the pool is at capacity (meaning the
+ # number of currently leased connections is greater than or equal to the
+ # size limit set), an ActiveRecord::PoolFullError exception will be raised.
#
# Returns: an AbstractAdapter object.
#
# Raises:
- # - ConnectionTimeoutError: no connection can be obtained from the pool
- # within the timeout period.
+ # - PoolFullError: no connection can be obtained from the pool.
def checkout
- # Checkout an available connection
- @connection_mutex.synchronize do
- loop do
- conn = if @checked_out.size < @connections.size
- checkout_existing_connection
- elsif @connections.size < @size
- checkout_new_connection
- end
- return conn if conn
-
- @queue.wait(@timeout)
-
- if(@checked_out.size < @connections.size)
- next
- else
- clear_stale_cached_connections!
- if @size == @checked_out.size
- raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it."
- end
+ loop do
+ # Checkout an available connection
+ synchronize do
+ # Try to find a connection that hasn't been leased, and lease it
+ conn = connections.find { |c| c.lease }
+
+ # If all connections were leased, and we have room to expand,
+ # create a new connection and lease it.
+ if !conn && connections.size < size
+ conn = checkout_new_connection
+ conn.lease
end
+ return checkout_and_verify(conn) if conn
end
+
+ Timeout.timeout(@timeout, PoolFullError) { @latch.await }
end
end
@@ -287,30 +247,58 @@ module ActiveRecord
# +conn+: an AbstractAdapter object, which was obtained by earlier by
# calling +checkout+ on this pool.
def checkin(conn)
- @connection_mutex.synchronize do
+ synchronize do
conn.run_callbacks :checkin do
- @checked_out.delete conn
- @queue.signal
+ conn.expire
end
+
+ release conn
+ end
+ @latch.release
+ end
+
+ # Remove a connection from the connection pool. The connection will
+ # remain open and active but will no longer be managed by this pool.
+ def remove(conn)
+ synchronize do
+ @connections.delete conn
+
+ # FIXME: we might want to store the key on the connection so that removing
+ # from the reserved hash will be a little easier.
+ release conn
end
+ @latch.release
end
- synchronize :clear_reloadable_connections!, :verify_active_connections!,
- :connected?, :disconnect!, :with => :@connection_mutex
+ # Removes dead connections from the pool. A dead connection can occur
+ # if a programmer forgets to close a connection at the end of a thread
+ # or a thread dies unexpectedly.
+ def reap
+ synchronize do
+ stale = Time.now - @timeout
+ connections.dup.each do |conn|
+ remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
+ end
+ end
+ @latch.release
+ end
private
- def new_connection
- connection = ActiveRecord::Base.send(spec.adapter_method, spec.config)
+ def release(conn)
+ thread_id = if @reserved_connections[current_connection_id] == conn
+ current_connection_id
+ else
+ @reserved_connections.keys.find { |k|
+ @reserved_connections[k] == conn
+ }
+ end
- # TODO: This is a bit icky, and in the long term we may want to change the method
- # signature for connections. Also, if we switch to have one visitor per
- # connection (and therefore per thread), we can get rid of the thread-local
- # variable in Arel::Visitors::ToSql.
- @visitor ||= connection.class.visitor_for(self)
- connection.visitor = @visitor
+ @reserved_connections.delete thread_id if thread_id
+ end
- connection
+ def new_connection
+ ActiveRecord::Base.send(spec.adapter_method, spec.config)
end
def current_connection_id #:nodoc:
@@ -321,19 +309,14 @@ module ActiveRecord
raise ConnectionNotEstablished unless @automatic_reconnect
c = new_connection
+ c.pool = self
@connections << c
- checkout_and_verify(c)
- end
-
- def checkout_existing_connection
- c = (@connections - @checked_out).first
- checkout_and_verify(c)
+ c
end
def checkout_and_verify(c)
c.run_callbacks :checkout do
c.verify!
- @checked_out << c
end
c
end
@@ -363,14 +346,18 @@ module ActiveRecord
# ActiveRecord::Base.connection_handler. Active Record models use this to
# determine that connection pool that they should use.
class ConnectionHandler
- attr_reader :connection_pools
-
- def initialize(pools = {})
+ def initialize(pools = Hash.new { |h,k| h[k] = {} })
@connection_pools = pools
+ @class_to_pool = Hash.new { |h,k| h[k] = {} }
+ end
+
+ def connection_pools
+ @connection_pools[Process.pid]
end
def establish_connection(name, spec)
- @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
+ set_pool_for_spec spec, ConnectionAdapters::ConnectionPool.new(spec)
+ set_class_to_pool name, connection_pools[spec]
end
# Returns true if there are any active connections among the connection
@@ -383,21 +370,16 @@ module ActiveRecord
# and also returns connections to the pool cached by threads that are no
# longer alive.
def clear_active_connections!
- @connection_pools.each_value {|pool| pool.release_connection }
+ connection_pools.each_value {|pool| pool.release_connection }
end
# Clears the cache which maps classes.
def clear_reloadable_connections!
- @connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
+ connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
end
def clear_all_connections!
- @connection_pools.each_value {|pool| pool.disconnect! }
- end
-
- # Verify active connections.
- def verify_active_connections! #:nodoc:
- @connection_pools.each_value {|pool| pool.verify_active_connections! }
+ connection_pools.each_value {|pool| pool.disconnect! }
end
# Locate the connection of the nearest super class. This can be an
@@ -421,52 +403,56 @@ module ActiveRecord
# can be used as an argument for establish_connection, for easily
# re-establishing the connection.
def remove_connection(klass)
- pool = @connection_pools.delete(klass.name)
+ pool = class_to_pool.delete(klass.name)
return nil unless pool
+ connection_pools.delete pool.spec
pool.automatic_reconnect = false
pool.disconnect!
pool.spec.config
end
def retrieve_connection_pool(klass)
- pool = @connection_pools[klass.name]
+ pool = get_pool_for_class klass.name
return pool if pool
- return nil if ActiveRecord::Base == klass
- retrieve_connection_pool klass.superclass
+ return nil if ActiveRecord::Model == klass
+ retrieve_connection_pool klass.active_record_super
end
- end
- class ConnectionManagement
- class Proxy # :nodoc:
- attr_reader :body, :testing
-
- def initialize(body, testing = false)
- @body = body
- @testing = testing
- end
+ private
- def method_missing(method_sym, *arguments, &block)
- @body.send(method_sym, *arguments, &block)
- end
+ def class_to_pool
+ @class_to_pool[Process.pid]
+ end
- def respond_to?(method_sym, include_private = false)
- super || @body.respond_to?(method_sym)
- end
+ def set_pool_for_spec(spec, pool)
+ @connection_pools[Process.pid][spec] = pool
+ end
- def each(&block)
- body.each(&block)
- end
+ def set_class_to_pool(name, pool)
+ @class_to_pool[Process.pid][name] = pool
+ pool
+ end
- def close
- body.close if body.respond_to?(:close)
+ def get_pool_for_class(klass)
+ @class_to_pool[Process.pid].fetch(klass) {
+ c_to_p = @class_to_pool.values.find { |class_to_pool|
+ class_to_pool[klass]
+ }
- # Don't return connection (and perform implicit rollback) if
- # this request is a part of integration test
- ActiveRecord::Base.clear_active_connections! unless testing
- end
+ if c_to_p
+ pool = c_to_p[klass]
+ pool = ConnectionAdapters::ConnectionPool.new pool.spec
+ set_pool_for_spec pool.spec, pool
+ set_class_to_pool klass, pool
+ else
+ set_class_to_pool klass, nil
+ end
+ }
end
+ end
+ class ConnectionManagement
def initialize(app)
@app = app
end
@@ -474,9 +460,12 @@ module ActiveRecord
def call(env)
testing = env.key?('rack.test')
- status, headers, body = @app.call(env)
+ response = @app.call(env)
+ response[2] = ::Rack::BodyProxy.new(response[2]) do
+ ActiveRecord::Base.clear_active_connections! unless testing
+ end
- [status, headers, Proxy.new(body, testing)]
+ response
rescue
ActiveRecord::Base.clear_active_connections! unless testing
raise
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
deleted file mode 100644
index 3d0f146fed..0000000000
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ /dev/null
@@ -1,161 +0,0 @@
-module ActiveRecord
- class Base
- class ConnectionSpecification #:nodoc:
- attr_reader :config, :adapter_method
- def initialize (config, adapter_method)
- @config, @adapter_method = config, adapter_method
- end
- end
-
- ##
- # :singleton-method:
- # The connection handler
- class_attribute :connection_handler, :instance_writer => false
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
-
- # Returns the connection currently associated with the class. This can
- # also be used to "borrow" the connection to do database work that isn't
- # easily done without going straight to SQL.
- def connection
- self.class.connection
- end
-
- # Establishes the connection to the database. Accepts a hash as input where
- # the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
- # example for regular databases (MySQL, Postgresql, etc):
- #
- # ActiveRecord::Base.establish_connection(
- # :adapter => "mysql",
- # :host => "localhost",
- # :username => "myuser",
- # :password => "mypass",
- # :database => "somedatabase"
- # )
- #
- # Example for SQLite database:
- #
- # ActiveRecord::Base.establish_connection(
- # :adapter => "sqlite",
- # :database => "path/to/dbfile"
- # )
- #
- # Also accepts keys as strings (for parsing from YAML for example):
- #
- # ActiveRecord::Base.establish_connection(
- # "adapter" => "sqlite",
- # "database" => "path/to/dbfile"
- # )
- #
- # Or a URL:
- #
- # ActiveRecord::Base.establish_connection(
- # "postgres://myuser:mypass@localhost/somedatabase"
- # )
- #
- # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
- # may be returned on an error.
- def self.establish_connection(spec = ENV["DATABASE_URL"])
- case spec
- when nil
- raise AdapterNotSpecified unless defined?(Rails.env)
- establish_connection(Rails.env)
- when ConnectionSpecification
- self.connection_handler.establish_connection(name, spec)
- when Symbol, String
- if configuration = configurations[spec.to_s]
- establish_connection(configuration)
- elsif spec.is_a?(String) && hash = connection_url_to_hash(spec)
- establish_connection(hash)
- else
- raise AdapterNotSpecified, "#{spec} database is not configured"
- end
- else
- spec = spec.symbolize_keys
- unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
-
- begin
- require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
- rescue LoadError => e
- raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e})"
- end
-
- adapter_method = "#{spec[:adapter]}_connection"
- unless respond_to?(adapter_method)
- raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter"
- end
-
- remove_connection
- establish_connection(ConnectionSpecification.new(spec, adapter_method))
- end
- end
-
- def self.connection_url_to_hash(url) # :nodoc:
- config = URI.parse url
- adapter = config.scheme
- adapter = "postgresql" if adapter == "postgres"
- spec = { :adapter => adapter,
- :username => config.user,
- :password => config.password,
- :port => config.port,
- :database => config.path.sub(%r{^/},""),
- :host => config.host }
- spec.reject!{ |_,value| !value }
- if config.query
- options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
- spec.merge!(options)
- end
- spec
- end
-
- class << self
- # Returns the connection currently associated with the class. This can
- # also be used to "borrow" the connection to do database work unrelated
- # to any of the specific Active Records.
- def connection
- retrieve_connection
- end
-
- def connection_id
- Thread.current['ActiveRecord::Base.connection_id']
- end
-
- def connection_id=(connection_id)
- Thread.current['ActiveRecord::Base.connection_id'] = connection_id
- end
-
- # Returns the configuration of the associated connection as a hash:
- #
- # ActiveRecord::Base.connection_config
- # # => {:pool=>5, :timeout=>5000, :database=>"db/development.sqlite3", :adapter=>"sqlite3"}
- #
- # Please use only for reading.
- def connection_config
- connection_pool.spec.config
- end
-
- def connection_pool
- connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
- end
-
- def retrieve_connection
- connection_handler.retrieve_connection(self)
- end
-
- # Returns true if Active Record is connected.
- def connected?
- connection_handler.connected?(self)
- end
-
- def remove_connection(klass = self)
- connection_handler.remove_connection(klass)
- end
-
- def clear_active_connections!
- connection_handler.clear_active_connections!
- end
-
- delegate :clear_reloadable_connections!,
- :clear_all_connections!,:verify_active_connections!, :to => :connection_handler
- end
- end
-end
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 dc4a53034b..7b2961a04a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -2,9 +2,11 @@ module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseStatements
# Converts an arel AST to SQL
- def to_sql(arel)
+ def to_sql(arel, binds = [])
if arel.respond_to?(:ast)
- visitor.accept(arel.ast)
+ visitor.accept(arel.ast) do
+ quote(*binds.shift.reverse)
+ end
else
arel
end
@@ -13,19 +15,19 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select_all(arel, name = nil, binds = [])
- select(to_sql(arel), name, binds)
+ select(to_sql(arel, binds), name, binds)
end
# Returns a record hash with the column names as keys and column values
# as values.
- def select_one(arel, name = nil)
- result = select_all(arel, name)
+ def select_one(arel, name = nil, binds = [])
+ result = select_all(arel, name, binds)
result.first if result
end
# Returns a single value from a record
- def select_value(arel, name = nil)
- if result = select_one(arel, name)
+ def select_value(arel, name = nil, binds = [])
+ if result = select_one(arel, name, binds)
result.values.first
end
end
@@ -33,7 +35,7 @@ module ActiveRecord
# Returns an array of the values of the first column in a select:
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
def select_values(arel, name = nil)
- result = select_rows(to_sql(arel), name)
+ result = select_rows(to_sql(arel, []), name)
result.map { |v| v[0] }
end
@@ -55,21 +57,21 @@ module ActiveRecord
end
# Executes insert +sql+ statement in the context of this connection using
- # +binds+ as the bind substitutes. +name+ is the logged along with
+ # +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
- def exec_insert(sql, name, binds)
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
exec_query(sql, name, binds)
end
# Executes delete +sql+ statement in the context of this connection using
- # +binds+ as the bind substitutes. +name+ is the logged along with
+ # +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
def exec_delete(sql, name, binds)
exec_query(sql, name, binds)
end
# Executes update +sql+ statement in the context of this connection using
- # +binds+ as the bind substitutes. +name+ is the logged along with
+ # +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
def exec_update(sql, name, binds)
exec_query(sql, name, binds)
@@ -84,19 +86,19 @@ module ActiveRecord
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- sql, binds = sql_for_insert(to_sql(arel), pk, id_value, sequence_name, binds)
- value = exec_insert(sql, name, binds)
+ sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
+ value = exec_insert(sql, name, binds, pk, sequence_name)
id_value || last_inserted_id(value)
end
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
- exec_update(to_sql(arel), name, binds)
+ exec_update(to_sql(arel, binds), name, binds)
end
# Executes the delete statement and returns the number of rows affected.
def delete(arel, name = nil, binds = [])
- exec_delete(to_sql(arel), name, binds)
+ exec_delete(to_sql(arel, binds), name, binds)
end
# Checks whether there is currently no transaction active. This is done
@@ -130,7 +132,7 @@ module ActiveRecord
#
# In order to get around this problem, #transaction will emulate the effect
# of nested transactions, by using savepoints:
- # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
+ # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
# Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
#
# It is safe to call this method if a database transaction is already open,
@@ -310,13 +312,27 @@ module ActiveRecord
# on mysql (even when aliasing the tables), but mysql allows using JOIN directly in
# an UPDATE statement, so in the mysql adapters we redefine this to do that.
def join_to_update(update, select) #:nodoc:
- subselect = select.clone
- subselect.projections = [update.key]
+ key = update.key
+ subselect = subquery_for(key, select)
+
+ update.where key.in(subselect)
+ end
+
+ def join_to_delete(delete, select, key) #:nodoc:
+ subselect = subquery_for(key, select)
- update.where update.key.in(subselect)
+ delete.where key.in(subselect)
end
protected
+
+ # Return a subquery for the given key using the join information.
+ def subquery_for(key, select)
+ subselect = select.clone
+ subselect.projections = [key]
+ subselect
+ end
+
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil, binds = [])
@@ -341,7 +357,7 @@ module ActiveRecord
# Send a rollback message to all records after they have been rolled back. If rollback
# is false, only rollback records since the last save point.
- def rollback_transaction_records(rollback) #:nodoc
+ def rollback_transaction_records(rollback)
if rollback
records = @_current_transaction_records.flatten
@_current_transaction_records.clear
@@ -361,7 +377,7 @@ module ActiveRecord
end
# Send a commit message to all records after they have been committed.
- def commit_transaction_records #:nodoc
+ def commit_transaction_records
records = @_current_transaction_records.flatten
@_current_transaction_records.clear
unless records.blank?
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 27ff13ad89..17377bad96 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -57,7 +57,7 @@ module ActiveRecord
def select_all(arel, name = nil, binds = [])
if @query_cache_enabled
- sql = to_sql(arel)
+ sql = to_sql(arel, binds)
cache_sql(sql, binds) { super(sql, name, binds) }
else
super
@@ -65,18 +65,24 @@ module ActiveRecord
end
private
- def cache_sql(sql, binds)
- result =
- if @query_cache[sql].key?(binds)
- ActiveSupport::Notifications.instrument("sql.active_record",
- :sql => sql, :name => "CACHE", :connection_id => object_id)
- @query_cache[sql][binds]
- else
- @query_cache[sql][binds] = yield
- end
+ def cache_sql(sql, binds)
+ result =
+ if @query_cache[sql].key?(binds)
+ ActiveSupport::Notifications.instrument("sql.active_record",
+ :sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id)
+ @query_cache[sql][binds]
+ else
+ @query_cache[sql][binds] = yield
+ end
+ # FIXME: we should guarantee that all cached items are Result
+ # objects. Then we can avoid this conditional
+ if ActiveRecord::Result === result
+ result.dup
+ else
result.collect { |row| row.dup }
end
+ 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 f93c7cd74a..6f9f0399db 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -34,6 +34,7 @@ module ActiveRecord
when Numeric then value.to_s
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
+ when Class then "'#{value.to_s}'"
else
"'#{quote_string(YAML.dump(value))}'"
end
@@ -71,7 +72,8 @@ module ActiveRecord
when Date, Time then quoted_date(value)
when Symbol then value.to_s
else
- YAML.dump(value)
+ to_type = column ? " to #{column.type}" : ""
+ raise TypeError, "can't cast #{value.class}#{to_type}"
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 989a4fcbca..df78ba6c5a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -6,7 +6,10 @@ require 'bigdecimal/util'
module ActiveRecord
module ConnectionAdapters #:nodoc:
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
+ # Abstract representation of an index definition on a table. Instances of
+ # this type are typically created and returned by methods in database
+ # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
@@ -20,7 +23,7 @@ module ActiveRecord
end
def sql_type
- base.type_to_sql(type.to_sym, limit, precision, scale) rescue type
+ base.type_to_sql(type.to_sym, limit, precision, scale)
end
def to_sql
@@ -46,13 +49,13 @@ module ActiveRecord
# +change_table+ is actually of this type:
#
# class SomeMigration < ActiveRecord::Migration
- # def self.up
+ # def up
# create_table :foo do |t|
# puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
# end
# end
#
- # def self.down
+ # def down
# ...
# end
# end
@@ -62,10 +65,12 @@ module ActiveRecord
class TableDefinition
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
- attr_accessor :columns
+ attr_accessor :columns, :indexes
def initialize(base)
@columns = []
+ @columns_hash = {}
+ @indexes = {}
@base = base
end
@@ -86,7 +91,7 @@ module ActiveRecord
# Returns a ColumnDefinition for the column with name +name+.
def [](name)
- @columns.find {|column| column.name.to_s == name.to_s}
+ @columns_hash[name.to_s]
end
# Instantiates a new column for the table.
@@ -208,46 +213,60 @@ module ActiveRecord
#
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
# column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
- # options, these will be used when creating the <tt>_type</tt> column. So what can be written like this:
+ # options, these will be used when creating the <tt>_type</tt> column. The <tt>:index</tt> option
+ # will also create an index, similar to calling <tt>add_index</tt>. So what can be written like this:
#
# create_table :taggings do |t|
# t.integer :tag_id, :tagger_id, :taggable_id
# t.string :tagger_type
# t.string :taggable_type, :default => 'Photo'
# end
+ # add_index :taggings, :tag_id, :name => 'index_taggings_on_tag_id'
+ # add_index :taggings, [:tagger_id, :tagger_type]
#
# Can also be written as follows using references:
#
# create_table :taggings do |t|
- # t.references :tag
- # t.references :tagger, :polymorphic => true
+ # t.references :tag, :index => { :name => 'index_taggings_on_tag_id' }
+ # t.references :tagger, :polymorphic => true, :index => true
# t.references :taggable, :polymorphic => { :default => 'Photo' }
# end
def column(name, type, options = {})
- column = self[name] || ColumnDefinition.new(@base, name, type)
- if options[:limit]
- column.limit = options[:limit]
- elsif native[type.to_sym].is_a?(Hash)
- column.limit = native[type.to_sym][:limit]
+ name = name.to_s
+ type = type.to_sym
+
+ column = self[name] || new_column_definition(@base, name, type)
+
+ limit = options.fetch(:limit) do
+ native[type][:limit] if native[type].is_a?(Hash)
end
+
+ column.limit = limit
column.precision = options[:precision]
- column.scale = options[:scale]
- column.default = options[:default]
- column.null = options[:null]
- @columns << column unless @columns.include? column
+ column.scale = options[:scale]
+ column.default = options[:default]
+ column.null = options[:null]
self
end
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
class_eval <<-EOV, __FILE__, __LINE__ + 1
- def #{column_type}(*args) # def string(*args)
- options = args.extract_options! # options = args.extract_options!
- column_names = args # column_names = args
- #
- column_names.each { |name| column(name, '#{column_type}', options) } # column_names.each { |name| column(name, 'string', options) }
- end # end
+ def #{column_type}(*args) # def string(*args)
+ options = args.extract_options! # options = args.extract_options!
+ column_names = args # column_names = args
+ type = :'#{column_type}' # type = :string
+ column_names.each { |name| column(name, type, options) } # column_names.each { |name| column(name, type, options) }
+ end # end
EOV
end
+
+ # Adds index options to the indexes hash, keyed by column name
+ # This is primarily used to track indexes that need to be created after the table
+ #
+ # index(:account_id, :name => 'index_projects_on_account_id')
+ def index(column_name, options = {})
+ indexes[column_name] = options
+ end
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
# <tt>:updated_at</tt> to the table.
@@ -260,9 +279,11 @@ module ActiveRecord
def references(*args)
options = args.extract_options!
polymorphic = options.delete(:polymorphic)
+ index_options = options.delete(:index)
args.each do |col|
column("#{col}_id", :integer, options)
column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+ index(polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
end
end
alias :belongs_to :references
@@ -275,9 +296,16 @@ module ActiveRecord
end
private
- def native
- @base.native_database_types
- end
+ def new_column_definition(base, name, type)
+ definition = ColumnDefinition.new base, name, type
+ @columns << definition
+ @columns_hash[name] = definition
+ definition
+ end
+
+ def native
+ @base.native_database_types
+ end
end
# Represents an SQL table in an abstract way for updating a table.
@@ -320,7 +348,7 @@ module ActiveRecord
# Adds a new column to the named table.
# See TableDefinition#column for details of the options you can use.
- # ===== Example
+ #
# ====== Creating a simple column
# t.column(:name, :string)
def column(column_name, type, options = {})
@@ -335,7 +363,6 @@ module ActiveRecord
# Adds a new index to the table. +column_name+ can be a single Symbol, or
# an Array of Symbols. See SchemaStatements#add_index
#
- # ===== Examples
# ====== Creating a simple index
# t.index(:name)
# ====== Creating a unique index
@@ -352,7 +379,7 @@ module ActiveRecord
end
# Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
- # ===== Example
+ #
# t.timestamps
def timestamps
@base.add_timestamps(@table_name)
@@ -360,7 +387,7 @@ module ActiveRecord
# Changes the column's definition according to the new options.
# See TableDefinition#column for details of the options you can use.
- # ===== Examples
+ #
# t.change(:name, :string, :limit => 80)
# t.change(:description, :text)
def change(column_name, type, options = {})
@@ -368,7 +395,7 @@ module ActiveRecord
end
# Sets a new default value for a column. See SchemaStatements#change_column_default
- # ===== Examples
+ #
# t.change_default(:qualification, 'new')
# t.change_default(:authorized, 1)
def change_default(column_name, default)
@@ -376,16 +403,15 @@ module ActiveRecord
end
# Removes the column(s) from the table definition.
- # ===== Examples
+ #
# t.remove(:qualification)
# t.remove(:qualification, :experience)
def remove(*column_names)
- @base.remove_column(@table_name, column_names)
+ @base.remove_column(@table_name, *column_names)
end
# Removes the given index from the table.
#
- # ===== Examples
# ====== Remove the index_table_name_on_column in the table_name table
# t.remove_index :column
# ====== Remove the index named index_table_name_on_branch_id in the table_name table
@@ -399,14 +425,14 @@ module ActiveRecord
end
# Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
- # ===== Example
+ #
# t.remove_timestamps
def remove_timestamps
@base.remove_timestamps(@table_name)
end
# Renames a column.
- # ===== Example
+ #
# t.rename(:description, :name)
def rename(column_name, new_column_name)
@base.rename_column(@table_name, column_name, new_column_name)
@@ -414,23 +440,25 @@ module ActiveRecord
# Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
# <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
- # ===== Examples
+ #
# t.references(:goat)
# t.references(:goat, :polymorphic => true)
# t.belongs_to(:goat)
def references(*args)
options = args.extract_options!
polymorphic = options.delete(:polymorphic)
+ index_options = options.delete(:index)
args.each do |col|
@base.add_column(@table_name, "#{col}_id", :integer, options)
@base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+ @base.add_index(@table_name, polymorphic ? %w(id type).map { |t| "#{col}_#{t}" } : "#{col}_id", index_options.is_a?(Hash) ? index_options : nil) if index_options
end
end
alias :belongs_to :references
# Removes a reference. Optionally removes a +type+ column.
# <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
- # ===== Examples
+ #
# t.remove_references(:goat)
# t.remove_references(:goat, :polymorphic => true)
# t.remove_belongs_to(:goat)
@@ -445,7 +473,7 @@ module ActiveRecord
alias :remove_belongs_to :remove_references
# Adds a column or columns of a specified type
- # ===== Examples
+ #
# t.string(:goat)
# t.string(:goat, :sheep)
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
@@ -453,13 +481,13 @@ module ActiveRecord
def #{column_type}(*args) # def string(*args)
options = args.extract_options! # options = args.extract_options!
column_names = args # column_names = args
- #
+ type = :'#{column_type}' # type = :string
column_names.each do |name| # column_names.each do |name|
- column = ColumnDefinition.new(@base, name, '#{column_type}') # column = ColumnDefinition.new(@base, name, 'string')
+ column = ColumnDefinition.new(@base, name.to_s, type) # column = ColumnDefinition.new(@base, name, type)
if options[:limit] # if options[:limit]
column.limit = options[:limit] # column.limit = options[:limit]
- elsif native['#{column_type}'.to_sym].is_a?(Hash) # elsif native['string'.to_sym].is_a?(Hash)
- column.limit = native['#{column_type}'.to_sym][:limit] # column.limit = native['string'.to_sym][:limit]
+ elsif native[type].is_a?(Hash) # elsif native[type].is_a?(Hash)
+ column.limit = native[type][:limit] # column.limit = native[type][:limit]
end # end
column.precision = options[:precision] # column.precision = options[:precision]
column.scale = options[:scale] # column.scale = options[:scale]
@@ -479,4 +507,3 @@ module ActiveRecord
end
end
-
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index b4a9e29ef1..62b0f51bb2 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1,8 +1,12 @@
-require 'active_support/core_ext/array/wrap'
+require 'active_support/deprecation/reporting'
+require 'active_record/schema_migration'
+require 'active_record/migration/join_table'
module ActiveRecord
module ConnectionAdapters # :nodoc:
module SchemaStatements
+ include ActiveRecord::Migration::JoinTable
+
# Returns a Hash of mappings from the abstract data types to the native
# database types. See TableDefinition#column for details on the recognized
# abstract data types.
@@ -12,14 +16,11 @@ module ActiveRecord
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
- table_name[0...table_alias_length].gsub(/\./, '_')
+ table_name[0...table_alias_length].tr('.', '_')
end
- # def tables(name = nil) end
-
# Checks to see if the table +table_name+ exists on the database.
#
- # === Example
# table_exists?(:developers)
def table_exists?(table_name)
tables.include?(table_name.to_s)
@@ -30,7 +31,6 @@ module ActiveRecord
# Checks to see if an index exists on a table for a given index definition.
#
- # === Examples
# # Check an index exists
# index_exists?(:suppliers, :company_id)
#
@@ -43,7 +43,7 @@ module ActiveRecord
# # Check an index with a custom name exists
# index_exists?(:suppliers, :company_id, :name => "idx_company_id"
def index_exists?(table_name, column_name, options = {})
- column_names = Array.wrap(column_name)
+ column_names = Array(column_name)
index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
if options[:unique]
indexes(table_name).any?{ |i| i.unique && i.name == index_name }
@@ -54,7 +54,7 @@ module ActiveRecord
# Returns an array of Column objects for the table specified by +table_name+.
# See the concrete implementation for details on the expected parameter values.
- def columns(table_name, name = nil) end
+ def columns(table_name) end
# Checks to see if a column exists in a given table.
#
@@ -113,7 +113,7 @@ module ActiveRecord
# Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
#
# Also note that this just sets the primary key in the table. You additionally
- # need to configure the primary key in the model via the +set_primary_key+ macro.
+ # need to configure the primary key in the model via +self.primary_key=+.
# Models do NOT auto-detect the primary key from their table definition.
#
# [<tt>:options</tt>]
@@ -124,7 +124,6 @@ module ActiveRecord
# Set to true to drop the table before creating it.
# Defaults to false.
#
- # ===== Examples
# ====== Add a backend specific option to the generated SQL (MySQL)
# create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
# generates:
@@ -161,7 +160,7 @@ module ActiveRecord
yield td if block_given?
if options[:force] && table_exists?(table_name)
- drop_table(table_name)
+ drop_table(table_name, options)
end
create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
@@ -169,11 +168,49 @@ module ActiveRecord
create_sql << td.to_sql
create_sql << ") #{options[:options]}"
execute create_sql
+ td.indexes.each_pair { |c,o| add_index table_name, c, o }
+ end
+
+ # Creates a new join table with the name created using the lexical order of the first two
+ # arguments. These arguments can be a String or a Symbol.
+ #
+ # # Creates a table called 'assemblies_parts' with no id.
+ # create_join_table(:assemblies, :parts)
+ #
+ # You can pass a +options+ hash can include the following keys:
+ # [<tt>:table_name</tt>]
+ # Sets the table name overriding the default
+ # [<tt>:column_options</tt>]
+ # Any extra options you want appended to the columns definition.
+ # [<tt>:options</tt>]
+ # Any extra options you want appended to the table definition.
+ # [<tt>:temporary</tt>]
+ # Make a temporary table.
+ # [<tt>:force</tt>]
+ # Set to true to drop the table before creating it.
+ # Defaults to false.
+ #
+ # ====== Add a backend specific option to the generated SQL (MySQL)
+ # create_join_table(:assemblies, :parts, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ # generates:
+ # CREATE TABLE assemblies_parts (
+ # assembly_id int NOT NULL,
+ # part_id int NOT NULL,
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
+ def create_join_table(table_1, table_2, options = {})
+ join_table_name = find_join_table_name(table_1, table_2, options)
+
+ column_options = options.delete(:column_options) || {}
+ column_options.reverse_merge!({:null => false})
+
+ create_table(join_table_name, options.merge!(:id => false)) do |td|
+ td.integer :"#{table_1.to_s.singularize}_id", column_options
+ td.integer :"#{table_2.to_s.singularize}_id", column_options
+ end
end
# A block for changing columns in +table+.
#
- # === Example
# # change_table() yields a Table instance
# change_table(:suppliers) do |t|
# t.column :name, :string, :limit => 60
@@ -187,7 +224,6 @@ module ActiveRecord
#
# Defaults to false.
#
- # ===== Examples
# ====== Add a column
# change_table(:suppliers) do |t|
# t.column :name, :string, :limit => 60
@@ -246,14 +282,14 @@ module ActiveRecord
end
# Renames a table.
- # ===== Example
+ #
# rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
raise NotImplementedError, "rename_table is not implemented"
end
# Drops a table from the database.
- def drop_table(table_name)
+ def drop_table(table_name, options = {})
execute "DROP TABLE #{quote_table_name(table_name)}"
end
@@ -266,7 +302,7 @@ module ActiveRecord
end
# Removes the column(s) from the table definition.
- # ===== Examples
+ #
# remove_column(:suppliers, :qualification)
# remove_columns(:suppliers, :qualification, :experience)
def remove_column(table_name, *column_names)
@@ -276,7 +312,7 @@ module ActiveRecord
# Changes the column's definition according to the new options.
# See TableDefinition#column for details of the options you can use.
- # ===== Examples
+ #
# change_column(:suppliers, :name, :string, :limit => 80)
# change_column(:accounts, :description, :text)
def change_column(table_name, column_name, type, options = {})
@@ -284,7 +320,7 @@ module ActiveRecord
end
# Sets a new default value for a column.
- # ===== Examples
+ #
# change_column_default(:suppliers, :qualification, 'new')
# change_column_default(:accounts, :authorized, 1)
# change_column_default(:users, :email, nil)
@@ -293,7 +329,7 @@ module ActiveRecord
end
# Renames a column.
- # ===== Example
+ #
# rename_column(:suppliers, :description, :name)
def rename_column(table_name, column_name, new_column_name)
raise NotImplementedError, "rename_column is not implemented"
@@ -302,17 +338,8 @@ module ActiveRecord
# Adds a new index to the table. +column_name+ can be a single Symbol, or
# an Array of Symbols.
#
- # The index will be named after the table and the first column name,
- # unless you pass <tt>:name</tt> as an option.
- #
- # When creating an index on multiple columns, the first column is used as a name
- # for the index. For example, when you specify an index on two columns
- # [<tt>:first</tt>, <tt>:last</tt>], the DBMS creates an index for both columns as well as an
- # index for the first column <tt>:first</tt>. Using just the first name for this index
- # makes sense, because you will never have to create a singular index with this
- # name.
- #
- # ===== Examples
+ # The index will be named after the table and the column name(s), unless
+ # you pass <tt>:name</tt> as an option.
#
# ====== Creating a simple index
# add_index(:suppliers, :name)
@@ -339,9 +366,24 @@ module ActiveRecord
# CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
#
# Note: SQLite doesn't support index length
+ #
+ # ====== Creating an index with a sort order (desc or asc, asc is the default)
+ # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :party_id => :asc})
+ # generates
+ # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
+ #
+ # Note: mysql doesn't yet support index order (it accepts the syntax but ignores it)
+ #
+ # ====== Creating a partial index
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true, :where => "active")
+ # generates
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
+ #
+ # Note: only supported by PostgreSQL
+ #
def add_index(table_name, column_name, options = {})
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})"
+ index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
end
# Remove the given index from the table.
@@ -377,7 +419,7 @@ module ActiveRecord
def index_name(table_name, options) #:nodoc:
if Hash === options # legacy support
if options[:column]
- "index_#{table_name}_on_#{Array.wrap(options[:column]) * '_and_'}"
+ "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
elsif options[:name]
options[:name]
else
@@ -405,37 +447,20 @@ module ActiveRecord
def dump_schema_information #:nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
- migrated = select_values("SELECT version FROM #{sm_table}")
- migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n")
+
+ ActiveRecord::SchemaMigration.order('version').all.map { |sm|
+ "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
+ }.join "\n\n"
end
# Should not be called normally, but this operation is non-destructive.
# The migrations module handles this automatically.
def initialize_schema_migrations_table
- sm_table = ActiveRecord::Migrator.schema_migrations_table_name
-
- unless table_exists?(sm_table)
- create_table(sm_table, :id => false) do |schema_migrations_table|
- schema_migrations_table.column :version, :string, :null => false
- end
- add_index sm_table, :version, :unique => true,
- :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
-
- # Backwards-compatibility: if we find schema_info, assume we've
- # migrated up to that point:
- si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
-
- if table_exists?(si_table)
-
- old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
- assume_migrated_upto_version(old_version)
- drop_table(si_table)
- end
- end
+ ActiveRecord::SchemaMigration.create_table
end
def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
- migrations_paths = Array.wrap(migrations_paths)
+ migrations_paths = Array(migrations_paths)
version = version.to_i
sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
@@ -504,15 +529,15 @@ module ActiveRecord
end
# Adds timestamps (created_at and updated_at) columns to the named table.
- # ===== Examples
+ #
# add_timestamps(:suppliers)
def add_timestamps(table_name)
- add_column table_name, :created_at, :datetime, :null => false
- add_column table_name, :updated_at, :datetime, :null => false
+ add_column table_name, :created_at, :datetime
+ add_column table_name, :updated_at, :datetime
end
# Removes the timestamp columns (created_at and updated_at) from the table definition.
- # ===== Examples
+ #
# remove_timestamps(:suppliers)
def remove_timestamps(table_name)
remove_column table_name, :updated_at
@@ -520,9 +545,29 @@ module ActiveRecord
end
protected
+ def add_index_sort_order(option_strings, column_names, options = {})
+ if options.is_a?(Hash) && order = options[:order]
+ case order
+ when Hash
+ column_names.each {|name| option_strings[name] += " #{order[name].to_s.upcase}" if order.has_key?(name)}
+ when String
+ column_names.each {|name| option_strings[name] += " #{order.upcase}"}
+ end
+ end
+
+ return option_strings
+ end
+
# Overridden by the mysql adapter for supporting index lengths
def quoted_columns_for_index(column_names, options = {})
- column_names.map {|name| quote_column_name(name) }
+ option_strings = Hash[column_names.map {|name| [name, '']}]
+
+ # add index sort order if supported
+ if supports_index_sort_order?
+ option_strings = add_index_sort_order(option_strings, column_names, options)
+ end
+
+ column_names.map {|name| quote_column_name(name) + option_strings[name]}
end
def options_include_default?(options)
@@ -530,12 +575,15 @@ module ActiveRecord
end
def add_index_options(table_name, column_name, options = {})
- column_names = Array.wrap(column_name)
+ column_names = Array(column_name)
index_name = index_name(table_name, :column => column_names)
if Hash === options # legacy support, since this param was a string
index_type = options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
+ if supports_partial_index?
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
+ end
else
index_type = options
end
@@ -548,7 +596,7 @@ module ActiveRecord
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns]
+ [index_name, index_type, index_columns, index_options]
end
def index_name_for_remove(table_name, options = {})
@@ -562,8 +610,6 @@ module ActiveRecord
end
def columns_for_remove(table_name, *column_names)
- column_names = column_names.flatten
-
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
column_names.map {|column_name| quote_column_name(column_name) }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 4c3a8f7233..c6faae77cc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -3,29 +3,34 @@ require 'bigdecimal'
require 'bigdecimal/util'
require 'active_support/core_ext/benchmark'
require 'active_support/deprecation'
+require 'active_record/connection_adapters/schema_cache'
+require 'monitor'
module ActiveRecord
module ConnectionAdapters # :nodoc:
extend ActiveSupport::Autoload
autoload :Column
+ autoload :ConnectionSpecification
- autoload_under 'abstract' do
- autoload :IndexDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
- autoload :ColumnDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
- autoload :TableDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
- autoload :Table, 'active_record/connection_adapters/abstract/schema_definitions'
+ autoload_at 'active_record/connection_adapters/abstract/schema_definitions' do
+ autoload :IndexDefinition
+ autoload :ColumnDefinition
+ autoload :TableDefinition
+ autoload :Table
+ end
+
+ autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
+ autoload :ConnectionHandler
+ autoload :ConnectionManagement
+ end
+ autoload_under 'abstract' do
autoload :SchemaStatements
autoload :DatabaseStatements
autoload :DatabaseLimits
autoload :Quoting
-
autoload :ConnectionPool
- autoload :ConnectionHandler, 'active_record/connection_adapters/abstract/connection_pool'
- autoload :ConnectionManagement, 'active_record/connection_adapters/abstract/connection_pool'
- autoload :ConnectionSpecification
-
autoload :QueryCache
end
@@ -47,39 +52,49 @@ module ActiveRecord
include DatabaseLimits
include QueryCache
include ActiveSupport::Callbacks
+ include MonitorMixin
define_callbacks :checkout, :checkin
- attr_accessor :visitor
-
- def initialize(connection, logger = nil) #:nodoc:
- @active = nil
- @connection, @logger = connection, logger
+ attr_accessor :visitor, :pool
+ attr_reader :schema_cache, :last_use, :in_use, :logger
+ alias :in_use? :in_use
+
+ def initialize(connection, logger = nil, pool = nil) #:nodoc:
+ super()
+
+ @active = nil
+ @connection = connection
+ @in_use = false
+ @instrumenter = ActiveSupport::Notifications.instrumenter
+ @last_use = false
+ @logger = logger
+ @open_transactions = 0
+ @pool = pool
+ @query_cache = Hash.new { |h,sql| h[sql] = {} }
@query_cache_enabled = false
- @query_cache = Hash.new { |h,sql| h[sql] = {} }
- @open_transactions = 0
- @instrumenter = ActiveSupport::Notifications.instrumenter
- @visitor = nil
- end
-
- # Returns a visitor instance for this adaptor, which conforms to the Arel::ToSql interface
- def self.visitor_for(pool) # :nodoc:
- adapter = pool.spec.config[:adapter]
-
- if Arel::Visitors::VISITORS[adapter]
- ActiveSupport::Deprecation.warn(
- "Arel::Visitors::VISITORS is deprecated and will be removed. Database adapters " \
- "should define a visitor_for method which returns the appropriate visitor for " \
- "the database. For example, MysqlAdapter.visitor_for(pool) returns " \
- "Arel::Visitors::MySQL.new(pool)."
- )
-
- Arel::Visitors::VISITORS[adapter].new(pool)
- else
- Arel::Visitors::ToSql.new(pool)
+ @schema_cache = SchemaCache.new self
+ @visitor = nil
+ end
+
+ def lease
+ synchronize do
+ unless in_use
+ @in_use = true
+ @last_use = Time.now
+ end
end
end
+ def schema_cache=(cache)
+ cache.connection = self
+ @schema_cache = cache
+ end
+
+ def expire
+ @in_use = false
+ end
+
# Returns the human-readable name of the adapter. Use mixed case - one
# can always use downcase if needed.
def adapter_name
@@ -130,17 +145,28 @@ module ActiveRecord
false
end
- # QUOTING ==================================================
+ # Does this adapter support index sort order?
+ def supports_index_sort_order?
+ false
+ end
+
+ # Does this adapter support partial indices?
+ def supports_partial_index?
+ false
+ end
- # Override to return the quoted table name. Defaults to column quoting.
- def quote_table_name(name)
- quote_column_name(name)
+ # Does this adapter support explain? As of this writing sqlite3,
+ # mysql2, and postgresql are the only ones that do.
+ def supports_explain?
+ false
end
+ # QUOTING ==================================================
+
# Returns a bind substitution value given a +column+ and list of current
# +binds+
def substitute_at(column, index)
- Arel.sql '?'
+ Arel::Nodes::BindParam.new '?'
end
# REFERENTIAL INTEGRITY ====================================
@@ -246,28 +272,32 @@ module ActiveRecord
"active_record_#{open_transactions}"
end
- protected
+ # Check the connection back in to the connection pool
+ def close
+ pool.checkin self
+ end
- def log(sql, name = "SQL", binds = [])
- @instrumenter.instrument(
- "sql.active_record",
- :sql => sql,
- :name => name,
- :connection_id => object_id,
- :binds => binds) { yield }
- rescue Exception => e
- message = "#{e.class.name}: #{e.message}: #{sql}"
- @logger.debug message if @logger
- exception = translate_exception(e, message)
- exception.set_backtrace e.backtrace
- raise exception
- end
+ protected
- def translate_exception(e, message)
- # override in derived class
- ActiveRecord::StatementInvalid.new(message)
- end
+ def log(sql, name = "SQL", binds = [])
+ @instrumenter.instrument(
+ "sql.active_record",
+ :sql => sql,
+ :name => name,
+ :connection_id => object_id,
+ :binds => binds) { yield }
+ rescue Exception => e
+ message = "#{e.class.name}: #{e.message}: #{sql}"
+ @logger.error message if @logger
+ exception = translate_exception(e, message)
+ exception.set_backtrace e.backtrace
+ raise exception
+ end
+ def translate_exception(exception, message)
+ # override in derived class
+ ActiveRecord::StatementInvalid.new(message)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index dd573ba569..9794c5663e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/object/blank'
+require 'arel/visitors/bind_visitor'
module ActiveRecord
module ConnectionAdapters
@@ -122,15 +123,21 @@ module ActiveRecord
:boolean => { :name => "tinyint", :limit => 1 }
}
+ class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
+ include Arel::Visitors::BindVisitor
+ end
+
# FIXME: Make the first parameter more similar for the two adapters
def initialize(connection, logger, connection_options, config)
super(connection, logger)
@connection_options, @config = connection_options, config
@quoted_column_names, @quoted_table_names = {}, {}
- end
- def self.visitor_for(pool) # :nodoc:
- Arel::Visitors::MySQL.new(pool)
+ if config.fetch(:prepared_statements) { true }
+ @visitor = Arel::Visitors::MySQL.new self
+ else
+ @visitor = BindSubstitution.new self
+ end
end
def adapter_name #:nodoc:
@@ -155,6 +162,12 @@ module ActiveRecord
true
end
+ # Technically MySQL allows to create indexes with the sort order syntax
+ # but at the moment (5.5) it doesn't yet implement them
+ def supports_index_sort_order?
+ true
+ end
+
def native_database_types
NATIVE_DATABASE_TYPES
end
@@ -238,7 +251,7 @@ module ActiveRecord
end
# MysqlAdapter has to free a result after using it, so we use this method to write
- # stuff in a abstract way without concerning ourselves about whether it needs to be
+ # stuff in an abstract way without concerning ourselves about whether it needs to be
# explicitly freed or not.
def execute_and_free(sql, name = nil) #:nodoc:
yield execute(sql, name)
@@ -281,19 +294,10 @@ module ActiveRecord
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
- # these, we must use a subquery. However, MySQL is too stupid to create a
- # temporary table for this automatically, so we have to give it some prompting
- # in the form of a subsubquery. Ugh!
+ # these, we must use a subquery.
def join_to_update(update, select) #:nodoc:
if select.limit || select.offset || select.orders.any?
- subsubselect = select.clone
- subsubselect.projections = [update.key]
-
- subselect = Arel::SelectManager.new(select.engine)
- subselect.project Arel.sql(update.key.name)
- subselect.from subsubselect.as('__active_record_temp')
-
- update.where update.key.in(subselect)
+ super
else
update.table select.source
update.wheres = select.constraints
@@ -309,11 +313,11 @@ module ActiveRecord
sql = "SHOW TABLES"
end
- select_all(sql).map do |table|
+ select_all(sql).map { |table|
table.delete('Table_type')
sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
exec_without_stmt(sql).first['Create Table'] + ";\n\n"
- end.join("")
+ }.join
end
# Drops the database specified on the +name+ attribute
@@ -360,8 +364,10 @@ module ActiveRecord
show_variable 'collation_database'
end
- def tables(name = nil, database = nil) #:nodoc:
- sql = ["SHOW TABLES", database].compact.join(' IN ')
+ def tables(name = nil, database = nil, like = nil) #:nodoc:
+ sql = "SHOW TABLES "
+ sql << "IN #{quote_table_name(database)} " if database
+ sql << "LIKE #{quote(like)}" if like
execute_and_free(sql, 'SCHEMA') do |result|
result.collect { |field| field.first }
@@ -369,7 +375,8 @@ module ActiveRecord
end
def table_exists?(name)
- return true if super
+ return false unless name
+ return true if tables(nil, nil, name).any?
name = name.to_s
schema, table = name.split('.', 2)
@@ -379,7 +386,7 @@ module ActiveRecord
schema = nil
end
- tables(nil, schema).include? table
+ tables(nil, schema, table).any?
end
# Returns an array of indexes for the given table.
@@ -403,7 +410,7 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name, name = nil)#:nodoc:
+ def columns(table_name)#:nodoc:
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
@@ -421,7 +428,7 @@ module ActiveRecord
table, arguments = args.shift, args
method = :"#{command}_sql"
- if respond_to?(method)
+ if respond_to?(method, true)
send(method, table, *arguments)
else
raise "Unknown method called : #{method}(#{arguments.inspect})"
@@ -468,15 +475,26 @@ module ActiveRecord
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- return super unless type.to_s == 'integer'
-
- case limit
- when 1; 'tinyint'
- when 2; 'smallint'
- when 3; 'mediumint'
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ case type.to_s
+ when 'integer'
+ case limit
+ when 1; 'tinyint'
+ when 2; 'smallint'
+ when 3; 'mediumint'
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ end
+ when 'text'
+ case limit
+ when 0..0xff; 'tinytext'
+ when nil, 0x100..0xffff; 'text'
+ when 0x10000..0xffffff; 'mediumtext'
+ when 0x1000000..0xffffffff; 'longtext'
+ else raise(ActiveRecordError, "No text type has character length #{limit}")
+ end
+ else
+ super
end
end
@@ -496,9 +514,14 @@ module ActiveRecord
# Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table)
- execute_and_free("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') do |result|
- keys = each_hash(result).select { |row| row[:Key] == 'PRI' }.map { |row| row[:Field] }
- keys.length == 1 ? [keys.first, nil] : nil
+ execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
+ create_table = each_hash(result).first[:"Create Table"]
+ if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/
+ keys = $1.split(",").map { |key| key.delete('`"') }
+ keys.length == 1 ? [keys.first, nil] : nil
+ else
+ nil
+ end
end
end
@@ -526,17 +549,40 @@ module ActiveRecord
protected
- def quoted_columns_for_index(column_names, options = {})
- length = options[:length] if options.is_a?(Hash)
+ # MySQL is too stupid to create a temporary table for use subquery, so we have
+ # to give it some prompting in the form of a subsubquery. Ugh!
+ def subquery_for(key, select)
+ subsubselect = select.clone
+ subsubselect.projections = [key]
- case length
- when Hash
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
- when Fixnum
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
- else
- column_names.map {|name| quote_column_name(name) }
+ subselect = Arel::SelectManager.new(select.engine)
+ subselect.project Arel.sql(key.name)
+ subselect.from subsubselect.as('__active_record_temp')
+ end
+
+ def add_index_length(option_strings, column_names, options = {})
+ if options.is_a?(Hash) && length = options[:length]
+ case length
+ when Hash
+ column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
+ when Fixnum
+ column_names.each {|name| option_strings[name] += "(#{length})"}
+ end
end
+
+ return option_strings
+ end
+
+ def quoted_columns_for_index(column_names, options = {})
+ option_strings = Hash[column_names.map {|name| [name, '']}]
+
+ # add index length
+ option_strings = add_index_length(option_strings, column_names, options)
+
+ # add index sort order
+ option_strings = add_index_sort_order(option_strings, column_names, options)
+
+ column_names.map {|name| quote_column_name(name) + option_strings[name]}
end
def translate_exception(exception, message)
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index a7856539b7..01bd3ae26c 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -1,12 +1,13 @@
require 'set'
+require 'active_support/deprecation'
module ActiveRecord
# :stopdoc:
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
- FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
module Format
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
@@ -66,6 +67,25 @@ module ActiveRecord
end
end
+ def binary?
+ type == :binary
+ end
+
+ # Casts a Ruby value to something appropriate for writing to the database.
+ def type_cast_for_write(value)
+ return value unless number?
+
+ if value == false
+ 0
+ elsif value == true
+ 1
+ elsif value.is_a?(String) && value.blank?
+ nil
+ else
+ value
+ end
+ end
+
# Casts value (which is a String) to an appropriate instance.
def type_cast(value)
return nil if value.nil?
@@ -75,12 +95,12 @@ module ActiveRecord
case type
when :string, :text then value
- when :integer then value.to_i rescue value ? 1 : 0
+ when :integer then value.to_i
when :float then value.to_f
when :decimal then klass.value_to_decimal(value)
when :datetime, :timestamp then klass.string_to_time(value)
when :time then klass.string_to_dummy_time(value)
- when :date then klass.string_to_date(value)
+ when :date then klass.value_to_date(value)
when :binary then klass.binary_to_string(value)
when :boolean then klass.value_to_boolean(value)
else value
@@ -88,6 +108,9 @@ module ActiveRecord
end
def type_cast_code(var_name)
+ ActiveSupport::Deprecation.warn("Column#type_cast_code is deprecated in favor of" \
+ "using Column#type_cast only, and it is going to be removed in future Rails versions.")
+
klass = self.class.name
case type
@@ -97,9 +120,11 @@ module ActiveRecord
when :decimal then "#{klass}.value_to_decimal(#{var_name})"
when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
when :time then "#{klass}.string_to_dummy_time(#{var_name})"
- when :date then "#{klass}.string_to_date(#{var_name})"
+ when :date then "#{klass}.value_to_date(#{var_name})"
when :binary then "#{klass}.binary_to_string(#{var_name})"
when :boolean then "#{klass}.value_to_boolean(#{var_name})"
+ when :hstore then "#{klass}.string_to_hstore(#{var_name})"
+ when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})"
else var_name
end
end
@@ -132,23 +157,27 @@ module ActiveRecord
value
end
- def string_to_date(string)
- return string unless string.is_a?(String)
- return nil if string.empty?
-
- fast_string_to_date(string) || fallback_string_to_date(string)
+ def value_to_date(value)
+ if value.is_a?(String)
+ return nil if value.blank?
+ fast_string_to_date(value) || fallback_string_to_date(value)
+ elsif value.respond_to?(:to_date)
+ value.to_date
+ else
+ value
+ end
end
def string_to_time(string)
return string unless string.is_a?(String)
- return nil if string.empty?
+ return nil if string.blank?
fast_string_to_time(string) || fallback_string_to_time(string)
end
def string_to_dummy_time(string)
return string unless string.is_a?(String)
- return nil if string.empty?
+ return nil if string.blank?
string_to_time "2000-01-01 #{string}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
new file mode 100644
index 0000000000..8491d42b86
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -0,0 +1,83 @@
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionSpecification #:nodoc:
+ attr_reader :config, :adapter_method
+
+ def initialize(config, adapter_method)
+ @config, @adapter_method = config, adapter_method
+ end
+
+ def initialize_dup(original)
+ @config = original.config.dup
+ end
+
+ ##
+ # Builds a ConnectionSpecification from user input
+ class Resolver # :nodoc:
+ attr_reader :config, :klass, :configurations
+
+ def initialize(config, configurations)
+ @config = config
+ @configurations = configurations
+ end
+
+ def spec
+ case config
+ when nil
+ raise AdapterNotSpecified unless defined?(Rails.env)
+ resolve_string_connection Rails.env
+ when Symbol, String
+ resolve_string_connection config.to_s
+ when Hash
+ resolve_hash_connection config
+ end
+ end
+
+ private
+ def resolve_string_connection(spec) # :nodoc:
+ hash = configurations.fetch(spec) do |k|
+ connection_url_to_hash(k)
+ end
+
+ raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
+
+ resolve_hash_connection hash
+ end
+
+ def resolve_hash_connection(spec) # :nodoc:
+ spec = spec.symbolize_keys
+
+ raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
+
+ begin
+ require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
+ rescue LoadError => e
+ raise LoadError, "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e.message})", e.backtrace
+ end
+
+ adapter_method = "#{spec[:adapter]}_connection"
+
+ ConnectionSpecification.new(spec, adapter_method)
+ end
+
+ def connection_url_to_hash(url) # :nodoc:
+ config = URI.parse url
+ adapter = config.scheme
+ adapter = "postgresql" if adapter == "postgres"
+ spec = { :adapter => adapter,
+ :username => config.user,
+ :password => config.password,
+ :port => config.port,
+ :database => config.path.sub(%r{^/},""),
+ :host => config.host }
+ spec.reject!{ |_,value| !value }
+ if config.query
+ options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
+ spec.merge!(options)
+ end
+ spec
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 971f3c35f3..350ccce03d 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,12 +1,12 @@
require 'active_record/connection_adapters/abstract_mysql_adapter'
-gem 'mysql2', '~> 0.3.6'
+gem 'mysql2', '~> 0.3.10'
require 'mysql2'
module ActiveRecord
- class Base
+ module ConnectionHandling
# Establishes a connection to the database that's used by all Active Record objects.
- def self.mysql2_connection(config)
+ def mysql2_connection(config)
config[:username] = 'root' if config[:username].nil?
if Mysql2::Client.const_defined? :FOUND_ROWS
@@ -32,9 +32,14 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super
+ @visitor = BindSubstitution.new self
configure_connection
end
+ def supports_explain?
+ true
+ end
+
# HELPER METHODS ===========================================
def each_hash(result) # :nodoc:
@@ -61,10 +66,6 @@ module ActiveRecord
@connection.escape(string)
end
- def substitute_at(column, index)
- Arel.sql "\0"
- end
-
# CONNECTION MANAGEMENT ====================================
def active?
@@ -76,6 +77,7 @@ module ActiveRecord
disconnect!
connect
end
+ alias :reset! :reconnect!
# Disconnects from the database if already connected.
# Otherwise, this method does nothing.
@@ -86,12 +88,81 @@ module ActiveRecord
end
end
- def reset!
- disconnect!
- connect
+ # DATABASE STATEMENTS ======================================
+
+ def explain(arel, binds = [])
+ sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
+ start = Time.now
+ result = exec_query(sql, 'EXPLAIN', binds)
+ elapsed = Time.now - start
+
+ ExplainPrettyPrinter.new.pp(result, elapsed)
end
- # DATABASE STATEMENTS ======================================
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
+ # MySQL shell:
+ #
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
+ # 2 rows in set (0.00 sec)
+ #
+ # This is an exercise in Ruby hyperrealism :).
+ def pp(result, elapsed)
+ widths = compute_column_widths(result)
+ separator = build_separator(widths)
+
+ pp = []
+
+ pp << separator
+ pp << build_cells(result.columns, widths)
+ pp << separator
+
+ result.rows.each do |row|
+ pp << build_cells(row, widths)
+ end
+
+ pp << separator
+ pp << build_footer(result.rows.length, elapsed)
+
+ pp.join("\n") + "\n"
+ end
+
+ private
+
+ def compute_column_widths(result)
+ [].tap do |widths|
+ result.columns.each_with_index do |column, i|
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
+ widths << cells_in_column.map(&:length).max
+ end
+ end
+ end
+
+ def build_separator(widths)
+ padding = 1
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
+ end
+
+ def build_cells(items, widths)
+ cells = []
+ items.each_with_index do |item, i|
+ item = 'NULL' if item.nil?
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
+ cells << item.to_s.send(justifier, widths[i])
+ end
+ '| ' + cells.join(' | ') + ' |'
+ end
+
+ def build_footer(nrows, elapsed)
+ rows_label = nrows == 1 ? 'row' : 'rows'
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
+ end
+ end
# FIXME: re-enable the following once a "better" query_cache solution is in core
#
@@ -146,8 +217,7 @@ module ActiveRecord
# Returns an array of record hashes with the column names as keys and
# column values as values.
def select(sql, name = nil, binds = [])
- binds = binds.dup
- exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
+ exec_query(sql, name)
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -156,18 +226,12 @@ module ActiveRecord
end
alias :create :insert_sql
- def exec_insert(sql, name, binds)
- binds = binds.dup
-
- # Pretend to support bind parameters
- execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
+ execute to_sql(sql, binds), name
end
def exec_delete(sql, name, binds)
- binds = binds.dup
-
- # Pretend to support bind parameters
- execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
+ execute to_sql(sql, binds), name
@connection.affected_rows
end
alias :exec_update :exec_delete
@@ -189,6 +253,14 @@ module ActiveRecord
# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
variable_assignments = ['SQL_AUTO_IS_NULL=0']
+
+ # Make MySQL reject illegal values rather than truncating or
+ # blanking them. See
+ # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
+ if @config.fetch(:strict, true)
+ variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'"
+ end
+
encoding = @config[:encoding]
# make sure we set the encoding
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index f092edecda..0b6734b010 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -18,9 +18,9 @@ class Mysql
end
module ActiveRecord
- class Base
+ module ConnectionHandling
# Establishes a connection to the database that's used by all Active Record objects.
- def self.mysql_connection(config) # :nodoc:
+ def mysql_connection(config) # :nodoc:
config = config.symbolize_keys
host = config[:host]
port = config[:port]
@@ -53,6 +53,7 @@ module ActiveRecord
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
+ # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html)
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -119,7 +120,7 @@ module ActiveRecord
private
def cache
- @cache[$$]
+ @cache[Process.pid]
end
end
@@ -224,52 +225,48 @@ 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
+ # 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,
+ }
# Get the client encoding for this database
def client_encoding
@@ -408,11 +405,18 @@ module ActiveRecord
# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
+
+ # Make MySQL reject illegal values rather than truncating or
+ # blanking them. See
+ # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
+ if @config.fetch(:strict, true)
+ execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging)
+ end
end
def select(sql, name = nil, binds = [])
@connection.query_with_result = true
- rows = exec_query(sql, name, binds).to_a
+ rows = exec_query(sql, name, binds)
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
rows
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
new file mode 100644
index 0000000000..df3d5e4657
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -0,0 +1,252 @@
+require 'active_record/connection_adapters/abstract_adapter'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter < AbstractAdapter
+ module OID
+ class Type
+ def type; end
+
+ def type_cast_for_write(value)
+ value
+ end
+ end
+
+ class Identity < Type
+ def type_cast(value)
+ value
+ end
+ end
+
+ class Bytea < Type
+ def type_cast(value)
+ PGconn.unescape_bytea value
+ end
+ end
+
+ class Money < Type
+ def type_cast(value)
+ return if value.nil?
+
+ # Because money output is formatted according to the locale, there are two
+ # cases to consider (note the decimal separators):
+ # (1) $12,345,678.12
+ # (2) $12.345.678,12
+
+ case value
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ value.gsub!(/[^-\d.]/, '')
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
+ value.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
+ end
+
+ ConnectionAdapters::Column.value_to_decimal value
+ end
+ end
+
+ class Vector < Type
+ attr_reader :delim, :subtype
+
+ # +delim+ corresponds to the `typdelim` column in the pg_types
+ # table. +subtype+ is derived from the `typelem` column in the
+ # pg_types table.
+ def initialize(delim, subtype)
+ @delim = delim
+ @subtype = subtype
+ end
+
+ # FIXME: this should probably split on +delim+ and use +subtype+
+ # to cast the values. Unfortunately, the current Rails behavior
+ # is to just return the string.
+ def type_cast(value)
+ value
+ end
+ end
+
+ class Integer < Type
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_i rescue value ? 1 : 0
+ end
+ end
+
+ class Boolean < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::Column.value_to_boolean value
+ end
+ end
+
+ class Timestamp < Type
+ def type; :timestamp; end
+
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is PG
+ # specific
+ ConnectionAdapters::PostgreSQLColumn.string_to_time value
+ end
+ end
+
+ class Date < Type
+ def type; :datetime; end
+
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is PG
+ # specific
+ ConnectionAdapters::Column.value_to_date value
+ end
+ end
+
+ class Time < Type
+ def type_cast(value)
+ return if value.nil?
+
+ # FIXME: probably we can improve this since we know it is PG
+ # specific
+ ConnectionAdapters::Column.string_to_dummy_time value
+ end
+ end
+
+ class Float < Type
+ def type_cast(value)
+ return if value.nil?
+
+ value.to_f
+ end
+ end
+
+ class Decimal < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::Column.value_to_decimal value
+ end
+ end
+
+ class Hstore < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::PostgreSQLColumn.string_to_hstore value
+ end
+ end
+
+ class Cidr < Type
+ def type_cast(value)
+ return if value.nil?
+
+ ConnectionAdapters::PostgreSQLColumn.string_to_cidr value
+ end
+ end
+
+ class TypeMap
+ def initialize
+ @mapping = {}
+ end
+
+ def []=(oid, type)
+ @mapping[oid] = type
+ end
+
+ def [](oid)
+ @mapping[oid]
+ end
+
+ def key?(oid)
+ @mapping.key? oid
+ end
+
+ def fetch(ftype, fmod)
+ # The type for the numeric depends on the width of the field,
+ # so we'll do something special here.
+ #
+ # When dealing with decimal columns:
+ #
+ # places after decimal = fmod - 4 & 0xffff
+ # places before decimal = (fmod - 4) >> 16 & 0xffff
+ if ftype == 1700 && (fmod - 4 & 0xffff).zero?
+ ftype = 23
+ end
+
+ @mapping.fetch(ftype) { |oid| yield oid, fmod }
+ end
+ end
+
+ TYPE_MAP = TypeMap.new # :nodoc:
+
+ # When the PG adapter connects, the pg_type table is queried. The
+ # key of this hash maps to the `typname` column from the table.
+ # TYPE_MAP is then dynamically built with oids as the key and type
+ # objects as values.
+ NAMES = Hash.new { |h,k| # :nodoc:
+ h[k] = OID::Identity.new
+ }
+
+ # Register an OID type named +name+ with a typcasting object in
+ # +type+. +name+ should correspond to the `typname` column in
+ # the `pg_type` table.
+ def self.register_type(name, type)
+ NAMES[name] = type
+ end
+
+ # Alias the +old+ type to the +new+ type.
+ def self.alias_type(new, old)
+ NAMES[new] = NAMES[old]
+ end
+
+ # Is +name+ a registered type?
+ def self.registered_type?(name)
+ NAMES.key? name
+ end
+
+ register_type 'int2', OID::Integer.new
+ alias_type 'int4', 'int2'
+ alias_type 'int8', 'int2'
+ alias_type 'oid', 'int2'
+
+ register_type 'numeric', OID::Decimal.new
+ register_type 'text', OID::Identity.new
+ alias_type 'varchar', 'text'
+ alias_type 'char', 'text'
+ alias_type 'bpchar', 'text'
+ alias_type 'xml', 'text'
+
+ # FIXME: why are we keeping these types as strings?
+ alias_type 'tsvector', 'text'
+ alias_type 'interval', 'text'
+ alias_type 'bit', 'text'
+ alias_type 'varbit', 'text'
+ alias_type 'macaddr', 'text'
+
+ # FIXME: I don't think this is correct. We should probably be returning a parsed date,
+ # but the tests pass with a string returned.
+ register_type 'timestamptz', OID::Identity.new
+
+ register_type 'money', OID::Money.new
+ register_type 'bytea', OID::Bytea.new
+ register_type 'bool', OID::Boolean.new
+
+ register_type 'float4', OID::Float.new
+ alias_type 'float8', 'float4'
+
+ register_type 'timestamp', OID::Timestamp.new
+ register_type 'date', OID::Date.new
+ register_type 'time', OID::Time.new
+
+ register_type 'path', OID::Identity.new
+ register_type 'polygon', OID::Identity.new
+ register_type 'circle', OID::Identity.new
+ register_type 'hstore', OID::Hstore.new
+
+ register_type 'cidr', OID::Cidr.new
+ alias_type 'inet', 'cidr'
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index d859843260..14bc95abfe 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,30 +1,36 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_support/core_ext/object/blank'
require 'active_record/connection_adapters/statement_pool'
+require 'active_record/connection_adapters/postgresql/oid'
+require 'arel/visitors/bind_visitor'
# Make sure we're using pg high enough for PGResult#values
gem 'pg', '~> 0.11'
require 'pg'
+require 'ipaddr'
+
module ActiveRecord
- class Base
+ module ConnectionHandling
# Establishes a connection to the database that's used by all Active Record objects
- def self.postgresql_connection(config) # :nodoc:
- config = config.symbolize_keys
- host = config[:host]
- port = config[:port] || 5432
- username = config[:username].to_s if config[:username]
- password = config[:password].to_s if config[:password]
+ def postgresql_connection(config) # :nodoc:
+ conn_params = config.symbolize_keys
- if config.key?(:database)
- database = config[:database]
- else
- raise ArgumentError, "No database specified. Missing argument: database."
+ # Forward any unused config params to PGconn.connect.
+ [:statement_limit, :encoding, :min_messages, :schema_search_path,
+ :schema_order, :adapter, :pool, :wait_timeout, :template,
+ :reaping_frequency, :insert_returning].each do |key|
+ conn_params.delete key
end
+ conn_params.delete_if { |k,v| v.nil? }
+
+ # Map ActiveRecords param names to PGs.
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
# The postgres drivers don't allow the creation of an unconnected PGconn object,
# so just pass a nil connection object for the time being.
- ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
+ ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config)
end
end
@@ -32,7 +38,8 @@ module ActiveRecord
# PostgreSQL-specific extensions to column definitions in a table.
class PostgreSQLColumn < Column #:nodoc:
# Instantiates a new PostgreSQL column definition in a table.
- def initialize(name, default, sql_type = nil, null = true)
+ def initialize(name, default, oid_type, sql_type = nil, null = true)
+ @oid_type = oid_type
super(name, self.class.extract_value_from_default(default), sql_type, null)
end
@@ -49,161 +56,242 @@ module ActiveRecord
super
end
end
- end
- # :startdoc:
- private
- def extract_limit(sql_type)
- case sql_type
- when /^bigint/i; 8
- when /^smallint/i; 2
- else super
+ def hstore_to_string(object)
+ if Hash === object
+ object.map { |k,v|
+ "#{escape_hstore(k)}=>#{escape_hstore(v)}"
+ }.join ','
+ else
+ object
end
end
- # Extracts the scale from PostgreSQL-specific data types.
- def extract_scale(sql_type)
- # Money type has a fixed scale of 2.
- sql_type =~ /^money/ ? 2 : super
+ def string_to_hstore(string)
+ if string.nil?
+ nil
+ elsif String === string
+ Hash[string.scan(HstorePair).map { |k,v|
+ v = v.upcase == 'NULL' ? nil : v.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
+ k = k.gsub(/^"(.*)"$/,'\1').gsub(/\\(.)/, '\1')
+ [k,v]
+ }]
+ else
+ string
+ end
end
- # Extracts the precision from PostgreSQL-specific data types.
- def extract_precision(sql_type)
- if sql_type == 'money'
- self.class.money_precision
+ def string_to_cidr(string)
+ if string.nil?
+ nil
+ elsif String === string
+ IPAddr.new(string)
else
- super
+ string
end
+
end
- # Maps PostgreSQL-specific data types to logical Rails types.
- def simplified_type(field_type)
- case field_type
- # Numeric and monetary types
- when /^(?:real|double precision)$/
- :float
- # Monetary types
- when 'money'
- :decimal
- # Character types
- when /^(?:character varying|bpchar)(?:\(\d+\))?$/
- :string
- # Binary data types
- when 'bytea'
- :binary
- # Date/time types
- when /^timestamp with(?:out)? time zone$/
- :datetime
- when 'interval'
- :string
- # Geometric types
- when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
- :string
- # Network address types
- when /^(?:cidr|inet|macaddr)$/
- :string
- # Bit strings
- when /^bit(?: varying)?(?:\(\d+\))?$/
- :string
- # XML type
- when 'xml'
- :xml
- # tsvector type
- when 'tsvector'
- :tsvector
- # Arrays
- when /^\D+\[\]$/
- :string
- # Object identifier types
- when 'oid'
- :integer
- # UUID type
- when 'uuid'
- :string
- # Small and big integer types
- when /^(?:small|big)int$/
- :integer
- # Pass through all types that are not specific to PostgreSQL.
- else
- super
+ def cidr_to_string(object)
+ if IPAddr === object
+ "#{object.to_s}/#{object.instance_variable_get(:@mask_addr).to_s(2).count('1')}"
+ else
+ object
end
end
- # Extracts the value from a PostgreSQL column default definition.
- def self.extract_value_from_default(default)
- case default
- # This is a performance optimization for Ruby 1.9.2 in development.
- # If the value is nil, we return nil straight away without checking
- # the regular expressions. If we check each regular expression,
- # Regexp#=== will call NilClass#to_str, which will trigger
- # method_missing (defined by whiny nil in ActiveSupport) which
- # makes this method very very slow.
- when NilClass
- nil
- # Numeric types
- when /\A\(?(-?\d+(\.\d*)?\)?)\z/
- $1
- # Character types
- when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
- $1
- # Character types (8.1 formatting)
- when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
- $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
- # Binary data types
- when /\A'(.*)'::bytea\z/m
- $1
- # Date/time types
- when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
- $1
- when /\A'(.*)'::interval\z/
- $1
- # Boolean type
- when 'true'
- true
- when 'false'
- false
- # Geometric types
- when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
- $1
- # Network address types
- when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
- $1
- # Bit string types
- when /\AB'(.*)'::"?bit(?: varying)?"?\z/
- $1
- # XML type
- when /\A'(.*)'::xml\z/m
- $1
- # Arrays
- when /\A'(.*)'::"?\D+"?\[\]\z/
- $1
- # Object identifier types
- when /\A-?\d+\z/
- $1
- else
- # Anything else is blank, some user type, or some function
- # and we can't know the value of that, so return nil.
- nil
- end
+ private
+ HstorePair = begin
+ quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
+ unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
+ /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
+ end
+
+ def escape_hstore(value)
+ value.nil? ? 'NULL'
+ : value == "" ? '""'
+ : '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1')
+ end
+ end
+ # :startdoc:
+
+ # Extracts the value from a PostgreSQL column default definition.
+ def self.extract_value_from_default(default)
+ # This is a performance optimization for Ruby 1.9.2 in development.
+ # If the value is nil, we return nil straight away without checking
+ # the regular expressions. If we check each regular expression,
+ # Regexp#=== will call NilClass#to_str, which will trigger
+ # method_missing (defined by whiny nil in ActiveSupport) which
+ # makes this method very very slow.
+ return default unless default
+
+ case default
+ # Numeric types
+ when /\A\(?(-?\d+(\.\d*)?\)?)\z/
+ $1
+ # Character types
+ when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
+ $1
+ # Character types (8.1 formatting)
+ when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
+ $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
+ # Binary data types
+ when /\A'(.*)'::bytea\z/m
+ $1
+ # Date/time types
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
+ $1
+ when /\A'(.*)'::interval\z/
+ $1
+ # Boolean type
+ when 'true'
+ true
+ when 'false'
+ false
+ # Geometric types
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
+ $1
+ # Network address types
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
+ $1
+ # Bit string types
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
+ $1
+ # XML type
+ when /\A'(.*)'::xml\z/m
+ $1
+ # Arrays
+ when /\A'(.*)'::"?\D+"?\[\]\z/
+ $1
+ # Hstore
+ when /\A'(.*)'::hstore\z/
+ $1
+ # Object identifier types
+ when /\A-?\d+\z/
+ $1
+ else
+ # Anything else is blank, some user type, or some function
+ # and we can't know the value of that, so return nil.
+ nil
+ end
+ end
+
+ def type_cast(value)
+ return if value.nil?
+ return super if encoded?
+
+ @oid_type.type_cast value
+ end
+
+ private
+ def extract_limit(sql_type)
+ case sql_type
+ when /^bigint/i; 8
+ when /^smallint/i; 2
+ else super
+ end
+ end
+
+ # Extracts the scale from PostgreSQL-specific data types.
+ def extract_scale(sql_type)
+ # Money type has a fixed scale of 2.
+ sql_type =~ /^money/ ? 2 : super
+ end
+
+ # Extracts the precision from PostgreSQL-specific data types.
+ def extract_precision(sql_type)
+ if sql_type == 'money'
+ self.class.money_precision
+ else
+ super
+ end
+ end
+
+ # Maps PostgreSQL-specific data types to logical Rails types.
+ def simplified_type(field_type)
+ case field_type
+ # Numeric and monetary types
+ when /^(?:real|double precision)$/
+ :float
+ # Monetary types
+ when 'money'
+ :decimal
+ when 'hstore'
+ :hstore
+ # Network address types
+ when 'inet'
+ :inet
+ when 'cidr'
+ :cidr
+ when 'macaddr'
+ :macaddr
+ # Character types
+ when /^(?:character varying|bpchar)(?:\(\d+\))?$/
+ :string
+ # Binary data types
+ when 'bytea'
+ :binary
+ # Date/time types
+ when /^timestamp with(?:out)? time zone$/
+ :datetime
+ when 'interval'
+ :string
+ # Geometric types
+ when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
+ :string
+ # Bit strings
+ when /^bit(?: varying)?(?:\(\d+\))?$/
+ :string
+ # XML type
+ when 'xml'
+ :xml
+ # tsvector type
+ when 'tsvector'
+ :tsvector
+ # Arrays
+ when /^\D+\[\]$/
+ :string
+ # Object identifier types
+ when 'oid'
+ :integer
+ # UUID type
+ when 'uuid'
+ :string
+ # Small and big integer types
+ when /^(?:small|big)int$/
+ :integer
+ # Pass through all types that are not specific to PostgreSQL.
+ else
+ super
end
+ end
end
- # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
- # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
+ # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver.
#
# Options:
#
- # * <tt>:host</tt> - Defaults to "localhost".
+ # * <tt>:host</tt> - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets,
+ # the default is to connect to localhost.
# * <tt>:port</tt> - Defaults to 5432.
- # * <tt>:username</tt> - Defaults to nothing.
- # * <tt>:password</tt> - Defaults to nothing.
- # * <tt>:database</tt> - The name of the database. No default, must be provided.
+ # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
+ # * <tt>:password</tt> - Password to be used if the server demands password authentication.
+ # * <tt>:database</tt> - Defaults to be the same as the user name.
# * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
# as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
# * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
# <encoding></tt> call on the connection.
# * <tt>:min_messages</tt> - An optional client min messages that is used in a
# <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
+ # * <tt>:insert_returning</tt> - An optional boolean to control the use or <tt>RETURNING</tt> for <tt>INSERT<tt> statements
+ # defaults to true.
+ #
+ # Any further options are used as connection parameters to libpq. See
+ # http://www.postgresql.org/docs/9.1/static/libpq-connect.html for the
+ # list of parameters.
+ #
+ # In addition, default connection parameters of libpq can be set per environment variables.
+ # See http://www.postgresql.org/docs/9.1/static/libpq-envars.html .
class PostgreSQLAdapter < AbstractAdapter
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
def xml(*args)
@@ -215,6 +303,22 @@ module ActiveRecord
options = args.extract_options!
column(args[0], 'tsvector', options)
end
+
+ def hstore(name, options = {})
+ column(name, 'hstore', options)
+ end
+
+ def inet(name, options = {})
+ column(name, 'inet', options)
+ end
+
+ def cidr(name, options = {})
+ column(name, 'cidr', options)
+ end
+
+ def macaddr(name, options = {})
+ column(name, 'macaddr', options)
+ end
end
ADAPTER_NAME = 'PostgreSQL'
@@ -233,7 +337,11 @@ module ActiveRecord
:binary => { :name => "bytea" },
:boolean => { :name => "boolean" },
:xml => { :name => "xml" },
- :tsvector => { :name => "tsvector" }
+ :tsvector => { :name => "tsvector" },
+ :hstore => { :name => "hstore" },
+ :inet => { :name => "inet" },
+ :cidr => { :name => "cidr" },
+ :macaddr => { :name => "macaddr" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -247,6 +355,14 @@ module ActiveRecord
true
end
+ def supports_index_sort_order?
+ true
+ end
+
+ def supports_partial_index?
+ true
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -278,19 +394,43 @@ module ActiveRecord
cache.clear
end
+ def delete(sql_key)
+ dealloc cache[sql_key]
+ cache.delete sql_key
+ end
+
private
def cache
- @cache[$$]
+ @cache[Process.pid]
end
def dealloc(key)
- @connection.query "DEALLOCATE #{key}"
+ @connection.query "DEALLOCATE #{key}" if connection_active?
end
+
+ def connection_active?
+ @connection.status == PGconn::CONNECTION_OK
+ rescue PGError
+ false
+ end
+ end
+
+ class BindSubstitution < Arel::Visitors::PostgreSQL # :nodoc:
+ include Arel::Visitors::BindVisitor
end
# Initializes and connects a PostgreSQL adapter.
def initialize(connection, logger, connection_parameters, config)
super(connection, logger)
+
+ if config.fetch(:prepared_statements) { true }
+ @visitor = Arel::Visitors::PostgreSQL.new self
+ else
+ @visitor = BindSubstitution.new self
+ end
+
+ connection_parameters.delete :prepared_statements
+
@connection_parameters, @config = connection_parameters, config
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@@ -305,11 +445,9 @@ module ActiveRecord
raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
end
+ initialize_type_map
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
- end
-
- def self.visitor_for(pool) # :nodoc:
- Arel::Visitors::PostgreSQL.new(pool)
+ @use_insert_returning = @config.key?(:insert_returning) ? @config[:insert_returning] : true
end
# Clears the prepared statements cache.
@@ -378,6 +516,11 @@ module ActiveRecord
true
end
+ # Returns true.
+ def supports_explain?
+ true
+ end
+
# Returns the configured supported identifier length supported by PostgreSQL
def table_alias_length
@table_alias_length ||= query('SHOW max_identifier_length')[0][0].to_i
@@ -387,14 +530,14 @@ module ActiveRecord
# Escapes binary strings for bytea input to the database.
def escape_bytea(value)
- @connection.escape_bytea(value) if value
+ PGconn.escape_bytea(value) if value
end
# Unescapes bytea output from a database to the binary string it represents.
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
# on escaped binary output from database drive.
def unescape_bytea(value)
- @connection.unescape_bytea(value) if value
+ PGconn.unescape_bytea(value) if value
end
# Quotes PostgreSQL-specific data types for SQL input.
@@ -402,9 +545,24 @@ module ActiveRecord
return super unless column
case value
+ when Hash
+ case column.sql_type
+ when 'hstore' then super(PostgreSQLColumn.hstore_to_string(value), column)
+ else super
+ end
+ when IPAddr
+ case column.sql_type
+ when 'inet', 'cidr' then super(PostgreSQLColumn.cidr_to_string(value), column)
+ else super
+ end
when Float
- return super unless value.infinite? && column.type == :datetime
- "'#{value.to_s.downcase}'"
+ if value.infinite? && column.type == :datetime
+ "'#{value.to_s.downcase}'"
+ elsif value.infinite? || value.nan?
+ "'#{value.to_s}'"
+ else
+ super
+ end
when Numeric
return super unless column.sql_type == 'money'
# Not truly string input, so doesn't require (or allow) escape string syntax.
@@ -433,6 +591,12 @@ module ActiveRecord
when String
return super unless 'bytea' == column.sql_type
{ :value => value, :format => 1 }
+ when Hash
+ return super unless 'hstore' == column.sql_type
+ PostgreSQLColumn.hstore_to_string(value)
+ when IPAddr
+ return super unless ['inet','cidr'].includes? column.sql_type
+ PostgreSQLColumn.cidr_to_string(value)
else
super
end
@@ -502,6 +666,48 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
+ def explain(arel, binds = [])
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
+ end
+
+ class ExplainPrettyPrinter # :nodoc:
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
+ # PostgreSQL shell:
+ #
+ # QUERY PLAN
+ # ------------------------------------------------------------------------------
+ # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
+ # Join Filter: (posts.user_id = users.id)
+ # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
+ # Index Cond: (id = 1)
+ # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
+ # Filter: (posts.user_id = 1)
+ # (6 rows)
+ #
+ def pp(result)
+ header = result.columns.first
+ lines = result.rows.map(&:first)
+
+ # We add 2 because there's one char of padding at both sides, note
+ # the extra hyphens in the example above.
+ width = [header, *lines].map(&:length).max + 2
+
+ pp = []
+
+ pp << header.center(width).rstrip
+ pp << '-' * width
+
+ pp += lines.map {|line| " #{line}"}
+
+ nrows = result.rows.length
+ rows_label = nrows == 1 ? 'row' : 'rows'
+ pp << "(#{nrows} #{rows_label})"
+
+ pp.join("\n") + "\n"
+ end
+ end
+
# Executes a SELECT query and returns an array of rows. Each row is an
# array of field values.
def select_rows(sql, name = nil)
@@ -516,8 +722,11 @@ module ActiveRecord
pk = primary_key(table_ref) if table_ref
end
- if pk
+ if pk && use_insert_returning?
select_value("#{sql} RETURNING #{quote_column_name(pk)}")
+ elsif pk
+ super
+ last_insert_id_value(sequence_name || default_sequence_name(table_ref, pk))
else
super
end
@@ -583,7 +792,14 @@ module ActiveRecord
end
def substitute_at(column, index)
- Arel.sql("$#{index + 1}")
+ Arel::Nodes::BindParam.new "$#{index + 1}"
+ end
+
+ class Result < ActiveRecord::Result
+ def initialize(columns, rows, column_types)
+ super(columns, rows)
+ @column_types = column_types
+ end
end
def exec_query(sql, name = 'SQL', binds = [])
@@ -591,7 +807,17 @@ module ActiveRecord
result = binds.empty? ? exec_no_cache(sql, binds) :
exec_cache(sql, binds)
- ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
+ types = {}
+ result.fields.each_with_index do |fname, i|
+ ftype = result.ftype i
+ fmod = result.fmod i
+ types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod|
+ warn "unknown OID: #{fname}(#{oid}) (#{sql})"
+ OID::Identity.new
+ }
+ end
+
+ ret = Result.new(result.fields, result.values, types)
result.clear
return ret
end
@@ -615,11 +841,27 @@ module ActiveRecord
pk = primary_key(table_ref) if table_ref
end
- sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
+ if pk && use_insert_returning?
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}"
+ end
[sql, binds]
end
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
+ val = exec_query(sql, name, binds)
+ if !use_insert_returning? && pk
+ unless sequence_name
+ table_ref = extract_table_ref_from_insert_sql(sql)
+ sequence_name = default_sequence_name(table_ref, pk)
+ return val unless sequence_name
+ end
+ last_insert_id_result(sequence_name)
+ else
+ val
+ end
+ end
+
# Executes an UPDATE query and returns the number of affected tuples.
def update_sql(sql, name = nil)
super.cmd_tuples
@@ -743,16 +985,15 @@ module ActiveRecord
# Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
- schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
result = query(<<-SQL, name)
- SELECT distinct i.relname, d.indisunique, d.indkey, t.oid
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
WHERE i.relkind = 'i'
AND d.indisprimary = 'f'
AND t.relname = '#{table_name}'
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
ORDER BY i.relname
SQL
@@ -761,7 +1002,8 @@ module ActiveRecord
index_name = row[0]
unique = row[1] == 't'
indkey = row[2].split(" ")
- oid = row[3]
+ inddef = row[3]
+ oid = row[4]
columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
SELECT a.attnum, a.attname
@@ -771,15 +1013,24 @@ module ActiveRecord
SQL
column_names = columns.values_at(*indkey).compact
- column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
+
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
+
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where)
end.compact
end
# Returns the list of all column definitions for a table.
- def columns(table_name, name = nil)
+ def columns(table_name)
# Limit, precision, and scale are all handled by the superclass.
- column_definitions(table_name).collect do |column_name, type, default, notnull|
- PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod|
+ oid = OID::TYPE_MAP.fetch(oid.to_i, fmod.to_i) {
+ OID::Identity.new
+ }
+ PostgreSQLColumn.new(column_name, default, oid, type, notnull == 'f')
end
end
@@ -801,6 +1052,27 @@ module ActiveRecord
end_sql
end
+ # Returns an array of schema names.
+ def schema_names
+ query(<<-SQL).flatten
+ SELECT nspname
+ FROM pg_namespace
+ WHERE nspname !~ '^pg_.*'
+ AND nspname NOT IN ('information_schema')
+ ORDER by nspname;
+ SQL
+ end
+
+ # Creates a schema for the given schema name.
+ def create_schema schema_name
+ execute "CREATE SCHEMA #{schema_name}"
+ end
+
+ # Drops the schema for the given schema name.
+ def drop_schema schema_name
+ execute "DROP SCHEMA #{schema_name} CASCADE"
+ end
+
# Sets the schema search path to a string of comma-separated schema names.
# Names beginning with $ have to be quoted (e.g. $user => '$user').
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
@@ -808,14 +1080,14 @@ module ActiveRecord
# This should be not be called manually but set in database.yml.
def schema_search_path=(schema_csv)
if schema_csv
- execute "SET search_path TO #{schema_csv}"
+ execute("SET search_path TO #{schema_csv}", 'SCHEMA')
@schema_search_path = schema_csv
end
end
# Returns the active schema search path.
def schema_search_path
- @schema_search_path ||= query('SHOW search_path')[0][0]
+ @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0]
end
# Returns the current client message level.
@@ -830,7 +1102,9 @@ module ActiveRecord
# Returns the sequence name for a table's primary key or some other specified key.
def default_sequence_name(table_name, pk = nil) #:nodoc:
- serial_sequence(table_name, pk || 'id').split('.').last
+ result = serial_sequence(table_name, pk || 'id')
+ return nil unless result
+ result.split('.').last
rescue ActiveRecord::StatementInvalid
"#{table_name}_#{pk || 'id'}_seq"
end
@@ -868,26 +1142,46 @@ 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, 'SCHEMA').rows.first
- SELECT attr.attname, ns.nspname, seq.relname
- FROM pg_class seq
- INNER JOIN pg_depend dep ON seq.oid = dep.objid
- INNER JOIN pg_attribute attr ON attr.attrelid = dep.refobjid AND attr.attnum = dep.refobjsubid
- INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1]
- INNER JOIN pg_namespace ns ON seq.relnamespace = ns.oid
- WHERE seq.relkind = 'S'
- AND cons.contype = 'p'
- AND dep.refobjid = '#{quote_table_name(table)}'::regclass
+ result = query(<<-end_sql, 'PK and serial sequence')[0]
+ SELECT attr.attname, seq.relname
+ FROM pg_class seq,
+ pg_attribute attr,
+ pg_depend dep,
+ pg_namespace name,
+ pg_constraint cons
+ WHERE seq.oid = dep.objid
+ AND seq.relkind = 'S'
+ AND 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 = '#{quote_table_name(table)}'::regclass
end_sql
- # [primary_key, sequence]
- if result.second == 'public' then
- sequence = result.last
- else
- sequence = result.second+'.'+result.last
+ 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
- [result.first, sequence]
+ [result.first, result.last]
rescue
nil
end
@@ -911,12 +1205,14 @@ module ActiveRecord
# Example:
# rename_table('octopuses', 'octopi')
def rename_table(name, new_name)
+ clear_cache!
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
end
# Adds a new column to the named table.
# See TableDefinition#column for details of the options you can use.
def add_column(table_name, column_name, type, options = {})
+ clear_cache!
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
add_column_options!(add_column_sql, options)
@@ -925,6 +1221,7 @@ module ActiveRecord
# Changes the column of a table.
def change_column(table_name, column_name, type, options = {})
+ clear_cache!
quoted_table_name = quote_table_name(table_name)
execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
@@ -935,10 +1232,12 @@ module ActiveRecord
# Changes the default value of a table column.
def change_column_default(table_name, column_name, default)
+ clear_cache!
execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
end
def change_column_null(table_name, column_name, null, default = nil)
+ clear_cache!
unless null || default.nil?
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
end
@@ -947,6 +1246,7 @@ module ActiveRecord
# Renames a column in a table.
def rename_column(table_name, column_name, new_column_name)
+ clear_cache!
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
end
@@ -964,14 +1264,25 @@ module ActiveRecord
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- return super unless type.to_s == 'integer'
- return 'integer' unless limit
-
- case limit
- when 1, 2; 'smallint'
- when 3, 4; 'integer'
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
+ case type.to_s
+ when 'binary'
+ # PostgreSQL doesn't support limits on binary (bytea) columns.
+ # The hard limit is 1Gb, because of a 32-bit size field, and TOAST.
+ case limit
+ when nil, 0..0x3fffffff; super(type)
+ else raise(ActiveRecordError, "No binary type has byte size #{limit}.")
+ end
+ when 'integer'
+ return 'integer' unless limit
+
+ case limit
+ when 1, 2; 'smallint'
+ when 3, 4; 'integer'
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
+ end
+ else
+ super
end
end
@@ -986,7 +1297,10 @@ module ActiveRecord
# Construct a clean list of column names from the ORDER BY clause, removing
# any ASC/DESC modifiers
- order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') }
+ order_columns = orders.collect do |s|
+ s = s.to_sql unless s.is_a?(String)
+ s.gsub(/\s+(ASC|DESC)\s*(NULLS\s+(FIRST|LAST)\s*)?/i, '')
+ end
order_columns.delete_if { |c| c.blank? }
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
@@ -1012,6 +1326,10 @@ module ActiveRecord
end
end
+ def use_insert_returning?
+ @use_insert_returning
+ end
+
protected
# Returns the version of the connected PostgreSQL server.
def postgresql_version
@@ -1030,27 +1348,74 @@ module ActiveRecord
end
private
+ def initialize_type_map
+ result = execute('SELECT oid, typname, typelem, typdelim FROM pg_type', 'SCHEMA')
+ leaves, nodes = result.partition { |row| row['typelem'] == '0' }
+
+ # populate the leaf nodes
+ leaves.find_all { |row| OID.registered_type? row['typname'] }.each do |row|
+ OID::TYPE_MAP[row['oid'].to_i] = OID::NAMES[row['typname']]
+ end
+
+ # populate composite types
+ nodes.find_all { |row| OID::TYPE_MAP.key? row['typelem'].to_i }.each do |row|
+ vector = OID::Vector.new row['typdelim'], OID::TYPE_MAP[row['typelem'].to_i]
+ OID::TYPE_MAP[row['oid'].to_i] = vector
+ end
+ end
+
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
+
def exec_no_cache(sql, binds)
@connection.async_exec(sql)
end
def exec_cache(sql, binds)
- sql_key = "#{schema_search_path}-#{sql}"
+ begin
+ stmt_key = prepare_statement sql
+
+ # Clear the queue
+ @connection.get_last_result
+ @connection.send_query_prepared(stmt_key, binds.map { |col, val|
+ type_cast(val, col)
+ })
+ @connection.block
+ @connection.get_last_result
+ rescue PGError => e
+ # Get the PG code for the failure. Annoyingly, the code for
+ # prepared statements whose return value may have changed is
+ # FEATURE_NOT_SUPPORTED. Check here for more details:
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
+ begin
+ code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ rescue
+ raise e
+ end
+ if FEATURE_NOT_SUPPORTED == code
+ @statements.delete sql_key(sql)
+ retry
+ else
+ raise e
+ end
+ end
+ end
+
+ # Returns the statement identifier for the client side cache
+ # of statements
+ def sql_key(sql)
+ "#{schema_search_path}-#{sql}"
+ end
+
+ # Prepare the statement if it hasn't been prepared, return
+ # the statement key.
+ def prepare_statement(sql)
+ sql_key = sql_key(sql)
unless @statements.key? sql_key
nextkey = @statements.next_key
@connection.prepare nextkey, sql
@statements[sql_key] = nextkey
end
-
- key = @statements[sql_key]
-
- # Clear the queue
- @connection.get_last_result
- @connection.send_query_prepared(key, binds.map { |col, val|
- type_cast(val, col)
- })
- @connection.block
- @connection.get_last_result
+ @statements[sql_key]
end
# The internal PostgreSQL identifier of the money data type.
@@ -1061,7 +1426,7 @@ module ActiveRecord
# Connects to a PostgreSQL server and sets up the adapter depending on the
# connected server's characteristics.
def connect
- @connection = PGconn.connect(*@connection_parameters)
+ @connection = PGconn.connect(@connection_parameters)
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
@@ -1094,14 +1459,21 @@ module ActiveRecord
# Returns the current ID of a table's sequence.
def last_insert_id(sequence_name) #:nodoc:
- r = exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
- Integer(r.rows.first.first)
+ Integer(last_insert_id_value(sequence_name))
+ end
+
+ def last_insert_id_value(sequence_name)
+ last_insert_id_result(sequence_name).rows.first.first
+ end
+
+ def last_insert_id_result(sequence_name) #:nodoc:
+ exec_query("SELECT currval($1)", 'SQL', [[nil, sequence_name]])
end
# Executes a SELECT query and returns the results, performing any data type
# conversions that are required to be performed here instead of in PostgreSQLColumn.
def select(sql, name = nil, binds = [])
- exec_query(sql, name, binds).to_a
+ exec_query(sql, name, binds)
end
def select_raw(sql, name = nil)
@@ -1132,7 +1504,7 @@ module ActiveRecord
# - ::regclass is a function that gives the id for a table name
def column_definitions(table_name) #:nodoc:
exec_query(<<-end_sql, 'SCHEMA').rows
- SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull, a.atttypid, a.atttypmod
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
new file mode 100644
index 0000000000..aad1f9a7ef
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -0,0 +1,82 @@
+module ActiveRecord
+ module ConnectionAdapters
+ class SchemaCache
+ attr_reader :columns, :columns_hash, :primary_keys, :tables, :version
+ attr_accessor :connection
+
+ def initialize(conn)
+ @connection = conn
+
+ @columns = {}
+ @columns_hash = {}
+ @primary_keys = {}
+ @tables = {}
+ prepare_default_proc
+ end
+
+ # A cached lookup for table existence.
+ def table_exists?(name)
+ return @tables[name] if @tables.key? name
+
+ @tables[name] = connection.table_exists?(name)
+ end
+
+ # Add internal cache for table with +table_name+.
+ def add(table_name)
+ if table_exists?(table_name)
+ @primary_keys[table_name]
+ @columns[table_name]
+ @columns_hash[table_name]
+ end
+ end
+
+ # Clears out internal caches
+ def clear!
+ @columns.clear
+ @columns_hash.clear
+ @primary_keys.clear
+ @tables.clear
+ @version = nil
+ end
+
+ # Clear out internal caches for table with +table_name+.
+ def clear_table_cache!(table_name)
+ @columns.delete table_name
+ @columns_hash.delete table_name
+ @primary_keys.delete table_name
+ @tables.delete table_name
+ end
+
+ def marshal_dump
+ # if we get current version during initialization, it happens stack over flow.
+ @version = ActiveRecord::Migrator.current_version
+ [@version] + [:@columns, :@columns_hash, :@primary_keys, :@tables].map do |val|
+ self.instance_variable_get(val).inject({}) { |h, v| h[v[0]] = v[1]; h }
+ end
+ end
+
+ def marshal_load(array)
+ @version, @columns, @columns_hash, @primary_keys, @tables = array
+ prepare_default_proc
+ end
+
+ private
+
+ def prepare_default_proc
+ @columns.default_proc = Proc.new do |h, table_name|
+ h[table_name] = connection.columns(table_name)
+ end
+
+ @columns_hash.default_proc = Proc.new do |h, table_name|
+ h[table_name] = Hash[columns[table_name].map { |col|
+ [col.name, col]
+ }]
+ end
+
+ @primary_keys.default_proc = Proc.new do |h, table_name|
+ h[table_name] = table_exists?(table_name) ? connection.primary_key(table_name) : nil
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 0a0da0b5d3..d4ffa82b17 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,12 +1,14 @@
-require 'active_record/connection_adapters/sqlite_adapter'
+require 'active_record/connection_adapters/abstract_adapter'
+require 'active_record/connection_adapters/statement_pool'
+require 'arel/visitors/bind_visitor'
-gem 'sqlite3', '~> 1.3.4'
+gem 'sqlite3', '~> 1.3.6'
require 'sqlite3'
module ActiveRecord
- class Base
+ module ConnectionHandling
# sqlite3 adapter reuses sqlite_connection.
- def self.sqlite3_connection(config) # :nodoc:
+ def sqlite3_connection(config) # :nodoc:
# Require database.
unless config[:database]
raise ArgumentError, "No database file specified. Missing argument: database"
@@ -19,10 +21,6 @@ module ActiveRecord
config[:database] = File.expand_path(config[:database], Rails.root)
end
- unless 'sqlite3' == config[:adapter]
- raise ArgumentError, 'adapter name should be "sqlite3"'
- end
-
db = SQLite3::Database.new(
config[:database],
:results_as_hash => true
@@ -35,7 +33,184 @@ module ActiveRecord
end
module ConnectionAdapters #:nodoc:
- class SQLite3Adapter < SQLiteAdapter # :nodoc:
+ class SQLite3Column < Column #:nodoc:
+ class << self
+ def binary_to_string(value)
+ if value.encoding != Encoding::ASCII_8BIT
+ value = value.force_encoding(Encoding::ASCII_8BIT)
+ end
+ value
+ end
+ end
+ end
+
+ # The SQLite3 adapter works SQLite 3.6.16 or newer
+ # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
+ #
+ # Options:
+ #
+ # * <tt>:database</tt> - Path to the database file.
+ class SQLite3Adapter < AbstractAdapter
+ class Version
+ include Comparable
+
+ def initialize(version_string)
+ @version = version_string.split('.').map { |v| v.to_i }
+ end
+
+ def <=>(version_string)
+ @version <=> version_string.split('.').map { |v| v.to_i }
+ end
+ end
+
+ class StatementPool < ConnectionAdapters::StatementPool
+ def initialize(connection, max)
+ super
+ @cache = Hash.new { |h,pid| h[pid] = {} }
+ end
+
+ def each(&block); cache.each(&block); end
+ def key?(key); cache.key?(key); end
+ def [](key); cache[key]; end
+ def length; cache.length; end
+
+ def []=(sql, key)
+ while @max <= cache.size
+ dealloc(cache.shift.last[:stmt])
+ end
+ cache[sql] = key
+ end
+
+ def clear
+ cache.values.each do |hash|
+ dealloc hash[:stmt]
+ end
+ cache.clear
+ end
+
+ private
+ def cache
+ @cache[$$]
+ end
+
+ def dealloc(stmt)
+ stmt.close unless stmt.closed?
+ end
+ end
+
+ class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
+ include Arel::Visitors::BindVisitor
+ end
+
+ def initialize(connection, logger, config)
+ super(connection, logger)
+ @statements = StatementPool.new(@connection,
+ config.fetch(:statement_limit) { 1000 })
+ @config = config
+
+ if config.fetch(:prepared_statements) { true }
+ @visitor = Arel::Visitors::SQLite.new self
+ else
+ @visitor = BindSubstitution.new self
+ end
+ end
+
+ def adapter_name #:nodoc:
+ 'SQLite'
+ end
+
+ # Returns true
+ def supports_ddl_transactions?
+ true
+ end
+
+ # Returns true if SQLite version is '3.6.8' or greater, false otherwise.
+ def supports_savepoints?
+ sqlite_version >= '3.6.8'
+ end
+
+ # Returns true, since this connection adapter supports prepared statement
+ # caching.
+ def supports_statement_cache?
+ true
+ end
+
+ # Returns true, since this connection adapter supports migrations.
+ def supports_migrations? #:nodoc:
+ true
+ end
+
+ # Returns true.
+ def supports_primary_key? #:nodoc:
+ true
+ end
+
+ def requires_reloading?
+ true
+ end
+
+ # Returns true
+ def supports_add_column?
+ true
+ end
+
+ # Disconnects from the database if already connected. Otherwise, this
+ # method does nothing.
+ def disconnect!
+ super
+ clear_cache!
+ @connection.close rescue nil
+ end
+
+ # Clears the prepared statements cache.
+ def clear_cache!
+ @statements.clear
+ end
+
+ # Returns true
+ def supports_count_distinct? #:nodoc:
+ true
+ end
+
+ # Returns true
+ def supports_autoincrement? #:nodoc:
+ true
+ end
+
+ def supports_index_sort_order?
+ true
+ end
+
+ def native_database_types #:nodoc:
+ {
+ :primary_key => default_primary_key_type,
+ :string => { :name => "varchar", :limit => 255 },
+ :text => { :name => "text" },
+ :integer => { :name => "integer" },
+ :float => { :name => "float" },
+ :decimal => { :name => "decimal" },
+ :datetime => { :name => "datetime" },
+ :timestamp => { :name => "datetime" },
+ :time => { :name => "datetime" },
+ :date => { :name => "date" },
+ :binary => { :name => "blob" },
+ :boolean => { :name => "boolean" }
+ }
+ end
+
+ # Returns the current database encoding format as a string, eg: 'UTF-8'
+ def encoding
+ @connection.encoding.to_s
+ end
+
+ # Returns true.
+ def supports_explain?
+ true
+ end
+
+
+ # QUOTING ==================================================
+
def quote(value, column = nil)
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
s = column.class.string_to_binary(value).unpack("H*")[0]
@@ -45,15 +220,392 @@ module ActiveRecord
end
end
- # Returns the current database encoding format as a string, eg: 'UTF-8'
- def encoding
- if @connection.respond_to?(:encoding)
- @connection.encoding.to_s
+
+ def quote_string(s) #:nodoc:
+ @connection.class.quote(s)
+ end
+
+ def quote_column_name(name) #:nodoc:
+ %Q("#{name.to_s.gsub('"', '""')}")
+ end
+
+ # Quote date/time values for use in SQL input. Includes microseconds
+ # if the value is a Time responding to usec.
+ def quoted_date(value) #:nodoc:
+ if value.respond_to?(:usec)
+ "#{super}.#{sprintf("%06d", value.usec)}"
else
- @connection.execute('PRAGMA encoding')[0]['encoding']
+ super
+ end
+ end
+
+ def type_cast(value, column) # :nodoc:
+ return value.to_f if BigDecimal === value
+ return super unless String === value
+ return super unless column && value
+
+ value = super
+ if column.type == :string && value.encoding == Encoding::ASCII_8BIT
+ logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
+ value.encode! 'utf-8'
end
+ value
+ end
+
+ # DATABASE STATEMENTS ======================================
+
+ def explain(arel, binds = [])
+ sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
end
+ class ExplainPrettyPrinter
+ # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
+ # the output of the SQLite shell:
+ #
+ # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
+ # 0|1|1|SCAN TABLE posts (~100000 rows)
+ #
+ def pp(result) # :nodoc:
+ result.rows.map do |row|
+ row.join('|')
+ end.join("\n") + "\n"
+ end
+ end
+
+ def exec_query(sql, name = nil, binds = [])
+ log(sql, name, binds) do
+
+ # Don't cache statements without bind values
+ if binds.empty?
+ stmt = @connection.prepare(sql)
+ cols = stmt.columns
+ records = stmt.to_a
+ stmt.close
+ stmt = records
+ else
+ cache = @statements[sql] ||= {
+ :stmt => @connection.prepare(sql)
+ }
+ stmt = cache[:stmt]
+ cols = cache[:cols] ||= stmt.columns
+ stmt.reset!
+ stmt.bind_params binds.map { |col, val|
+ type_cast(val, col)
+ }
+ end
+
+ ActiveRecord::Result.new(cols, stmt.to_a)
+ end
+ end
+
+ def exec_delete(sql, name = 'SQL', binds = [])
+ exec_query(sql, name, binds)
+ @connection.changes
+ end
+ alias :exec_update :exec_delete
+
+ def last_inserted_id(result)
+ @connection.last_insert_row_id
+ end
+
+ def execute(sql, name = nil) #:nodoc:
+ log(sql, name) { @connection.execute(sql) }
+ end
+
+ def update_sql(sql, name = nil) #:nodoc:
+ super
+ @connection.changes
+ end
+
+ def delete_sql(sql, name = nil) #:nodoc:
+ sql += " WHERE 1=1" unless sql =~ /WHERE/i
+ super sql, name
+ end
+
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
+ super
+ id_value || @connection.last_insert_row_id
+ end
+ alias :create :insert_sql
+
+ def select_rows(sql, name = nil)
+ exec_query(sql, name).rows
+ end
+
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def begin_db_transaction #:nodoc:
+ log('begin transaction',nil) { @connection.transaction }
+ end
+
+ def commit_db_transaction #:nodoc:
+ log('commit transaction',nil) { @connection.commit }
+ end
+
+ def rollback_db_transaction #:nodoc:
+ log('rollback transaction',nil) { @connection.rollback }
+ end
+
+ # SCHEMA STATEMENTS ========================================
+
+ def tables(name = 'SCHEMA', table_name = nil) #:nodoc:
+ sql = <<-SQL
+ SELECT name
+ FROM sqlite_master
+ WHERE type = 'table' AND NOT name = 'sqlite_sequence'
+ SQL
+ sql << " AND name = #{quote_table_name(table_name)}" if table_name
+
+ exec_query(sql, name).map do |row|
+ row['name']
+ end
+ end
+
+ def table_exists?(name)
+ name && tables('SCHEMA', name).any?
+ end
+
+ # Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
+ def columns(table_name) #:nodoc:
+ table_structure(table_name).map do |field|
+ case field["dflt_value"]
+ when /^null$/i
+ field["dflt_value"] = nil
+ when /^'(.*)'$/
+ field["dflt_value"] = $1.gsub("''", "'")
+ when /^"(.*)"$/
+ field["dflt_value"] = $1.gsub('""', '"')
+ end
+
+ SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
+ end
+ end
+
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil) #:nodoc:
+ exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
+ IndexDefinition.new(
+ table_name,
+ row['name'],
+ row['unique'] != 0,
+ exec_query("PRAGMA index_info('#{row['name']}')").map { |col|
+ col['name']
+ })
+ end
+ end
+
+ def primary_key(table_name) #:nodoc:
+ column = table_structure(table_name).find { |field|
+ field['pk'] == 1
+ }
+ column && column['name']
+ end
+
+ def remove_index!(table_name, index_name) #:nodoc:
+ exec_query "DROP INDEX #{quote_column_name(index_name)}"
+ end
+
+ # Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
+ def rename_table(name, new_name)
+ exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
+ end
+
+ # See: http://www.sqlite.org/lang_altertable.html
+ # SQLite has an additional restriction on the ALTER TABLE statement
+ def valid_alter_table_options( type, options)
+ type.to_sym != :primary_key
+ end
+
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
+ if supports_add_column? && valid_alter_table_options( type, options )
+ super(table_name, column_name, type, options)
+ else
+ alter_table(table_name) do |definition|
+ definition.column(column_name, type, options)
+ end
+ end
+ end
+
+ def remove_column(table_name, *column_names) #:nodoc:
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
+ column_names.each do |column_name|
+ alter_table(table_name) do |definition|
+ definition.columns.delete(definition[column_name])
+ end
+ end
+ end
+ alias :remove_columns :remove_column
+
+ def change_column_default(table_name, column_name, default) #:nodoc:
+ alter_table(table_name) do |definition|
+ definition[column_name].default = default
+ end
+ end
+
+ def change_column_null(table_name, column_name, null, default = nil)
+ unless null || default.nil?
+ exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ end
+ alter_table(table_name) do |definition|
+ definition[column_name].null = null
+ end
+ end
+
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
+ alter_table(table_name) do |definition|
+ include_default = options_include_default?(options)
+ definition[column_name].instance_eval do
+ self.type = type
+ self.limit = options[:limit] if options.include?(:limit)
+ self.default = options[:default] if include_default
+ self.null = options[:null] if options.include?(:null)
+ self.precision = options[:precision] if options.include?(:precision)
+ self.scale = options[:scale] if options.include?(:scale)
+ end
+ end
+ end
+
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
+ unless columns(table_name).detect{|c| c.name == column_name.to_s }
+ raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
+ end
+ alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
+ end
+
+ def empty_insert_statement_value
+ "VALUES(NULL)"
+ end
+
+ protected
+ def select(sql, name = nil, binds = []) #:nodoc:
+ exec_query(sql, name, binds)
+ end
+
+ def table_structure(table_name)
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
+ structure
+ end
+
+ def alter_table(table_name, options = {}) #:nodoc:
+ altered_table_name = "altered_#{table_name}"
+ caller = lambda {|definition| yield definition if block_given?}
+
+ transaction do
+ move_table(table_name, altered_table_name,
+ options.merge(:temporary => true))
+ move_table(altered_table_name, table_name, &caller)
+ end
+ end
+
+ def move_table(from, to, options = {}, &block) #:nodoc:
+ copy_table(from, to, options, &block)
+ drop_table(from)
+ end
+
+ def copy_table(from, to, options = {}) #:nodoc:
+ from_primary_key = primary_key(from)
+ options[:primary_key] = from_primary_key if from_primary_key != 'id'
+ unless options[:primary_key]
+ options[:id] = columns(from).detect{|c| c.name == 'id'}.present? && from_primary_key == 'id'
+ end
+ create_table(to, options) do |definition|
+ @definition = definition
+ columns(from).each do |column|
+ column_name = options[:rename] ?
+ (options[:rename][column.name] ||
+ options[:rename][column.name.to_sym] ||
+ column.name) : column.name
+
+ @definition.column(column_name, column.type,
+ :limit => column.limit, :default => column.default,
+ :precision => column.precision, :scale => column.scale,
+ :null => column.null)
+ end
+ @definition.primary_key(from_primary_key) if from_primary_key
+ yield @definition if block_given?
+ end
+
+ copy_table_indexes(from, to, options[:rename] || {})
+ copy_table_contents(from, to,
+ @definition.columns.map {|column| column.name},
+ options[:rename] || {})
+ end
+
+ def copy_table_indexes(from, to, rename = {}) #:nodoc:
+ indexes(from).each do |index|
+ name = index.name
+ if to == "altered_#{from}"
+ name = "temp_#{name}"
+ elsif from == "altered_#{to}"
+ name = name[5..-1]
+ end
+
+ to_column_names = columns(to).map { |c| c.name }
+ columns = index.columns.map {|c| rename[c] || c }.select do |column|
+ to_column_names.include?(column)
+ end
+
+ unless columns.empty?
+ # index name can't be the same
+ opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
+ opts[:unique] = true if index.unique
+ add_index(to, columns, opts)
+ end
+ end
+ end
+
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
+ column_mappings = Hash[columns.map {|name| [name, name]}]
+ rename.each { |a| column_mappings[a.last] = a.first }
+ from_columns = columns(from).collect {|col| col.name}
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
+ quoted_columns = columns.map { |col| quote_column_name(col) } * ','
+
+ quoted_to = quote_table_name(to)
+ exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
+ sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
+ sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
+ sql << ')'
+ exec_query sql
+ end
+ end
+
+ def sqlite_version
+ @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
+ end
+
+ def default_primary_key_type
+ if supports_autoincrement?
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
+ else
+ 'INTEGER PRIMARY KEY NOT NULL'
+ end
+ end
+
+ def translate_exception(exception, message)
+ case exception.message
+ when /column(s)? .* (is|are) not unique/
+ RecordNotUnique.new(message, exception)
+ else
+ super
+ end
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
deleted file mode 100644
index 1932a849ee..0000000000
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ /dev/null
@@ -1,544 +0,0 @@
-require 'active_record/connection_adapters/abstract_adapter'
-require 'active_record/connection_adapters/statement_pool'
-require 'active_support/core_ext/string/encoding'
-
-module ActiveRecord
- module ConnectionAdapters #:nodoc:
- class SQLiteColumn < Column #:nodoc:
- class << self
- def string_to_binary(value)
- value.gsub(/\0|\%/n) do |b|
- case b
- when "\0" then "%00"
- when "%" then "%25"
- end
- end
- end
-
- def binary_to_string(value)
- if value.respond_to?(:force_encoding) && value.encoding != Encoding::ASCII_8BIT
- value = value.force_encoding(Encoding::ASCII_8BIT)
- end
-
- value.gsub(/%00|%25/n) do |b|
- case b
- when "%00" then "\0"
- when "%25" then "%"
- end
- end
- end
- end
- end
-
- # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby
- # drivers (available both as gems and from http://rubyforge.org/projects/sqlite-ruby/).
- #
- # Options:
- #
- # * <tt>:database</tt> - Path to the database file.
- class SQLiteAdapter < AbstractAdapter
- class Version
- include Comparable
-
- def initialize(version_string)
- @version = version_string.split('.').map { |v| v.to_i }
- end
-
- def <=>(version_string)
- @version <=> version_string.split('.').map { |v| v.to_i }
- end
- end
-
- class StatementPool < ConnectionAdapters::StatementPool
- def initialize(connection, max)
- super
- @cache = Hash.new { |h,pid| h[pid] = {} }
- end
-
- def each(&block); cache.each(&block); end
- def key?(key); cache.key?(key); end
- def [](key); cache[key]; end
- def length; cache.length; end
-
- def []=(sql, key)
- while @max <= cache.size
- dealloc(cache.shift.last[:stmt])
- end
- cache[sql] = key
- end
-
- def clear
- cache.values.each do |hash|
- dealloc hash[:stmt]
- end
- cache.clear
- end
-
- private
- def cache
- @cache[$$]
- end
-
- def dealloc(stmt)
- stmt.close unless stmt.closed?
- end
- end
-
- def initialize(connection, logger, config)
- super(connection, logger)
- @statements = StatementPool.new(@connection,
- config.fetch(:statement_limit) { 1000 })
- @config = config
- end
-
- def self.visitor_for(pool) # :nodoc:
- Arel::Visitors::SQLite.new(pool)
- end
-
- def adapter_name #:nodoc:
- 'SQLite'
- end
-
- # Returns true if SQLite version is '2.0.0' or greater, false otherwise.
- def supports_ddl_transactions?
- sqlite_version >= '2.0.0'
- end
-
- # Returns true if SQLite version is '3.6.8' or greater, false otherwise.
- def supports_savepoints?
- sqlite_version >= '3.6.8'
- end
-
- # Returns true, since this connection adapter supports prepared statement
- # caching.
- def supports_statement_cache?
- true
- end
-
- # Returns true, since this connection adapter supports migrations.
- def supports_migrations? #:nodoc:
- true
- end
-
- # Returns true.
- def supports_primary_key? #:nodoc:
- true
- end
-
- def requires_reloading?
- true
- end
-
- # Returns true if SQLite version is '3.1.6' or greater, false otherwise.
- def supports_add_column?
- sqlite_version >= '3.1.6'
- end
-
- # Disconnects from the database if already connected. Otherwise, this
- # method does nothing.
- def disconnect!
- super
- clear_cache!
- @connection.close rescue nil
- end
-
- # Clears the prepared statements cache.
- def clear_cache!
- @statements.clear
- end
-
- # Returns true if SQLite version is '3.2.6' or greater, false otherwise.
- def supports_count_distinct? #:nodoc:
- sqlite_version >= '3.2.6'
- end
-
- # Returns true if SQLite version is '3.1.0' or greater, false otherwise.
- def supports_autoincrement? #:nodoc:
- sqlite_version >= '3.1.0'
- end
-
- def native_database_types #:nodoc:
- {
- :primary_key => default_primary_key_type,
- :string => { :name => "varchar", :limit => 255 },
- :text => { :name => "text" },
- :integer => { :name => "integer" },
- :float => { :name => "float" },
- :decimal => { :name => "decimal" },
- :datetime => { :name => "datetime" },
- :timestamp => { :name => "datetime" },
- :time => { :name => "time" },
- :date => { :name => "date" },
- :binary => { :name => "blob" },
- :boolean => { :name => "boolean" }
- }
- end
-
-
- # QUOTING ==================================================
-
- def quote_string(s) #:nodoc:
- @connection.class.quote(s)
- end
-
- def quote_column_name(name) #:nodoc:
- %Q("#{name.to_s.gsub('"', '""')}")
- end
-
- # Quote date/time values for use in SQL input. Includes microseconds
- # if the value is a Time responding to usec.
- def quoted_date(value) #:nodoc:
- if value.respond_to?(:usec)
- "#{super}.#{sprintf("%06d", value.usec)}"
- else
- super
- end
- end
-
- if "<3".encoding_aware?
- def type_cast(value, column) # :nodoc:
- return value.to_f if BigDecimal === value
- return super unless String === value
- return super unless column && value
-
- value = super
- if column.type == :string && value.encoding == Encoding::ASCII_8BIT
- @logger.error "Binary data inserted for `string` type on column `#{column.name}`"
- value.encode! 'utf-8'
- end
- value
- end
- else
- def type_cast(value, column) # :nodoc:
- return super unless BigDecimal === value
-
- value.to_f
- end
- end
-
- # DATABASE STATEMENTS ======================================
-
- def exec_query(sql, name = nil, binds = [])
- log(sql, name, binds) do
-
- # Don't cache statements without bind values
- if binds.empty?
- stmt = @connection.prepare(sql)
- cols = stmt.columns
- records = stmt.to_a
- stmt.close
- stmt = records
- else
- cache = @statements[sql] ||= {
- :stmt => @connection.prepare(sql)
- }
- stmt = cache[:stmt]
- cols = cache[:cols] ||= stmt.columns
- stmt.reset!
- stmt.bind_params binds.map { |col, val|
- type_cast(val, col)
- }
- end
-
- ActiveRecord::Result.new(cols, stmt.to_a)
- end
- end
-
- def exec_delete(sql, name = 'SQL', binds = [])
- exec_query(sql, name, binds)
- @connection.changes
- end
- alias :exec_update :exec_delete
-
- def last_inserted_id(result)
- @connection.last_insert_row_id
- end
-
- def execute(sql, name = nil) #:nodoc:
- log(sql, name) { @connection.execute(sql) }
- end
-
- def update_sql(sql, name = nil) #:nodoc:
- super
- @connection.changes
- end
-
- def delete_sql(sql, name = nil) #:nodoc:
- sql += " WHERE 1=1" unless sql =~ /WHERE/i
- super sql, name
- end
-
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
- super
- id_value || @connection.last_insert_row_id
- end
- alias :create :insert_sql
-
- def select_rows(sql, name = nil)
- exec_query(sql, name).rows
- end
-
- def create_savepoint
- execute("SAVEPOINT #{current_savepoint_name}")
- end
-
- def rollback_to_savepoint
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
- end
-
- def release_savepoint
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
- end
-
- def begin_db_transaction #:nodoc:
- log('begin transaction',nil) { @connection.transaction }
- end
-
- def commit_db_transaction #:nodoc:
- log('commit transaction',nil) { @connection.commit }
- end
-
- def rollback_db_transaction #:nodoc:
- log('rollback transaction',nil) { @connection.rollback }
- end
-
- # SCHEMA STATEMENTS ========================================
-
- def tables(name = 'SCHEMA') #:nodoc:
- sql = <<-SQL
- SELECT name
- FROM sqlite_master
- WHERE type = 'table' AND NOT name = 'sqlite_sequence'
- SQL
-
- exec_query(sql, name).map do |row|
- row['name']
- end
- end
-
- # Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+.
- def columns(table_name, name = nil) #:nodoc:
- table_structure(table_name).map do |field|
- case field["dflt_value"]
- when /^null$/i
- field["dflt_value"] = nil
- when /^'(.*)'$/
- field["dflt_value"] = $1.gsub(/''/, "'")
- when /^"(.*)"$/
- field["dflt_value"] = $1.gsub(/""/, '"')
- end
-
- SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
- end
- end
-
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil) #:nodoc:
- exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
- IndexDefinition.new(
- table_name,
- row['name'],
- row['unique'] != 0,
- exec_query("PRAGMA index_info('#{row['name']}')").map { |col|
- col['name']
- })
- end
- end
-
- def primary_key(table_name) #:nodoc:
- column = table_structure(table_name).find { |field|
- field['pk'] == 1
- }
- column && column['name']
- end
-
- def remove_index!(table_name, index_name) #:nodoc:
- exec_query "DROP INDEX #{quote_column_name(index_name)}"
- end
-
- # Renames a table.
- #
- # Example:
- # rename_table('octopuses', 'octopi')
- def rename_table(name, new_name)
- exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
- end
-
- # See: http://www.sqlite.org/lang_altertable.html
- # SQLite has an additional restriction on the ALTER TABLE statement
- def valid_alter_table_options( type, options)
- type.to_sym != :primary_key
- end
-
- def add_column(table_name, column_name, type, options = {}) #:nodoc:
- if supports_add_column? && valid_alter_table_options( type, options )
- super(table_name, column_name, type, options)
- else
- alter_table(table_name) do |definition|
- definition.column(column_name, type, options)
- end
- end
- end
-
- def remove_column(table_name, *column_names) #:nodoc:
- raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
- column_names.flatten.each do |column_name|
- alter_table(table_name) do |definition|
- definition.columns.delete(definition[column_name])
- end
- end
- end
- alias :remove_columns :remove_column
-
- def change_column_default(table_name, column_name, default) #:nodoc:
- alter_table(table_name) do |definition|
- definition[column_name].default = default
- end
- end
-
- def change_column_null(table_name, column_name, null, default = nil)
- unless null || default.nil?
- exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
- end
- alter_table(table_name) do |definition|
- definition[column_name].null = null
- end
- end
-
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
- alter_table(table_name) do |definition|
- include_default = options_include_default?(options)
- definition[column_name].instance_eval do
- self.type = type
- self.limit = options[:limit] if options.include?(:limit)
- self.default = options[:default] if include_default
- self.null = options[:null] if options.include?(:null)
- end
- end
- end
-
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
- unless columns(table_name).detect{|c| c.name == column_name.to_s }
- raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
- end
- alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
- end
-
- def empty_insert_statement_value
- "VALUES(NULL)"
- end
-
- protected
- def select(sql, name = nil, binds = []) #:nodoc:
- exec_query(sql, name, binds).to_a
- end
-
- def table_structure(table_name)
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
- raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
- structure
- end
-
- def alter_table(table_name, options = {}) #:nodoc:
- altered_table_name = "altered_#{table_name}"
- caller = lambda {|definition| yield definition if block_given?}
-
- transaction do
- move_table(table_name, altered_table_name,
- options.merge(:temporary => true))
- move_table(altered_table_name, table_name, &caller)
- end
- end
-
- def move_table(from, to, options = {}, &block) #:nodoc:
- copy_table(from, to, options, &block)
- drop_table(from)
- end
-
- def copy_table(from, to, options = {}) #:nodoc:
- options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
- create_table(to, options) do |definition|
- @definition = definition
- columns(from).each do |column|
- column_name = options[:rename] ?
- (options[:rename][column.name] ||
- options[:rename][column.name.to_sym] ||
- column.name) : column.name
-
- @definition.column(column_name, column.type,
- :limit => column.limit, :default => column.default,
- :null => column.null)
- end
- @definition.primary_key(primary_key(from)) if primary_key(from)
- yield @definition if block_given?
- end
-
- copy_table_indexes(from, to, options[:rename] || {})
- copy_table_contents(from, to,
- @definition.columns.map {|column| column.name},
- options[:rename] || {})
- end
-
- def copy_table_indexes(from, to, rename = {}) #:nodoc:
- indexes(from).each do |index|
- name = index.name
- if to == "altered_#{from}"
- name = "temp_#{name}"
- elsif from == "altered_#{to}"
- name = name[5..-1]
- end
-
- to_column_names = columns(to).map { |c| c.name }
- columns = index.columns.map {|c| rename[c] || c }.select do |column|
- to_column_names.include?(column)
- end
-
- unless columns.empty?
- # index name can't be the same
- opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
- opts[:unique] = true if index.unique
- add_index(to, columns, opts)
- end
- end
- end
-
- def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
- column_mappings = Hash[columns.map {|name| [name, name]}]
- rename.each { |a| column_mappings[a.last] = a.first }
- from_columns = columns(from).collect {|col| col.name}
- columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
- quoted_columns = columns.map { |col| quote_column_name(col) } * ','
-
- quoted_to = quote_table_name(to)
- exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
- sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
- sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
- sql << ')'
- exec_query sql
- end
- end
-
- def sqlite_version
- @sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
- end
-
- def default_primary_key_type
- if supports_autoincrement?
- 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
- else
- 'INTEGER PRIMARY KEY NOT NULL'
- end
- end
-
- def translate_exception(exception, message)
- case exception.message
- when /column(s)? .* (is|are) not unique/
- RecordNotUnique.new(message, exception)
- else
- super
- end
- end
-
- end
- end
-end
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
new file mode 100644
index 0000000000..7b218a5570
--- /dev/null
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -0,0 +1,96 @@
+require 'active_support/core_ext/module/delegation'
+
+module ActiveRecord
+ module ConnectionHandling
+ # Establishes the connection to the database. Accepts a hash as input where
+ # the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
+ # example for regular databases (MySQL, Postgresql, etc):
+ #
+ # ActiveRecord::Base.establish_connection(
+ # :adapter => "mysql",
+ # :host => "localhost",
+ # :username => "myuser",
+ # :password => "mypass",
+ # :database => "somedatabase"
+ # )
+ #
+ # Example for SQLite database:
+ #
+ # ActiveRecord::Base.establish_connection(
+ # :adapter => "sqlite",
+ # :database => "path/to/dbfile"
+ # )
+ #
+ # Also accepts keys as strings (for parsing from YAML for example):
+ #
+ # ActiveRecord::Base.establish_connection(
+ # "adapter" => "sqlite",
+ # "database" => "path/to/dbfile"
+ # )
+ #
+ # Or a URL:
+ #
+ # ActiveRecord::Base.establish_connection(
+ # "postgres://myuser:mypass@localhost/somedatabase"
+ # )
+ #
+ # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
+ # may be returned on an error.
+ def establish_connection(spec = ENV["DATABASE_URL"])
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new spec, configurations
+ spec = resolver.spec
+
+ unless respond_to?(spec.adapter_method)
+ raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
+ end
+
+ remove_connection
+ connection_handler.establish_connection name, spec
+ end
+
+ # Returns the connection currently associated with the class. This can
+ # also be used to "borrow" the connection to do database work unrelated
+ # to any of the specific Active Records.
+ def connection
+ retrieve_connection
+ end
+
+ def connection_id
+ Thread.current['ActiveRecord::Base.connection_id']
+ end
+
+ def connection_id=(connection_id)
+ Thread.current['ActiveRecord::Base.connection_id'] = connection_id
+ end
+
+ # Returns the configuration of the associated connection as a hash:
+ #
+ # ActiveRecord::Base.connection_config
+ # # => {:pool=>5, :timeout=>5000, :database=>"db/development.sqlite3", :adapter=>"sqlite3"}
+ #
+ # Please use only for reading.
+ def connection_config
+ connection_pool.spec.config
+ end
+
+ def connection_pool
+ connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
+ end
+
+ def retrieve_connection
+ connection_handler.retrieve_connection(self)
+ end
+
+ # Returns true if Active Record is connected.
+ def connected?
+ connection_handler.connected?(self)
+ end
+
+ def remove_connection(klass = self)
+ connection_handler.remove_connection(klass)
+ end
+
+ delegate :clear_active_connections!, :clear_reloadable_connections!,
+ :clear_all_connections!, :to => :connection_handler
+ end
+end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
new file mode 100644
index 0000000000..f2833fbf3c
--- /dev/null
+++ b/activerecord/lib/active_record/core.rb
@@ -0,0 +1,393 @@
+require 'active_support/concern'
+require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/object/deep_dup'
+require 'thread'
+
+module ActiveRecord
+ module Core
+ extend ActiveSupport::Concern
+
+ included do
+ ##
+ # :singleton-method:
+ # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
+ # which is then passed on to any new database connections made and which can be retrieved on both
+ # a class and instance level by calling +logger+.
+ config_attribute :logger, :global => true
+
+ ##
+ # :singleton-method:
+ # Contains the database configuration - as is typically stored in config/database.yml -
+ # as a Hash.
+ #
+ # For example, the following database.yml...
+ #
+ # development:
+ # adapter: sqlite3
+ # database: db/development.sqlite3
+ #
+ # production:
+ # adapter: sqlite3
+ # database: db/production.sqlite3
+ #
+ # ...would result in ActiveRecord::Base.configurations to look like this:
+ #
+ # {
+ # 'development' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/development.sqlite3'
+ # },
+ # 'production' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/production.sqlite3'
+ # }
+ # }
+ config_attribute :configurations, :global => true
+ self.configurations = {}
+
+ ##
+ # :singleton-method:
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
+ # dates and times from the database. This is set to :utc by default.
+ config_attribute :default_timezone, :global => true
+ self.default_timezone = :utc
+
+ ##
+ # :singleton-method:
+ # Specifies the format to use when dumping the database schema with Rails'
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
+ # specific) SQL statements. If :ruby, the schema is dumped as an
+ # ActiveRecord::Schema file which can be loaded into any database that
+ # supports migrations. Use :ruby if you want to have different database
+ # adapters for, e.g., your development and test environments.
+ config_attribute :schema_format, :global => true
+ self.schema_format = :ruby
+
+ ##
+ # :singleton-method:
+ # Specify whether or not to use timestamps for migration versions
+ config_attribute :timestamped_migrations, :global => true
+ self.timestamped_migrations = true
+
+ ##
+ # :singleton-method:
+ # The connection handler
+ config_attribute :connection_handler
+ self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+
+ ##
+ # :singleton-method:
+ # Specifies whether or not has_many or has_one association option
+ # :dependent => :restrict raises an exception. If set to true, the
+ # ActiveRecord::DeleteRestrictionError exception will be raised
+ # along with a DEPRECATION WARNING. If set to false, an error would
+ # be added to the model instead.
+ config_attribute :dependent_restrict_raises, :global => true
+ self.dependent_restrict_raises = true
+ end
+
+ module ClassMethods
+ def inherited(child_class) #:nodoc:
+ child_class.initialize_generated_modules
+ super
+ end
+
+ def initialize_generated_modules
+ @attribute_methods_mutex = Mutex.new
+
+ # force attribute methods to be higher in inheritance hierarchy than other generated methods
+ generated_attribute_methods
+ generated_feature_methods
+ end
+
+ def generated_feature_methods
+ @generated_feature_methods ||= begin
+ mod = const_set(:GeneratedFeatureMethods, Module.new)
+ include mod
+ mod
+ end
+ end
+
+ # Returns a string like 'Post(id:integer, title:string, body:text)'
+ def inspect
+ if self == Base
+ super
+ elsif abstract_class?
+ "#{super}(abstract)"
+ elsif table_exists?
+ attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
+ "#{super}(#{attr_list})"
+ else
+ "#{super}(Table doesn't exist)"
+ end
+ end
+
+ # Overwrite the default class equality method to provide support for association proxies.
+ def ===(object)
+ object.is_a?(self)
+ end
+
+ # Returns an instance of <tt>Arel::Table</tt> loaded with the current table name.
+ #
+ # class Post < ActiveRecord::Base
+ # scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0))
+ # end
+ def arel_table
+ @arel_table ||= Arel::Table.new(table_name, arel_engine)
+ end
+
+ # Returns the Arel engine.
+ def arel_engine
+ @arel_engine ||= connection_handler.retrieve_connection_pool(self) ? self : active_record_super.arel_engine
+ end
+
+ private
+
+ def relation #:nodoc:
+ relation = Relation.new(self, arel_table)
+
+ if finder_needs_type_condition?
+ relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
+ else
+ relation
+ end
+ end
+ end
+
+ # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
+ # attributes but not yet saved (pass a hash with key names matching the associated table column names).
+ # In both instances, valid attribute keys are determined by the column names of the associated table --
+ # hence you can't have attributes that aren't part of the table columns.
+ #
+ # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
+ # in the +options+ parameter.
+ #
+ # ==== Examples
+ # # Instantiates a single new object
+ # User.new(:first_name => 'Jamie')
+ #
+ # # Instantiates a single new object using the :admin mass-assignment security role
+ # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
+ #
+ # # Instantiates a single new object bypassing mass-assignment security
+ # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
+ def initialize(attributes = nil, options = {})
+ @attributes = self.class.initialize_attributes(self.class.column_defaults.deep_dup)
+ @columns_hash = self.class.column_types.dup
+
+ init_internals
+
+ ensure_proper_type
+
+ populate_with_current_scope_attributes
+
+ assign_attributes(attributes, options) if attributes
+
+ yield self if block_given?
+ run_callbacks :initialize if _initialize_callbacks.any?
+ end
+
+ # Initialize an empty model object from +coder+. +coder+ must contain
+ # the attributes necessary for initializing an empty model object. For
+ # example:
+ #
+ # class Post < ActiveRecord::Base
+ # end
+ #
+ # post = Post.allocate
+ # post.init_with('attributes' => { 'title' => 'hello world' })
+ # post.title # => 'hello world'
+ def init_with(coder)
+ @attributes = self.class.initialize_attributes(coder['attributes'])
+ @columns_hash = self.class.column_types.merge(coder['column_types'] || {})
+
+ init_internals
+
+ @new_record = false
+
+ run_callbacks :find
+ run_callbacks :initialize
+
+ self
+ end
+
+ ##
+ # :method: clone
+ # Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied.
+ # That means that modifying attributes of the clone will modify the original, since they will both point to the
+ # same attributes hash. If you need a copy of your attributes hash, please use the #dup method.
+ #
+ # user = User.first
+ # new_user = user.clone
+ # user.name # => "Bob"
+ # new_user.name = "Joe"
+ # user.name # => "Joe"
+ #
+ # user.object_id == new_user.object_id # => false
+ # user.name.object_id == new_user.name.object_id # => true
+ #
+ # user.name.object_id == user.dup.name.object_id # => false
+
+ ##
+ # :method: dup
+ # Duped objects have no id assigned and are treated as new records. Note
+ # that this is a "shallow" copy as it copies the object's attributes
+ # only, not its associations. The extent of a "deep" copy is application
+ # specific and is therefore left to the application to implement according
+ # to its need.
+ # The dup method does not preserve the timestamps (created|updated)_(at|on).
+
+ ##
+ def initialize_dup(other) # :nodoc:
+ cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
+ self.class.initialize_attributes(cloned_attributes)
+
+ cloned_attributes.delete(self.class.primary_key)
+
+ @attributes = cloned_attributes
+ @attributes[self.class.primary_key] = nil
+
+ run_callbacks(:initialize) if _initialize_callbacks.any?
+
+ @changed_attributes = {}
+ self.class.column_defaults.each do |attr, orig_value|
+ @changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
+ end
+
+ @aggregation_cache = {}
+ @association_cache = {}
+ @attributes_cache = {}
+
+ @new_record = true
+
+ ensure_proper_type
+ populate_with_current_scope_attributes
+ super
+ end
+
+ # Populate +coder+ with attributes about this record that should be
+ # serialized. The structure of +coder+ defined in this method is
+ # guaranteed to match the structure of +coder+ passed to the +init_with+
+ # method.
+ #
+ # Example:
+ #
+ # class Post < ActiveRecord::Base
+ # end
+ # coder = {}
+ # Post.new.encode_with(coder)
+ # coder # => {"attributes" => {"id" => nil, ... }}
+ def encode_with(coder)
+ coder['attributes'] = attributes
+ end
+
+ # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
+ # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
+ #
+ # Note that new records are different from any other record by definition, unless the
+ # other record is the receiver itself. Besides, if you fetch existing records with
+ # +select+ and leave the ID out, you're on your own, this predicate will return false.
+ #
+ # Note also that destroying a record preserves its ID in the model instance, so deleted
+ # models are still comparable.
+ def ==(comparison_object)
+ super ||
+ comparison_object.instance_of?(self.class) &&
+ id.present? &&
+ comparison_object.id == id
+ end
+ alias :eql? :==
+
+ # Delegates to id in order to allow two records of the same type and id to work with something like:
+ # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
+ def hash
+ id.hash
+ end
+
+ # Freeze the attributes hash such that associations are still accessible, even on destroyed records.
+ def freeze
+ @attributes.freeze; self
+ end
+
+ # Returns +true+ if the attributes hash has been frozen.
+ def frozen?
+ @attributes.frozen?
+ end
+
+ # Allows sort on objects
+ def <=>(other_object)
+ if other_object.is_a?(self.class)
+ self.to_key <=> other_object.to_key
+ else
+ nil
+ end
+ end
+
+ # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
+ # attributes will be marked as read only since they cannot be saved.
+ def readonly?
+ @readonly
+ end
+
+ # Marks this record as read only.
+ def readonly!
+ @readonly = true
+ end
+
+ # Returns the connection currently associated with the class. This can
+ # also be used to "borrow" the connection to do database work that isn't
+ # easily done without going straight to SQL.
+ def connection
+ self.class.connection
+ end
+
+ # Returns the contents of the record as a nicely formatted string.
+ def inspect
+ inspection = if @attributes
+ self.class.column_names.collect { |name|
+ if has_attribute?(name)
+ "#{name}: #{attribute_for_inspect(name)}"
+ end
+ }.compact.join(", ")
+ else
+ "not initialized"
+ end
+ "#<#{self.class} #{inspection}>"
+ end
+
+ # Returns a hash of the given methods with their names as keys and returned values as values.
+ def slice(*methods)
+ Hash[methods.map { |method| [method, public_send(method)] }].with_indifferent_access
+ end
+
+ private
+
+ # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
+ # of the array, and then rescues from the possible NoMethodError. If those elements are
+ # ActiveRecord::Base's, then this triggers the various method_missing's that we have,
+ # which significantly impacts upon performance.
+ #
+ # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
+ #
+ # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/
+ def to_ary # :nodoc:
+ nil
+ end
+
+ def init_internals
+ pk = self.class.primary_key
+
+ @attributes[pk] = nil unless @attributes.key?(pk)
+
+ @aggregation_cache = {}
+ @association_cache = {}
+ @attributes_cache = {}
+ @previously_changed = {}
+ @changed_attributes = {}
+ @readonly = false
+ @destroyed = false
+ @marked_for_destruction = false
+ @new_record = true
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 3c7defedac..b163ef3c12 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -19,15 +19,10 @@ module ActiveRecord
counters.each do |association|
has_many_association = reflect_on_association(association.to_sym)
- expected_name = if has_many_association.options[:as]
- has_many_association.options[:as].to_s.classify
- else
- self.name
- end
-
+ foreign_key = has_many_association.foreign_key.to_s
child_class = has_many_association.klass
belongs_to = child_class.reflect_on_all_associations(:belongs_to)
- reflection = belongs_to.find { |e| e.class_name == expected_name }
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
counter_name = reflection.counter_cache_column
stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
@@ -65,7 +60,7 @@ module ActiveRecord
# Post.update_counters [10, 15], :comment_count => 1
# # Executes the following SQL:
# # UPDATE posts
- # # SET comment_count = COALESCE(comment_count, 0) + 1,
+ # # SET comment_count = COALESCE(comment_count, 0) + 1
# # WHERE id IN (10, 15)
def update_counters(id, counters)
updates = counters.map do |counter_name, value|
@@ -74,9 +69,7 @@ module ActiveRecord
"#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
end
- IdentityMap.remove_by_id(symbolized_base_class, id) if IdentityMap.enabled?
-
- update_all(updates.join(', '), primary_key => id )
+ where(primary_key => id).update_all updates.join(', ')
end
# Increment a number field by one, usually representing a count.
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
deleted file mode 100644
index b309df9b1b..0000000000
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-module ActiveRecord
-
- # = Active Record Dynamic Finder Match
- #
- # Refer to ActiveRecord::Base documentation for Dynamic attribute-based finders for detailed info
- #
- class DynamicFinderMatch
- def self.match(method)
- finder = :first
- bang = false
- instantiator = nil
-
- case method.to_s
- when /^find_(all_|last_)?by_([_a-zA-Z]\w*)$/
- finder = :last if $1 == 'last_'
- finder = :all if $1 == 'all_'
- names = $2
- when /^find_by_([_a-zA-Z]\w*)\!$/
- bang = true
- names = $1
- when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
- instantiator = $1 == 'initialize' ? :new : :create
- names = $2
- else
- return nil
- end
-
- new(finder, instantiator, bang, names.split('_and_'))
- end
-
- def initialize(finder, instantiator, bang, attribute_names)
- @finder = finder
- @instantiator = instantiator
- @bang = bang
- @attribute_names = attribute_names
- end
-
- attr_reader :finder, :attribute_names, :instantiator
-
- def finder?
- @finder && !@instantiator
- end
-
- def instantiator?
- @finder == :first && @instantiator
- end
-
- def creator?
- @finder == :first && @instantiator == :create
- end
-
- def bang?
- @bang
- end
- end
-end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
new file mode 100644
index 0000000000..e278e62ce7
--- /dev/null
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -0,0 +1,130 @@
+module ActiveRecord
+ module DynamicMatchers #:nodoc:
+ # This code in this file seems to have a lot of indirection, but the indirection
+ # is there to provide extension points for the active_record_deprecated_finders
+ # gem. When we stop supporting active_record_deprecated_finders (from Rails 5),
+ # then we can remove the indirection.
+
+ def respond_to?(name, include_private = false)
+ match = Method.match(self, name)
+ match && match.valid? || super
+ end
+
+ private
+
+ def method_missing(name, *arguments, &block)
+ match = Method.match(self, name)
+
+ if match && match.valid?
+ match.define
+ send(name, *arguments, &block)
+ else
+ super
+ end
+ end
+
+ class Method
+ @matchers = []
+
+ class << self
+ attr_reader :matchers
+
+ def match(model, name)
+ klass = matchers.find { |k| name =~ k.pattern }
+ klass.new(model, name) if klass
+ end
+
+ def pattern
+ /^#{prefix}_([_a-zA-Z]\w*)#{suffix}$/
+ end
+
+ def prefix
+ raise NotImplementedError
+ end
+
+ def suffix
+ ''
+ end
+ end
+
+ attr_reader :model, :name, :attribute_names
+
+ def initialize(model, name)
+ @model = model
+ @name = name.to_s
+ @attribute_names = @name.match(self.class.pattern)[1].split('_and_')
+ end
+
+ def valid?
+ attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
+ end
+
+ def define
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def self.#{name}(#{signature})
+ #{body}
+ end
+ CODE
+ end
+
+ def body
+ raise NotImplementedError
+ end
+ end
+
+ module Finder
+ # Extended in active_record_deprecated_finders
+ def body
+ result
+ end
+
+ # Extended in active_record_deprecated_finders
+ def result
+ "#{finder}(#{attributes_hash})"
+ end
+
+ # Extended in active_record_deprecated_finders
+ def signature
+ attribute_names.join(', ')
+ end
+
+ def attributes_hash
+ "{" + attribute_names.map { |name| ":#{name} => #{name}" }.join(',') + "}"
+ end
+
+ def finder
+ raise NotImplementedError
+ end
+ end
+
+ class FindBy < Method
+ Method.matchers << self
+ include Finder
+
+ def self.prefix
+ "find_by"
+ end
+
+ def finder
+ "find_by"
+ end
+ end
+
+ class FindByBang < Method
+ Method.matchers << self
+ include Finder
+
+ def self.prefix
+ "find_by"
+ end
+
+ def self.suffix
+ "!"
+ end
+
+ def finder
+ "find_by!"
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb
deleted file mode 100644
index c832e927d6..0000000000
--- a/activerecord/lib/active_record/dynamic_scope_match.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module ActiveRecord
-
- # = Active Record Dynamic Scope Match
- #
- # Provides dynamic attribute-based scopes such as <tt>scoped_by_price(4.99)</tt>
- # if, for example, the <tt>Product</tt> has an attribute with that name. You can
- # chain more <tt>scoped_by_* </tt> methods after the other. It acts like a named
- # scope except that it's dynamic.
- class DynamicScopeMatch
- def self.match(method)
- return unless method.to_s =~ /^scoped_by_([_a-zA-Z]\w*)$/
- new(true, $1 && $1.split('_and_'))
- end
-
- def initialize(scope, attribute_names)
- @scope = scope
- @attribute_names = attribute_names
- end
-
- attr_reader :scope, :attribute_names
- alias :scope? :scope
- end
-end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
new file mode 100644
index 0000000000..313fdb3487
--- /dev/null
+++ b/activerecord/lib/active_record/explain.rb
@@ -0,0 +1,82 @@
+require 'active_support/core_ext/class/attribute'
+
+module ActiveRecord
+ module Explain
+ def self.extended(base)
+ # If a query takes longer than these many seconds we log its query plan
+ # automatically. nil disables this feature.
+ base.config_attribute :auto_explain_threshold_in_seconds, :global => true
+ end
+
+ # If auto explain is enabled, this method triggers EXPLAIN logging for the
+ # queries triggered by the block if it takes more than the threshold as a
+ # whole. That is, the threshold is not checked against each individual
+ # query, but against the duration of the entire block. This approach is
+ # convenient for relations.
+ #
+ # The available_queries_for_explain thread variable collects the queries
+ # to be explained. If the value is nil, it means queries are not being
+ # currently collected. A false value indicates collecting is turned
+ # off. Otherwise it is an array of queries.
+ def logging_query_plan # :nodoc:
+ return yield unless logger
+
+ threshold = auto_explain_threshold_in_seconds
+ current = Thread.current
+ if threshold && current[:available_queries_for_explain].nil?
+ begin
+ queries = current[:available_queries_for_explain] = []
+ start = Time.now
+ result = yield
+ logger.warn(exec_explain(queries)) if Time.now - start > threshold
+ result
+ ensure
+ current[:available_queries_for_explain] = nil
+ end
+ else
+ yield
+ end
+ end
+
+ # Relation#explain needs to be able to collect the queries regardless of
+ # whether auto explain is enabled. This method serves that purpose.
+ def collecting_queries_for_explain # :nodoc:
+ current = Thread.current
+ original, current[:available_queries_for_explain] = current[:available_queries_for_explain], []
+ return yield, current[:available_queries_for_explain]
+ ensure
+ # Note that the return value above does not depend on this assigment.
+ current[:available_queries_for_explain] = original
+ end
+
+ # Makes the adapter execute EXPLAIN for the tuples of queries and bindings.
+ # Returns a formatted string ready to be logged.
+ def exec_explain(queries) # :nodoc:
+ queries && queries.map do |sql, bind|
+ [].tap do |msg|
+ msg << "EXPLAIN for: #{sql}"
+ unless bind.empty?
+ bind_msg = bind.map {|col, val| [col.name, val]}.inspect
+ msg.last << " #{bind_msg}"
+ end
+ msg << connection.explain(sql, bind)
+ end.join("\n")
+ end.join("\n")
+ end
+
+ # Silences automatic EXPLAIN logging for the duration of the block.
+ #
+ # This has high priority, no EXPLAINs will be run even if downwards
+ # the threshold is set to 0.
+ #
+ # As the name of the method suggests this only applies to automatic
+ # EXPLAINs, manual calls to <tt>ActiveRecord::Relation#explain</tt> run.
+ def silence_auto_explain
+ current = Thread.current
+ original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false
+ yield
+ ensure
+ current[:available_queries_for_explain] = original
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
new file mode 100644
index 0000000000..1f8c4fc203
--- /dev/null
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -0,0 +1,24 @@
+require 'active_support/notifications'
+
+module ActiveRecord
+ class ExplainSubscriber # :nodoc:
+ def call(*args)
+ if queries = Thread.current[:available_queries_for_explain]
+ payload = args.last
+ queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
+ end
+ end
+
+ # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
+ # our own EXPLAINs now matter how loopingly beautiful that would be.
+ #
+ # On the other hand, we want to monitor the performance of our real database
+ # queries, not the performance of the access to the query cache.
+ IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE)
+ def ignore_payload?(payload)
+ payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name])
+ end
+
+ ActiveSupport::Notifications.subscribe("sql.active_record", new)
+ end
+end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index cad9417216..7e6512501c 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -1,30 +1,15 @@
require 'erb'
-
-begin
- require 'psych'
-rescue LoadError
-end
-
require 'yaml'
require 'zlib'
require 'active_support/dependencies'
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/logger'
-require 'active_support/ordered_hash'
require 'active_record/fixtures/file'
+require 'active_record/errors'
-if defined? ActiveRecord
+module ActiveRecord
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
end
-else
- class FixtureClassNotFound < StandardError #:nodoc:
- end
-end
-class FixturesFileNotFound < StandardError; end
-
-module ActiveRecord
# \Fixtures are a way of organizing data that you want to test against; in short, sample data.
#
# They are stored in YAML files, one file per model, which are placed in the directory
@@ -98,7 +83,7 @@ module ActiveRecord
# end
#
# test "find_alt_method_2" do
- # assert_equal "Ruby on Rails", @rubyonrails.news
+ # assert_equal "Ruby on Rails", @rubyonrails.name
# end
#
# In order to use these methods to access fixtured data within your testcases, you must specify one of the
@@ -387,14 +372,23 @@ module ActiveRecord
#
# Any fixture labeled "DEFAULTS" is safely ignored.
class Fixtures
+ #--
+ # NOTE: an instance of Fixtures can be called fixture_set, it is normally stored in a single YAML file and possibly in a folder with the same name.
+ #++
MAX_ID = 2 ** 30 - 1
@@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
- def self.find_table_name(table_name) # :nodoc:
+ def self.default_fixture_model_name(fixture_set_name) # :nodoc:
ActiveRecord::Base.pluralize_table_names ?
- table_name.to_s.singularize.camelize :
- table_name.to_s.camelize
+ fixture_set_name.singularize.camelize :
+ fixture_set_name.camelize
+ end
+
+ def self.default_fixture_table_name(fixture_set_name) # :nodoc:
+ "#{ ActiveRecord::Base.table_name_prefix }"\
+ "#{ fixture_set_name.tr('/', '_') }"\
+ "#{ ActiveRecord::Base.table_name_suffix }".to_sym
end
def self.reset_cache
@@ -421,11 +415,11 @@ module ActiveRecord
cache_for_connection(connection).update(fixtures_map)
end
- def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
+ def self.instantiate_fixtures(object, fixture_set, load_instances = true)
if load_instances
- fixtures.each do |name, fixture|
+ fixture_set.each do |fixture_name, fixture|
begin
- object.instance_variable_set "@#{name}", fixture.find
+ object.instance_variable_set "@#{fixture_name}", fixture.find
rescue FixtureClassNotFound
nil
end
@@ -434,63 +428,59 @@ module ActiveRecord
end
def self.instantiate_all_loaded_fixtures(object, load_instances = true)
- all_loaded_fixtures.each do |table_name, fixtures|
- ActiveRecord::Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ all_loaded_fixtures.each_value do |fixture_set|
+ instantiate_fixtures(object, fixture_set, load_instances)
end
end
cattr_accessor :all_loaded_fixtures
self.all_loaded_fixtures = {}
- def self.create_fixtures(fixtures_directory, table_names, class_names = {})
- table_names = [table_names].flatten.map { |n| n.to_s }
- table_names.each { |n|
- class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
- }
+ def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {})
+ fixture_set_names = Array(fixture_set_names).map(&:to_s)
+ class_names = class_names.stringify_keys
# FIXME: Apparently JK uses this.
connection = block_given? ? yield : ActiveRecord::Base.connection
- files_to_read = table_names.reject { |table_name|
- fixture_is_cached?(connection, table_name)
+ files_to_read = fixture_set_names.reject { |fs_name|
+ fixture_is_cached?(connection, fs_name)
}
unless files_to_read.empty?
connection.disable_referential_integrity do
fixtures_map = {}
- fixture_files = files_to_read.map do |path|
- table_name = path.tr '/', '_'
-
- fixtures_map[path] = ActiveRecord::Fixtures.new(
+ fixture_sets = files_to_read.map do |fs_name|
+ fixtures_map[fs_name] = new( # ActiveRecord::Fixtures.new
connection,
- table_name,
- class_names[table_name.to_sym] || table_name.classify,
- ::File.join(fixtures_directory, path))
+ fs_name,
+ class_names[fs_name] || default_fixture_model_name(fs_name),
+ ::File.join(fixtures_directory, fs_name))
end
all_loaded_fixtures.update(fixtures_map)
connection.transaction(:requires_new => true) do
- fixture_files.each do |ff|
- conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
- table_rows = ff.table_rows
+ fixture_sets.each do |fs|
+ conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
+ table_rows = fs.table_rows
table_rows.keys.each do |table|
conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
end
- table_rows.each do |table_name,rows|
+ table_rows.each do |fixture_set_name, rows|
rows.each do |row|
- conn.insert_fixture(row, table_name)
+ conn.insert_fixture(row, fixture_set_name)
end
end
end
# Cap primary key sequences to max(pk).
if connection.respond_to?(:reset_pk_sequence!)
- table_names.each do |table_name|
- connection.reset_pk_sequence!(table_name.tr('/', '_'))
+ fixture_sets.each do |fs|
+ connection.reset_pk_sequence!(fs.table_name)
end
end
end
@@ -498,7 +488,7 @@ module ActiveRecord
cache_fixtures(connection, fixtures_map)
end
end
- cached_fixtures(connection, table_names)
+ cached_fixtures(connection, fixture_set_names)
end
# Returns a consistent, platform-independent identifier for +label+.
@@ -509,25 +499,24 @@ module ActiveRecord
attr_reader :table_name, :name, :fixtures, :model_class
- def initialize(connection, table_name, class_name, fixture_path)
- @connection = connection
- @table_name = table_name
- @fixture_path = fixture_path
- @name = table_name # preserve fixture base name
- @class_name = class_name
-
- @fixtures = ActiveSupport::OrderedHash.new
- @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
-
- # Should be an AR::Base type class
- if class_name.is_a?(Class)
- @table_name = class_name.table_name
- @connection = class_name.connection
- @model_class = class_name
+ def initialize(connection, name, class_name, path)
+ @fixtures = {} # Ordered hash
+ @name = name
+ @path = path
+
+ if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
+ @model_class = class_name
else
- @model_class = class_name.constantize rescue nil
+ @model_class = class_name.constantize rescue nil
end
+ @connection = ( model_class.respond_to?(:connection) ?
+ model_class.connection : connection )
+
+ @table_name = ( model_class.respond_to?(:table_name) ?
+ model_class.table_name :
+ self.class.default_fixture_table_name(name) )
+
read_fixture_files
end
@@ -562,11 +551,11 @@ module ActiveRecord
rows[table_name] = fixtures.map do |label, fixture|
row = fixture.to_hash
- if model_class && model_class < ActiveRecord::Base
+ if model_class && model_class < ActiveRecord::Model
# fill in timestamp columns if they aren't specified and the model is set to record_timestamps
if model_class.record_timestamps
- timestamp_column_names.each do |name|
- row[name] = now unless row.key?(name)
+ timestamp_column_names.each do |c_name|
+ row[c_name] = now unless row.key?(c_name)
end
end
@@ -644,34 +633,23 @@ module ActiveRecord
end
def read_fixture_files
- if ::File.file?(yaml_file_path)
- read_yaml_fixture_files
- else
- raise FixturesFileNotFound, "Could not find #{yaml_file_path}"
- end
- end
-
- def read_yaml_fixture_files
- yaml_files = Dir["#{@fixture_path}/**/*.yml"].select { |f|
+ yaml_files = Dir["#{@path}/**/*.yml"].select { |f|
::File.file?(f)
} + [yaml_file_path]
yaml_files.each do |file|
Fixtures::File.open(file) do |fh|
- fh.each do |name, row|
- fixtures[name] = ActiveRecord::Fixture.new(row, model_class)
+ fh.each do |fixture_name, row|
+ fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
end
end
end
end
def yaml_file_path
- "#{@fixture_path}.yml"
+ "#{@path}.yml"
end
- def yaml_fixtures_key(path)
- ::File.basename(@fixture_path).split(".").first
- end
end
class Fixture #:nodoc:
@@ -726,7 +704,7 @@ module ActiveRecord
class_attribute :fixture_table_names
class_attribute :fixture_class_names
class_attribute :use_transactional_fixtures
- class_attribute :use_instantiated_fixtures # true, false, or :no_instances
+ class_attribute :use_instantiated_fixtures # true, false, or :no_instances
class_attribute :pre_loaded_fixtures
self.fixture_table_names = []
@@ -734,27 +712,43 @@ module ActiveRecord
self.use_instantiated_fixtures = false
self.pre_loaded_fixtures = false
- self.fixture_class_names = Hash.new do |h, table_name|
- h[table_name] = ActiveRecord::Fixtures.find_table_name(table_name)
+ self.fixture_class_names = Hash.new do |h, fixture_set_name|
+ h[fixture_set_name] = ActiveRecord::Fixtures.default_fixture_model_name(fixture_set_name)
end
end
module ClassMethods
+ # Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
+ #
+ # Examples:
+ #
+ # set_fixture_class :some_fixture => SomeModel,
+ # 'namespaced/fixture' => Another::Model
+ #
+ # The keys must be the fixture names, that coincide with the short paths to the fixture files.
+ #--
+ # It is also possible to pass the class name instead of the class:
+ # set_fixture_class 'some_fixture' => 'SomeModel'
+ # I think this option is redundant, i propose to deprecate it.
+ # Isn't it easier to always pass the class itself?
+ # (2011-12-20 alexeymuranov)
+ #++
def set_fixture_class(class_names = {})
- self.fixture_class_names = self.fixture_class_names.merge(class_names)
+ self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys)
end
- def fixtures(*fixture_names)
- if fixture_names.first == :all
- fixture_names = Dir["#{fixture_path}/**/*.{yml}"]
- fixture_names.map! { |f| f[(fixture_path.size + 1)..-5] }
+ def fixtures(*fixture_set_names)
+ if fixture_set_names.first == :all
+ fixture_set_names = Dir["#{fixture_path}/**/*.yml"].map { |f|
+ File.basename f, '.yml'
+ }
else
- fixture_names = fixture_names.flatten.map { |n| n.to_s }
+ fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
end
- self.fixture_table_names |= fixture_names
- require_fixture_classes(fixture_names)
- setup_fixture_accessors(fixture_names)
+ self.fixture_table_names |= fixture_set_names
+ require_fixture_classes(fixture_set_names)
+ setup_fixture_accessors(fixture_set_names)
end
def try_to_load_dependency(file_name)
@@ -769,40 +763,45 @@ module ActiveRecord
end
end
- def require_fixture_classes(fixture_names = nil)
- (fixture_names || fixture_table_names).each do |fixture_name|
- file_name = fixture_name.to_s
+ def require_fixture_classes(fixture_set_names = nil)
+ if fixture_set_names
+ fixture_set_names = fixture_set_names.map { |n| n.to_s }
+ else
+ fixture_set_names = fixture_table_names
+ end
+
+ fixture_set_names.each do |file_name|
file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
try_to_load_dependency(file_name)
end
end
- def setup_fixture_accessors(fixture_names = nil)
- fixture_names = Array.wrap(fixture_names || fixture_table_names)
+ def setup_fixture_accessors(fixture_set_names = nil)
+ fixture_set_names = Array(fixture_set_names || fixture_table_names)
methods = Module.new do
- fixture_names.each do |fixture_name|
- fixture_name = fixture_name.to_s.tr('./', '_')
+ fixture_set_names.each do |fs_name|
+ fs_name = fs_name.to_s
+ accessor_name = fs_name.tr('/', '_').to_sym
- define_method(fixture_name) do |*fixtures|
- force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
+ define_method(accessor_name) do |*fixture_names|
+ force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
- @fixture_cache[fixture_name] ||= {}
+ @fixture_cache[fs_name] ||= {}
- instances = fixtures.map do |fixture|
- @fixture_cache[fixture_name].delete(fixture) if force_reload
+ instances = fixture_names.map do |f_name|
+ f_name = f_name.to_s
+ @fixture_cache[fs_name].delete(f_name) if force_reload
- if @loaded_fixtures[fixture_name][fixture.to_s]
- ActiveRecord::IdentityMap.without do
- @fixture_cache[fixture_name][fixture] ||= @loaded_fixtures[fixture_name][fixture.to_s].find
- end
+ if @loaded_fixtures[fs_name][f_name]
+ @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
else
- raise StandardError, "No fixture with name '#{fixture}' found for table '#{fixture_name}'"
+ raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
end
end
instances.size == 1 ? instances.first : instances
end
- private fixture_name
+ private accessor_name
end
end
include methods
@@ -825,13 +824,14 @@ module ActiveRecord
end
def setup_fixtures
- return unless !ActiveRecord::Base.configurations.blank?
+ return if ActiveRecord::Base.configurations.blank?
if pre_loaded_fixtures && !use_transactional_fixtures
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
end
@fixture_cache = {}
+ @fixture_connections = []
@@already_loaded_fixtures ||= {}
# Load fixtures once and begin transaction.
@@ -902,8 +902,8 @@ module ActiveRecord
ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
else
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
- @loaded_fixtures.each do |fixture_name, fixtures|
- ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
+ @loaded_fixtures.each_value do |fixture_set|
+ ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_set, load_instances?)
end
end
end
diff --git a/activerecord/lib/active_record/fixtures/file.rb b/activerecord/lib/active_record/fixtures/file.rb
index 6bad36abb9..6547791144 100644
--- a/activerecord/lib/active_record/fixtures/file.rb
+++ b/activerecord/lib/active_record/fixtures/file.rb
@@ -1,8 +1,3 @@
-begin
- require 'psych'
-rescue LoadError
-end
-
require 'erb'
require 'yaml'
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
deleted file mode 100644
index b15b5a8133..0000000000
--- a/activerecord/lib/active_record/identity_map.rb
+++ /dev/null
@@ -1,157 +0,0 @@
-module ActiveRecord
- # = Active Record Identity Map
- #
- # Ensures that each object gets loaded only once by keeping every loaded
- # object in a map. Looks up objects using the map when referring to them.
- #
- # More information on Identity Map pattern:
- # http://www.martinfowler.com/eaaCatalog/identityMap.html
- #
- # == Configuration
- #
- # In order to enable IdentityMap, set <tt>config.active_record.identity_map = true</tt>
- # in your <tt>config/application.rb</tt> file.
- #
- # IdentityMap is disabled by default and still in development (i.e. use it with care).
- #
- # == Associations
- #
- # Active Record Identity Map does not track associations yet. For example:
- #
- # comment = @post.comments.first
- # comment.post = nil
- # @post.comments.include?(comment) #=> true
- #
- # Ideally, the example above would return false, removing the comment object from the
- # post association when the association is nullified. This may cause side effects, as
- # in the situation below, if Identity Map is enabled:
- #
- # Post.has_many :comments, :dependent => :destroy
- #
- # comment = @post.comments.first
- # comment.post = nil
- # comment.save
- # Post.destroy(@post.id)
- #
- # Without using Identity Map, the code above will destroy the @post object leaving
- # the comment object intact. However, once we enable Identity Map, the post loaded
- # by Post.destroy is exactly the same object as the object @post. As the object @post
- # still has the comment object in @post.comments, once Identity Map is enabled, the
- # comment object will be accidently removed.
- #
- # This inconsistency is meant to be fixed in future Rails releases.
- #
- module IdentityMap
-
- class << self
- def enabled=(flag)
- Thread.current[:identity_map_enabled] = flag
- end
-
- def enabled
- Thread.current[:identity_map_enabled]
- end
- alias enabled? enabled
-
- def repository
- Thread.current[:identity_map] ||= Hash.new { |h,k| h[k] = {} }
- end
-
- def use
- old, self.enabled = enabled, true
-
- yield if block_given?
- ensure
- self.enabled = old
- clear
- end
-
- def without
- old, self.enabled = enabled, false
-
- yield if block_given?
- ensure
- self.enabled = old
- end
-
- def get(klass, primary_key)
- record = repository[klass.symbolized_sti_name][primary_key]
-
- if record.is_a?(klass)
- ActiveSupport::Notifications.instrument("identity.active_record",
- :line => "From Identity Map (id: #{primary_key})",
- :name => "#{klass} Loaded",
- :connection_id => object_id)
-
- record
- else
- nil
- end
- end
-
- def add(record)
- repository[record.class.symbolized_sti_name][record.id] = record
- end
-
- def remove(record)
- repository[record.class.symbolized_sti_name].delete(record.id)
- end
-
- def remove_by_id(symbolized_sti_name, id)
- repository[symbolized_sti_name].delete(id)
- end
-
- def clear
- repository.clear
- end
- end
-
- # Reinitialize an Identity Map model object from +coder+.
- # +coder+ must contain the attributes necessary for initializing an empty
- # model object.
- def reinit_with(coder)
- @attributes_cache = {}
- dirty = @changed_attributes.keys
- @attributes.update(coder['attributes'].except(*dirty))
- @changed_attributes.update(coder['attributes'].slice(*dirty))
- @changed_attributes.delete_if{|k,v| v.eql? @attributes[k]}
-
- set_serialized_attributes
-
- run_callbacks :find
-
- self
- end
-
- class Middleware
- class Body #:nodoc:
- def initialize(target, original)
- @target = target
- @original = original
- end
-
- def each(&block)
- @target.each(&block)
- end
-
- def close
- @target.close if @target.respond_to?(:close)
- ensure
- IdentityMap.enabled = @original
- IdentityMap.clear
- end
- end
-
- def initialize(app)
- @app = app
- end
-
- def call(env)
- enabled = IdentityMap.enabled
- IdentityMap.enabled = true
- status, headers, body = @app.call(env)
- [status, headers, Body.new(body, enabled)]
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
new file mode 100644
index 0000000000..46d253b0a7
--- /dev/null
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -0,0 +1,181 @@
+require 'active_support/concern'
+
+module ActiveRecord
+ module Inheritance
+ extend ActiveSupport::Concern
+
+ included do
+ # Determine whether to store the full constant name including namespace when using STI
+ config_attribute :store_full_sti_class
+ self.store_full_sti_class = true
+ end
+
+ module ClassMethods
+ # True if this isn't a concrete subclass needing a STI type condition.
+ def descends_from_active_record?
+ sup = active_record_super
+
+ if sup.abstract_class?
+ sup.descends_from_active_record?
+ elsif self == Base
+ false
+ else
+ [Base, Model].include?(sup) || !columns_hash.include?(inheritance_column)
+ end
+ end
+
+ def finder_needs_type_condition? #:nodoc:
+ # This is like this because benchmarking justifies the strange :false stuff
+ :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
+ end
+
+ def symbolized_base_class
+ @symbolized_base_class ||= base_class.to_s.to_sym
+ end
+
+ def symbolized_sti_name
+ @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
+ end
+
+ # Returns the base AR subclass that this class descends from. If A
+ # extends AR::Base, A.base_class will return A. If B descends from A
+ # through some arbitrarily deep hierarchy, B.base_class will return A.
+ #
+ # If B < A and C < B and if A is an abstract_class then both B.base_class
+ # and C.base_class would return B as the answer since A is an abstract_class.
+ def base_class
+ class_of_active_record_descendant(self)
+ end
+
+ # Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
+ # If you are using inheritance with ActiveRecord and don't want child classes
+ # to utilize the implied STI table name of the parent class, this will need to be true.
+ # For example, given the following:
+ #
+ # class SuperClass < ActiveRecord::Base
+ # self.abstract_class = true
+ # end
+ # class Child < SuperClass
+ # self.table_name = 'the_table_i_really_want'
+ # end
+ #
+ #
+ # <tt>self.abstract_class = true</tt> is required to make <tt>Child<.find,.create, or any Arel method></tt> use <tt>the_table_i_really_want</tt> instead of a table called <tt>super_classes</tt>
+ #
+ attr_accessor :abstract_class
+
+ # Returns whether this class is an abstract class or not.
+ def abstract_class?
+ defined?(@abstract_class) && @abstract_class == true
+ end
+
+ def sti_name
+ store_full_sti_class ? name : name.demodulize
+ end
+
+ # Finder methods must instantiate through this method to work with the
+ # single-table inheritance model that makes it possible to create
+ # objects of different types from the same table.
+ def instantiate(record, column_types = {})
+ sti_class = find_sti_class(record[inheritance_column])
+ column_types = sti_class.decorate_columns(column_types)
+ sti_class.allocate.init_with('attributes' => record, 'column_types' => column_types)
+ end
+
+ # For internal use.
+ #
+ # If this class includes ActiveRecord::Model then it won't have a
+ # superclass. So this provides a way to get to the 'root' (ActiveRecord::Model).
+ def active_record_super #:nodoc:
+ superclass < Model ? superclass : Model
+ end
+
+ protected
+
+ # Returns the class descending directly from ActiveRecord::Base or an
+ # abstract class, if any, in the inheritance hierarchy.
+ def class_of_active_record_descendant(klass)
+ unless klass < Model
+ raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
+ end
+
+ sup = klass.active_record_super
+ if [Base, Model].include?(klass) || [Base, Model].include?(sup) || sup.abstract_class?
+ klass
+ else
+ class_of_active_record_descendant(sup)
+ end
+ end
+
+ # Returns the class type of the record using the current module as a prefix. So descendants of
+ # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
+ def compute_type(type_name)
+ if type_name.match(/^::/)
+ # If the type is prefixed with a scope operator then we assume that
+ # the type_name is an absolute reference.
+ ActiveSupport::Dependencies.constantize(type_name)
+ else
+ # Build a list of candidates to search for
+ candidates = []
+ name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
+ candidates << type_name
+
+ candidates.each do |candidate|
+ begin
+ constant = ActiveSupport::Dependencies.constantize(candidate)
+ return constant if candidate == constant.to_s
+ rescue NameError => e
+ # We don't want to swallow NoMethodError < NameError errors
+ raise e unless e.instance_of?(NameError)
+ end
+ end
+
+ raise NameError, "uninitialized constant #{candidates.first}"
+ end
+ end
+
+ private
+
+ def find_sti_class(type_name)
+ if type_name.blank? || !columns_hash.include?(inheritance_column)
+ self
+ else
+ begin
+ if store_full_sti_class
+ ActiveSupport::Dependencies.constantize(type_name)
+ else
+ compute_type(type_name)
+ end
+ rescue NameError
+ raise SubclassNotFound,
+ "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " +
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
+ "or overwrite #{name}.inheritance_column to use another column for that information."
+ end
+ end
+ end
+
+ def type_condition(table = arel_table)
+ sti_column = table[inheritance_column.to_sym]
+ sti_names = ([self] + descendants).map { |model| model.sti_name }
+
+ sti_column.in(sti_names)
+ end
+ end
+
+ private
+
+ # Sets the attribute used for single table inheritance to this class name if this is not the
+ # ActiveRecord::Base descendant.
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
+ # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
+ # No such attribute would be set for objects of the Message class in that example.
+ def ensure_proper_type
+ klass = self.class
+ if klass.finder_needs_type_condition?
+ write_attribute(klass.inheritance_column, klass.sti_name)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
new file mode 100644
index 0000000000..23c272ef12
--- /dev/null
+++ b/activerecord/lib/active_record/integration.rb
@@ -0,0 +1,49 @@
+module ActiveRecord
+ module Integration
+ # Returns a String, which Action Pack uses for constructing an URL to this
+ # object. The default implementation returns this record's id as a String,
+ # or nil if this record's unsaved.
+ #
+ # For example, suppose that you have a User model, and that you have a
+ # <tt>resources :users</tt> route. Normally, +user_path+ will
+ # construct a path with the user object's 'id' in it:
+ #
+ # user = User.find_by_name('Phusion')
+ # user_path(user) # => "/users/1"
+ #
+ # You can override +to_param+ in your model to make +user_path+ construct
+ # a path using the user's name instead of the user's id:
+ #
+ # class User < ActiveRecord::Base
+ # def to_param # overridden
+ # name
+ # end
+ # end
+ #
+ # user = User.find_by_name('Phusion')
+ # user_path(user) # => "/users/Phusion"
+ def to_param
+ # We can't use alias_method here, because method 'id' optimizes itself on the fly.
+ id && id.to_s # Be sure to stringify the id for routes
+ end
+
+ # Returns a cache key that can be used to identify this record.
+ #
+ # ==== Examples
+ #
+ # Product.new.cache_key # => "products/new"
+ # Product.find(5).cache_key # => "products/5" (updated_at not available)
+ # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
+ def cache_key
+ case
+ when new_record?
+ "#{self.class.model_name.cache_key}/new"
+ when timestamp = self[:updated_at]
+ timestamp = timestamp.utc.to_s(:nsec)
+ "#{self.class.model_name.cache_key}/#{id}-#{timestamp}"
+ else
+ "#{self.class.model_name.cache_key}/#{id}"
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml
index 44328f63b6..896132d566 100644
--- a/activerecord/lib/active_record/locale/en.yml
+++ b/activerecord/lib/active_record/locale/en.yml
@@ -10,6 +10,9 @@ en:
messages:
taken: "has already been taken"
record_invalid: "Validation failed: %{errors}"
+ restrict_dependent_destroy:
+ one: "Cannot delete record because a dependent %{record} exists"
+ many: "Cannot delete record because dependent %{record} exist"
# Append your own errors here or at the model/attributes scope.
# You can define own errors for models or model attributes.
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 2df3309648..a3412582fa 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -37,21 +37,22 @@ module ActiveRecord
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
# or otherwise apply the business logic needed to resolve the conflict.
#
- # You must ensure that your database schema defaults the +lock_version+ column to 0.
+ # This locking mechanism will function inside a single Ruby process. To make it work across all
+ # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form.
#
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
- # To override the name of the +lock_version+ column, invoke the <tt>set_locking_column</tt> method.
- # This method uses the same syntax as <tt>set_table_name</tt>
+ # To override the name of the +lock_version+ column, set the <tt>locking_column</tt> class attribute:
+ #
+ # class Person < ActiveRecord::Base
+ # self.locking_column = :lock_person
+ # end
+ #
module Optimistic
extend ActiveSupport::Concern
included do
- cattr_accessor :lock_optimistically, :instance_writer => false
+ config_attribute :lock_optimistically, :global => true
self.lock_optimistically = true
-
- class << self
- alias_method :locking_column=, :set_locking_column
- end
end
def locking_enabled? #:nodoc:
@@ -65,21 +66,6 @@ module ActiveRecord
send(lock_col + '=', previous_lock_value + 1)
end
- def attributes_from_column_definition
- result = super
-
- # If the locking column has no default value set,
- # start the lock version at zero. Note we can't use
- # <tt>locking_enabled?</tt> at this point as
- # <tt>@attributes</tt> may not have been initialized yet.
-
- if result.key?(self.class.locking_column) && lock_optimistically
- result[self.class.locking_column] ||= 0
- end
-
- result
- end
-
def update(attribute_names = @attributes.keys) #:nodoc:
return super unless locking_enabled?
return 0 if attribute_names.empty?
@@ -98,7 +84,7 @@ module ActiveRecord
relation.table[self.class.primary_key].eq(id).and(
relation.table[lock_col].eq(quote_value(previous_lock_value))
)
- ).arel.compile_update(arel_attributes_values(false, false, attribute_names))
+ ).arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
affected_rows = connection.update stmt
@@ -115,24 +101,29 @@ module ActiveRecord
end
end
- def destroy #:nodoc:
- return super unless locking_enabled?
+ def destroy_row
+ affected_rows = super
- if persisted?
- table = self.class.arel_table
- lock_col = self.class.locking_column
- predicate = table[self.class.primary_key].eq(id).
- and(table[lock_col].eq(send(lock_col).to_i))
+ if locking_enabled? && affected_rows != 1
+ raise ActiveRecord::StaleObjectError.new(self, "destroy")
+ end
- affected_rows = self.class.unscoped.where(predicate).delete_all
+ affected_rows
+ end
- unless affected_rows == 1
- raise ActiveRecord::StaleObjectError.new(self, "destroy")
- end
+ def relation_for_destroy
+ relation = super
+
+ if locking_enabled?
+ column_name = self.class.locking_column
+ column = self.class.columns_hash[column_name]
+ substitute = connection.substitute_at(column, relation.bind_values.length)
+
+ relation = relation.where(self.class.arel_table[column_name].eq(substitute))
+ relation.bind_values << [column, self[column_name].to_i]
end
- @destroyed = true
- freeze
+ relation
end
module ClassMethods
@@ -146,14 +137,14 @@ module ActiveRecord
end
# Set the column to use for optimistic locking. Defaults to +lock_version+.
- def set_locking_column(value = nil, &block)
- define_attr_method :locking_column, value, &block
- value
+ def locking_column=(value)
+ @locking_column = value.to_s
end
# The version column used for optimistic locking. Defaults to +lock_version+.
def locking_column
- reset_locking_column
+ reset_locking_column unless defined?(@locking_column)
+ @locking_column
end
# Quote the column name used for optimistic locking.
@@ -163,7 +154,7 @@ module ActiveRecord
# Reset the column used for optimistic locking back to the +lock_version+ default.
def reset_locking_column
- set_locking_column DEFAULT_LOCKING_COLUMN
+ self.locking_column = DEFAULT_LOCKING_COLUMN
end
# Make sure the lock version column gets updated when counters are
@@ -172,6 +163,18 @@ module ActiveRecord
counters = counters.merge(locking_column => 1) if locking_enabled?
super
end
+
+ # If the locking column has no default value set,
+ # start the lock version at zero. Note we can't use
+ # <tt>locking_enabled?</tt> at this point as
+ # <tt>@attributes</tt> may not have been initialized yet.
+ def initialize_attributes(attributes) #:nodoc:
+ if attributes.key?(locking_column) && lock_optimistically
+ attributes[locking_column] ||= 0
+ end
+
+ attributes
+ end
end
end
end
diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb
index 66994e4797..58af92f0b1 100644
--- a/activerecord/lib/active_record/locking/pessimistic.rb
+++ b/activerecord/lib/active_record/locking/pessimistic.rb
@@ -38,6 +38,18 @@ module ActiveRecord
# account2.save!
# end
#
+ # You can start a transaction and acquire the lock in one go by calling
+ # <tt>with_lock</tt> with a block. The block is called from within
+ # a transaction, the object is already locked. Example:
+ #
+ # account = Account.first
+ # account.with_lock do
+ # # This block is called within a transaction,
+ # # account is already locked.
+ # account.balance -= 100
+ # account.save!
+ # end
+ #
# Database-specific information on row locking:
# MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
# PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
@@ -50,6 +62,16 @@ module ActiveRecord
reload(:lock => lock) if persisted?
self
end
+
+ # Wraps the passed block in a transaction, locking the object
+ # before yielding. You pass can the SQL locking clause
+ # as argument (see <tt>lock!</tt>).
+ def with_lock(lock = true)
+ transaction do
+ lock!(lock)
+ yield
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 3a015ee8c2..a25f2c7bca 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -26,9 +26,9 @@ module ActiveRecord
return if 'SCHEMA' == payload[:name]
- name = '%s (%.1fms)' % [payload[:name], event.duration]
- sql = payload[:sql].squeeze(' ')
- binds = nil
+ name = '%s (%.1fms)' % [payload[:name], event.duration]
+ sql = payload[:sql].squeeze(' ')
+ binds = nil
unless (payload[:binds] || []).empty?
binds = " " + payload[:binds].map { |col,v|
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 7166f1b82a..2a9139749d 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,6 +1,8 @@
require "active_support/core_ext/module/delegation"
require "active_support/core_ext/class/attribute_accessors"
-require "active_support/core_ext/array/wrap"
+require 'active_support/deprecation'
+require 'active_record/schema_migration'
+require 'set'
module ActiveRecord
# Exception that can be raised to stop migrations from going backwards.
@@ -112,12 +114,13 @@ module ActiveRecord
# a column but keeps the type and content.
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
# the column to a different type using the same parameters as add_column.
- # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named
- # +column_name+ from the table called +table_name+.
+ # * <tt>remove_column(table_name, column_names)</tt>: Removes the column listed in
+ # +column_names+ from the table called +table_name+.
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
# with the name of the column. Other options include
- # <tt>:name</tt> and <tt>:unique</tt> (e.g.
- # <tt>{ :name => "users_name_index", :unique => true }</tt>).
+ # <tt>:name</tt>, <tt>:unique</tt> (e.g.
+ # <tt>{ :name => "users_name_index", :unique => true }</tt>) and <tt>:order</tt>
+ # (e.g. { :order => {:name => :desc} }</tt>).
# * <tt>remove_index(table_name, :column => column_name)</tt>: Removes the index
# specified by +column_name+.
# * <tt>remove_index(table_name, :name => index_name)</tt>: Removes the index
@@ -301,7 +304,7 @@ module ActiveRecord
#
# class TenderloveMigration < ActiveRecord::Migration
# def change
- # create_table(:horses) do
+ # create_table(:horses) do |t|
# t.column :content, :text
# t.column :remind_at, :datetime
# end
@@ -340,16 +343,28 @@ module ActiveRecord
attr_accessor :name, :version
- def initialize
- @name = self.class.name
- @version = nil
+ def initialize(name = self.class.name, version = nil)
+ @name = name
+ @version = version
@connection = nil
+ @reverting = false
end
# instantiate the delegate object after initialize is defined
self.verbose = true
self.delegate = new
+ def revert
+ @reverting = true
+ yield
+ ensure
+ @reverting = false
+ end
+
+ def reverting?
+ @reverting
+ end
+
def up
self.class.delegate = self
return unless self.class.respond_to?(:up)
@@ -383,9 +398,11 @@ module ActiveRecord
end
@connection = conn
time = Benchmark.measure {
- recorder.inverse.each do |cmd, args|
- send(cmd, *args)
- end
+ self.revert {
+ recorder.inverse.each do |cmd, args|
+ send(cmd, *args)
+ end
+ }
}
else
time = Benchmark.measure { change }
@@ -440,8 +457,11 @@ module ActiveRecord
arg_list = arguments.map{ |a| a.inspect } * ', '
say_with_time "#{method}(#{arg_list})" do
- unless arguments.empty? || method == :execute
- arguments[0] = Migrator.proper_table_name(arguments.first)
+ unless reverting?
+ unless arguments.empty? || method == :execute
+ arguments[0] = Migrator.proper_table_name(arguments.first)
+ arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
+ end
end
return super unless connection.respond_to?(method)
connection.send(method, *arguments, &block)
@@ -455,26 +475,28 @@ module ActiveRecord
destination_migrations = ActiveRecord::Migrator.migrations(destination)
last = destination_migrations.last
- sources.each do |name, path|
+ sources.each do |scope, path|
source_migrations = ActiveRecord::Migrator.migrations(path)
source_migrations.each do |migration|
source = File.read(migration.filename)
- source = "# This migration comes from #{name} (originally #{migration.version})\n#{source}"
+ source = "# This migration comes from #{scope} (originally #{migration.version})\n#{source}"
if duplicate = destination_migrations.detect { |m| m.name == migration.name }
- options[:on_skip].call(name, migration) if File.read(duplicate.filename) != source && options[:on_skip]
+ if options[:on_skip] && duplicate.scope != scope.to_s
+ options[:on_skip].call(scope, migration)
+ end
next
end
migration.version = next_migration_number(last ? last.version + 1 : 0).to_i
- new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.rb")
+ new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb")
old_path, migration.filename = migration.filename, new_path
last = migration
- FileUtils.cp(old_path, migration.filename)
+ File.open(migration.filename, "w") { |f| f.write source }
copied << migration
- options[:on_copy].call(name, migration, old_path) if options[:on_copy]
+ options[:on_copy].call(scope, migration, old_path) if options[:on_copy]
destination_migrations << migration
end
end
@@ -493,9 +515,9 @@ module ActiveRecord
# MigrationProxy is used to defer loading of the actual migration classes
# until they are needed
- class MigrationProxy < Struct.new(:name, :version, :filename)
+ class MigrationProxy < Struct.new(:name, :version, :filename, :scope)
- def initialize(name, version, filename)
+ def initialize(name, version, filename, scope)
super
@migration = nil
end
@@ -504,7 +526,7 @@ module ActiveRecord
File.basename(filename)
end
- delegate :migrate, :announce, :write, :to=>:migration
+ delegate :migrate, :announce, :write, :to => :migration
private
@@ -524,16 +546,16 @@ module ActiveRecord
attr_writer :migrations_paths
alias :migrations_path= :migrations_paths=
- def migrate(migrations_paths, target_version = nil)
+ def migrate(migrations_paths, target_version = nil, &block)
case
- when target_version.nil?
- up(migrations_paths, target_version)
- when current_version == 0 && target_version == 0
- []
- when current_version > target_version
- down(migrations_paths, target_version)
- else
- up(migrations_paths, target_version)
+ when target_version.nil?
+ up(migrations_paths, target_version, &block)
+ when current_version == 0 && target_version == 0
+ []
+ when current_version > target_version
+ down(migrations_paths, target_version, &block)
+ else
+ up(migrations_paths, target_version, &block)
end
end
@@ -546,24 +568,33 @@ module ActiveRecord
end
def up(migrations_paths, target_version = nil)
- self.new(:up, migrations_paths, target_version).migrate
+ migrations = migrations(migrations_paths)
+ migrations.select! { |m| yield m } if block_given?
+
+ self.new(:up, migrations, target_version).migrate
end
- def down(migrations_paths, target_version = nil)
- self.new(:down, migrations_paths, target_version).migrate
+ def down(migrations_paths, target_version = nil, &block)
+ migrations = migrations(migrations_paths)
+ migrations.select! { |m| yield m } if block_given?
+
+ self.new(:down, migrations, target_version).migrate
end
def run(direction, migrations_paths, target_version)
- self.new(direction, migrations_paths, target_version).run
+ self.new(direction, migrations(migrations_paths), target_version).run
+ end
+
+ def open(migrations_paths)
+ self.new(:up, migrations(migrations_paths), nil)
end
def schema_migrations_table_name
- Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
+ SchemaMigration.table_name
end
def get_all_versions
- table = Arel::Table.new(schema_migrations_table_name)
- Base.connection.select_values(table.project(table['version'])).map{ |v| v.to_i }.sort
+ SchemaMigration.all.map { |x| x.version.to_i }.sort
end
def current_version
@@ -583,7 +614,7 @@ module ActiveRecord
def migrations_paths
@migrations_paths ||= ['db/migrate']
# just to not break things if someone uses: migration_path = some_string
- Array.wrap(@migrations_paths)
+ Array(@migrations_paths)
end
def migrations_path
@@ -591,25 +622,18 @@ module ActiveRecord
end
def migrations(paths)
- paths = Array.wrap(paths)
-
- files = Dir[*paths.map { |p| "#{p}/[0-9]*_*.rb" }]
+ paths = Array(paths)
- seen = Hash.new false
+ files = Dir[*paths.map { |p| "#{p}/**/[0-9]*_*.rb" }]
migrations = files.map do |file|
- version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
+ version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
raise IllegalMigrationNameError.new(file) unless version
version = version.to_i
name = name.camelize
- raise DuplicateMigrationVersionError.new(version) if seen[version]
- raise DuplicateMigrationNameError.new(name) if seen[name]
-
- seen[version] = seen[name] = true
-
- MigrationProxy.new(name, version, file)
+ MigrationProxy.new(name, version, file, scope)
end
migrations.sort_by(&:version)
@@ -618,7 +642,7 @@ module ActiveRecord
private
def move(direction, migrations_paths, steps)
- migrator = self.new(direction, migrations_paths)
+ migrator = self.new(direction, migrations(migrations_paths))
start_index = migrator.migrations.index(migrator.current_migration)
if start_index
@@ -629,19 +653,33 @@ module ActiveRecord
end
end
- def initialize(direction, migrations_paths, target_version = nil)
+ def initialize(direction, migrations, target_version = nil)
raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
- Base.connection.initialize_schema_migrations_table
- @direction, @migrations_paths, @target_version = direction, migrations_paths, target_version
+
+ @direction = direction
+ @target_version = target_version
+ @migrated_versions = nil
+
+ if Array(migrations).grep(String).empty?
+ @migrations = migrations
+ else
+ ActiveSupport::Deprecation.warn "instantiate this class with a list of migrations"
+ @migrations = self.class.migrations(migrations)
+ end
+
+ validate(@migrations)
+
+ ActiveRecord::SchemaMigration.create_table
end
def current_version
- migrated.last || 0
+ migrated.sort.last || 0
end
def current_migration
migrations.detect { |m| m.version == current_version }
end
+ alias :current :current_migration
def run
target = migrations.detect { |m| m.version == @target_version }
@@ -653,96 +691,108 @@ module ActiveRecord
end
def migrate
- current = migrations.detect { |m| m.version == current_version }
- target = migrations.detect { |m| m.version == @target_version }
-
- if target.nil? && @target_version && @target_version > 0
+ if !target && @target_version && @target_version > 0
raise UnknownMigrationVersionError.new(@target_version)
end
- start = up? ? 0 : (migrations.index(current) || 0)
- finish = migrations.index(target) || migrations.size - 1
- runnable = migrations[start..finish]
+ running = runnable
- # skip the last migration if we're headed down, but not ALL the way down
- runnable.pop if down? && target
+ if block_given?
+ ActiveSupport::Deprecation.warn(<<-eomsg)
+block argument to migrate is deprecated, please filter migrations before constructing the migrator
+ eomsg
+ running.select! { |m| yield m }
+ end
- ran = []
- runnable.each do |migration|
+ running.each do |migration|
Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
- seen = migrated.include?(migration.version.to_i)
-
- # On our way up, we skip migrating the ones we've already migrated
- next if up? && seen
-
- # On our way down, we skip reverting the ones we've never migrated
- if down? && !seen
- migration.announce 'never migrated, skipping'; migration.write
- next
- end
-
begin
ddl_transaction do
migration.migrate(@direction)
record_version_state_after_migrating(migration.version)
end
- ran << migration
rescue => e
canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
end
end
- ran
end
- def migrations
- @migrations ||= begin
- migrations = self.class.migrations(@migrations_paths)
- down? ? migrations.reverse : migrations
+ def runnable
+ runnable = migrations[start..finish]
+ if up?
+ runnable.reject { |m| ran?(m) }
+ else
+ # skip the last migration if we're headed down, but not ALL the way down
+ runnable.pop if target
+ runnable.find_all { |m| ran?(m) }
end
end
+ def migrations
+ down? ? @migrations.reverse : @migrations.sort_by(&:version)
+ end
+
def pending_migrations
already_migrated = migrated
- migrations.reject { |m| already_migrated.include?(m.version.to_i) }
+ migrations.reject { |m| already_migrated.include?(m.version) }
end
def migrated
- @migrated_versions ||= self.class.get_all_versions
+ @migrated_versions ||= Set.new(self.class.get_all_versions)
end
private
- def record_version_state_after_migrating(version)
- table = Arel::Table.new(self.class.schema_migrations_table_name)
-
- @migrated_versions ||= []
- if down?
- @migrated_versions.delete(version)
- stmt = table.where(table["version"].eq(version.to_s)).compile_delete
- Base.connection.delete stmt
- else
- @migrated_versions.push(version).sort!
- stmt = table.compile_insert table["version"] => version.to_s
- Base.connection.insert stmt
- end
- end
+ def ran?(migration)
+ migrated.include?(migration.version.to_i)
+ end
- def up?
- @direction == :up
- end
+ def target
+ migrations.detect { |m| m.version == @target_version }
+ end
+
+ def finish
+ migrations.index(target) || migrations.size - 1
+ end
- def down?
- @direction == :down
+ def start
+ up? ? 0 : (migrations.index(current) || 0)
+ end
+
+ def validate(migrations)
+ name ,= migrations.group_by(&:name).find { |_,v| v.length > 1 }
+ raise DuplicateMigrationNameError.new(name) if name
+
+ version ,= migrations.group_by(&:version).find { |_,v| v.length > 1 }
+ raise DuplicateMigrationVersionError.new(version) if version
+ end
+
+ def record_version_state_after_migrating(version)
+ if down?
+ migrated.delete(version)
+ ActiveRecord::SchemaMigration.where(:version => version.to_s).delete_all
+ else
+ migrated << version
+ ActiveRecord::SchemaMigration.create!(:version => version.to_s)
end
+ end
- # Wrap the migration in a transaction only if supported by the adapter.
- def ddl_transaction(&block)
- if Base.connection.supports_ddl_transactions?
- Base.transaction { block.call }
- else
- block.call
- end
+ def up?
+ @direction == :up
+ end
+
+ def down?
+ @direction == :down
+ end
+
+ # Wrap the migration in a transaction only if supported by the adapter.
+ def ddl_transaction
+ if Base.connection.supports_ddl_transactions?
+ Base.transaction { yield }
+ else
+ yield
end
+ end
end
end
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index ffee5a081a..96b62fdd61 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -8,11 +8,14 @@ module ActiveRecord
# * add_index
# * add_timestamps
# * create_table
+ # * create_join_table
# * remove_timestamps
# * rename_column
# * rename_index
# * rename_table
class CommandRecorder
+ include JoinTable
+
attr_accessor :commands, :delegate
def initialize(delegate = nil)
@@ -48,7 +51,7 @@ module ActiveRecord
super || delegate.respond_to?(*args)
end
- [:create_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
+ [:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args) # def create_table(*args)
record(:"#{method}", args) # record(:create_table, args)
@@ -59,7 +62,13 @@ module ActiveRecord
private
def invert_create_table(args)
- [:drop_table, args]
+ [:drop_table, [args.first]]
+ end
+
+ def invert_create_join_table(args)
+ table_name = find_join_table_name(*args)
+
+ [:drop_table, [table_name]]
end
def invert_rename_table(args)
@@ -99,7 +108,6 @@ module ActiveRecord
rescue NoMethodError => e
raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}")
end
-
end
end
end
diff --git a/activerecord/lib/active_record/migration/join_table.rb b/activerecord/lib/active_record/migration/join_table.rb
new file mode 100644
index 0000000000..01a580781b
--- /dev/null
+++ b/activerecord/lib/active_record/migration/join_table.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class Migration
+ module JoinTable #:nodoc:
+ private
+
+ def find_join_table_name(table_1, table_2, options = {})
+ options.delete(:table_name) { join_table_name(table_1, table_2) }
+ end
+
+ def join_table_name(table_1, table_2)
+ tables_names = [table_1, table_2].map(&:to_s).sort
+
+ tables_names.join("_").to_sym
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb
new file mode 100644
index 0000000000..105d1e0e2b
--- /dev/null
+++ b/activerecord/lib/active_record/model.rb
@@ -0,0 +1,108 @@
+require 'active_support/deprecation'
+
+module ActiveRecord
+ # <tt>ActiveRecord::Model</tt> can be included into a class to add Active Record persistence.
+ # This is an alternative to inheriting from <tt>ActiveRecord::Base</tt>. Example:
+ #
+ # class Post
+ # include ActiveRecord::Model
+ # end
+ #
+ module Model
+ module ClassMethods #:nodoc:
+ include ActiveSupport::Callbacks::ClassMethods
+ include ActiveModel::Naming
+ include QueryCache::ClassMethods
+ include ActiveSupport::Benchmarkable
+ include ActiveSupport::DescendantsTracker
+
+ include Querying
+ include Translation
+ include DynamicMatchers
+ include CounterCache
+ include Explain
+ include ConnectionHandling
+ end
+
+ def self.included(base)
+ return if base.singleton_class < ClassMethods
+
+ base.class_eval do
+ extend ClassMethods
+ Callbacks::Register.setup(self)
+ initialize_generated_modules unless self == Base
+ end
+ end
+
+ extend ActiveModel::Configuration
+ extend ActiveModel::Callbacks
+ extend ActiveModel::MassAssignmentSecurity::ClassMethods
+ extend ActiveModel::AttributeMethods::ClassMethods
+ extend Callbacks::Register
+ extend Explain
+ extend ConnectionHandling
+
+ def self.extend(*modules)
+ ClassMethods.send(:include, *modules)
+ end
+
+ include Persistence
+ include ReadonlyAttributes
+ include ModelSchema
+ include Inheritance
+ include Scoping
+ include Sanitization
+ include Integration
+ include AttributeAssignment
+ include ActiveModel::Conversion
+ include Validations
+ include Locking::Optimistic, Locking::Pessimistic
+ include AttributeMethods
+ include Callbacks, ActiveModel::Observing, Timestamp
+ include Associations
+ include ActiveModel::SecurePassword
+ include AutosaveAssociation, NestedAttributes
+ include Aggregations, Transactions, Reflection, Serialization, Store
+ include Core
+
+ class << self
+ def arel_engine
+ self
+ end
+
+ def abstract_class?
+ false
+ end
+
+ def inheritance_column
+ 'type'
+ end
+ end
+
+ module DeprecationProxy #:nodoc:
+ class << self
+ instance_methods.each { |m| undef_method m unless m =~ /^__|^object_id$|^instance_eval$/ }
+
+ def method_missing(name, *args, &block)
+ if Model.respond_to?(name)
+ Model.send(name, *args, &block)
+ else
+ ActiveSupport::Deprecation.warn(
+ "The object passed to the active_record load hook was previously ActiveRecord::Base " \
+ "(a Class). Now it is ActiveRecord::Model (a Module). You have called `#{name}' which " \
+ "is only defined on ActiveRecord::Base. Please change your code so that it works with " \
+ "a module rather than a class. (Model is included in Base, so anything added to Model " \
+ "will be available on Base as well.)"
+ )
+ Base.send(name, *args, &block)
+ end
+ end
+
+ alias send method_missing
+ end
+ end
+ end
+
+ # Load Base at this point, because the active_record load hook is run in that file.
+ Base
+end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
new file mode 100644
index 0000000000..7f38dda11e
--- /dev/null
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -0,0 +1,342 @@
+require 'active_support/concern'
+require 'active_support/core_ext/class/attribute_accessors'
+
+module ActiveRecord
+ module ModelSchema
+ extend ActiveSupport::Concern
+
+ included do
+ ##
+ # :singleton-method:
+ # Accessor for the prefix type that will be prepended to every primary key column name.
+ # The options are :table_name and :table_name_with_underscore. If the first is specified,
+ # the Product class will look for "productid" instead of "id" as the primary column. If the
+ # latter is specified, the Product class will look for "product_id" instead of "id". Remember
+ # that this is a global setting for all Active Records.
+ config_attribute :primary_key_prefix_type, :global => true
+
+ ##
+ # :singleton-method:
+ # Accessor for the name of the prefix string to prepend to every table name. So if set
+ # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
+ # etc. This is a convenient way of creating a namespace for tables in a shared database.
+ # By default, the prefix is the empty string.
+ #
+ # If you are organising your models within modules you can add a prefix to the models within
+ # a namespace by defining a singleton method in the parent module called table_name_prefix which
+ # returns your chosen prefix.
+ config_attribute :table_name_prefix
+ self.table_name_prefix = ""
+
+ ##
+ # :singleton-method:
+ # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
+ # "people_basecamp"). By default, the suffix is the empty string.
+ config_attribute :table_name_suffix
+ self.table_name_suffix = ""
+
+ ##
+ # :singleton-method:
+ # Indicates whether table names should be the pluralized versions of the corresponding class names.
+ # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
+ # See table_name for the full rules on table/class naming. This is true, by default.
+ config_attribute :pluralize_table_names
+ self.pluralize_table_names = true
+ end
+
+ module ClassMethods
+ # Guesses the table name (in forced lower-case) based on the name of the class in the
+ # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
+ # looks like: Reply < Message < ActiveRecord::Base, then Message is used
+ # to guess the table name even when called on Reply. The rules used to do the guess
+ # are handled by the Inflector class in Active Support, which knows almost all common
+ # English inflections. You can add new inflections in config/initializers/inflections.rb.
+ #
+ # Nested classes are given table names prefixed by the singular form of
+ # the parent's table name. Enclosing modules are not considered.
+ #
+ # ==== Examples
+ #
+ # class Invoice < ActiveRecord::Base
+ # end
+ #
+ # file class table_name
+ # invoice.rb Invoice invoices
+ #
+ # class Invoice < ActiveRecord::Base
+ # class Lineitem < ActiveRecord::Base
+ # end
+ # end
+ #
+ # file class table_name
+ # invoice.rb Invoice::Lineitem invoice_lineitems
+ #
+ # module Invoice
+ # class Lineitem < ActiveRecord::Base
+ # end
+ # end
+ #
+ # file class table_name
+ # invoice/lineitem.rb Invoice::Lineitem lineitems
+ #
+ # Additionally, the class-level +table_name_prefix+ is prepended and the
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
+ # the table name guess for an Invoice class becomes "myapp_invoices".
+ # Invoice::Lineitem becomes "myapp_invoice_lineitems".
+ #
+ # You can also set your own table name explicitly:
+ #
+ # class Mouse < ActiveRecord::Base
+ # self.table_name = "mice"
+ # end
+ #
+ # Alternatively, you can override the table_name method to define your
+ # own computation. (Possibly using <tt>super</tt> to manipulate the default
+ # table name.) Example:
+ #
+ # class Post < ActiveRecord::Base
+ # def self.table_name
+ # "special_" + super
+ # end
+ # end
+ # Post.table_name # => "special_posts"
+ def table_name
+ reset_table_name unless defined?(@table_name)
+ @table_name
+ end
+
+ # Sets the table name explicitly. Example:
+ #
+ # class Project < ActiveRecord::Base
+ # self.table_name = "project"
+ # end
+ #
+ # You can also just define your own <tt>self.table_name</tt> method; see
+ # the documentation for ActiveRecord::Base#table_name.
+ def table_name=(value)
+ value = value && value.to_s
+
+ if defined?(@table_name)
+ return if value == @table_name
+ reset_column_information if connected?
+ end
+
+ @table_name = value
+ @quoted_table_name = nil
+ @arel_table = nil
+ @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
+ @relation = Relation.new(self, arel_table)
+ end
+
+ # Returns a quoted version of the table name, used to construct SQL statements.
+ def quoted_table_name
+ @quoted_table_name ||= connection.quote_table_name(table_name)
+ end
+
+ # Computes the table name, (re)sets it internally, and returns it.
+ def reset_table_name #:nodoc:
+ if abstract_class?
+ self.table_name = if active_record_super == Base || active_record_super.abstract_class?
+ nil
+ else
+ active_record_super.table_name
+ end
+ elsif active_record_super.abstract_class?
+ self.table_name = active_record_super.table_name || compute_table_name
+ else
+ self.table_name = compute_table_name
+ end
+ end
+
+ def full_table_name_prefix #:nodoc:
+ (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
+ end
+
+ # The name of the column containing the object's class when Single Table Inheritance is used
+ def inheritance_column
+ (@inheritance_column ||= nil) || active_record_super.inheritance_column
+ end
+
+ # Sets the value of inheritance_column
+ def inheritance_column=(value)
+ @inheritance_column = value.to_s
+ @explicit_inheritance_column = true
+ end
+
+ def sequence_name
+ if base_class == self
+ @sequence_name ||= reset_sequence_name
+ else
+ (@sequence_name ||= nil) || base_class.sequence_name
+ end
+ end
+
+ def reset_sequence_name #:nodoc:
+ @explicit_sequence_name = false
+ @sequence_name = connection.default_sequence_name(table_name, primary_key)
+ end
+
+ # Sets the name of the sequence to use when generating ids to the given
+ # value, or (if the value is nil or false) to the value returned by the
+ # given block. This is required for Oracle and is useful for any
+ # database which relies on sequences for primary key generation.
+ #
+ # If a sequence name is not explicitly set when using Oracle or Firebird,
+ # it will default to the commonly used pattern of: #{table_name}_seq
+ #
+ # If a sequence name is not explicitly set when using PostgreSQL, it
+ # will discover the sequence corresponding to your primary key for you.
+ #
+ # class Project < ActiveRecord::Base
+ # self.sequence_name = "projectseq" # default would have been "project_seq"
+ # end
+ def sequence_name=(value)
+ @sequence_name = value.to_s
+ @explicit_sequence_name = true
+ end
+
+ # Indicates whether the table associated with this class exists
+ def table_exists?
+ connection.schema_cache.table_exists?(table_name)
+ end
+
+ # Returns an array of column objects for the table associated with this class.
+ def columns
+ @columns ||= connection.schema_cache.columns[table_name].map do |col|
+ col = col.dup
+ col.primary = (col.name == primary_key)
+ col
+ end
+ end
+
+ # Returns a hash of column objects for the table associated with this class.
+ def columns_hash
+ @columns_hash ||= Hash[columns.map { |c| [c.name, c] }]
+ end
+
+ def column_types # :nodoc:
+ @column_types ||= decorate_columns(columns_hash.dup)
+ end
+
+ def decorate_columns(columns_hash) # :nodoc:
+ return if columns_hash.empty?
+
+ serialized_attributes.keys.each do |key|
+ columns_hash[key] = AttributeMethods::Serialization::Type.new(columns_hash[key])
+ end
+
+ columns_hash.each do |name, col|
+ if create_time_zone_conversion_attribute?(name, col)
+ columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col)
+ end
+ end
+
+ columns_hash
+ end
+
+ # Returns a hash where the keys are column names and the values are
+ # default values when instantiating the AR object for this table.
+ def column_defaults
+ @column_defaults ||= Hash[columns.map { |c| [c.name, c.default] }]
+ end
+
+ # Returns an array of column names as strings.
+ def column_names
+ @column_names ||= columns.map { |column| column.name }
+ end
+
+ # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
+ # and columns used for single table inheritance have been removed.
+ def content_columns
+ @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
+ end
+
+ # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
+ # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
+ # is available.
+ def column_methods_hash #:nodoc:
+ @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
+ attr_name = attr.to_s
+ methods[attr.to_sym] = attr_name
+ methods["#{attr}=".to_sym] = attr_name
+ methods["#{attr}?".to_sym] = attr_name
+ methods["#{attr}_before_type_cast".to_sym] = attr_name
+ methods
+ end
+ end
+
+ # Resets all the cached information about columns, which will cause them
+ # to be reloaded on the next request.
+ #
+ # The most common usage pattern for this method is probably in a migration,
+ # when just after creating a table you want to populate it with some default
+ # values, eg:
+ #
+ # class CreateJobLevels < ActiveRecord::Migration
+ # def up
+ # create_table :job_levels do |t|
+ # t.integer :id
+ # t.string :name
+ #
+ # t.timestamps
+ # end
+ #
+ # JobLevel.reset_column_information
+ # %w{assistant executive manager director}.each do |type|
+ # JobLevel.create(:name => type)
+ # end
+ # end
+ #
+ # def down
+ # drop_table :job_levels
+ # end
+ # end
+ def reset_column_information
+ connection.clear_cache!
+ undefine_attribute_methods
+ connection.schema_cache.clear_table_cache!(table_name) if table_exists?
+
+ @arel_engine = nil
+ @column_defaults = nil
+ @column_names = nil
+ @columns = nil
+ @columns_hash = nil
+ @column_types = nil
+ @content_columns = nil
+ @dynamic_methods_hash = nil
+ @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
+ @relation = nil
+ end
+
+ def clear_cache! # :nodoc:
+ connection.schema_cache.clear!
+ end
+
+ private
+
+ # Guesses the table name, but does not decorate it with prefix and suffix information.
+ def undecorated_table_name(class_name = base_class.name)
+ table_name = class_name.to_s.demodulize.underscore
+ table_name = table_name.pluralize if pluralize_table_names
+ table_name
+ end
+
+ # Computes and returns a table name according to default conventions.
+ def compute_table_name
+ base = base_class
+ if self == base
+ # Nested classes are prefixed with singular parent table name.
+ if parent < ActiveRecord::Model && !parent.abstract_class?
+ contained = parent.table_name
+ contained = contained.singularize if parent.pluralize_table_names
+ contained += '_'
+ end
+ "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{table_name_suffix}"
+ else
+ # STI subclasses always use their superclass' table.
+ base.table_name
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
deleted file mode 100644
index 0313abe456..0000000000
--- a/activerecord/lib/active_record/named_scope.rb
+++ /dev/null
@@ -1,200 +0,0 @@
-require 'active_support/core_ext/array'
-require 'active_support/core_ext/hash/except'
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/class/attribute'
-
-module ActiveRecord
- # = Active Record Named \Scopes
- module NamedScope
- extend ActiveSupport::Concern
-
- module ClassMethods
- # Returns an anonymous \scope.
- #
- # posts = Post.scoped
- # posts.size # Fires "select count(*) from posts" and returns the count
- # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
- #
- # fruits = Fruit.scoped
- # fruits = fruits.where(:color => 'red') if options[:red_only]
- # fruits = fruits.limit(10) if limited?
- #
- # Anonymous \scopes tend to be useful when procedurally generating complex
- # queries, where passing intermediate values (\scopes) around as first-class
- # objects is convenient.
- #
- # You can define a \scope that applies to all finders using
- # ActiveRecord::Base.default_scope.
- def scoped(options = nil)
- if options
- scoped.apply_finder_options(options)
- else
- if current_scope
- current_scope.clone
- else
- scope = relation.clone
- scope.default_scoped = true
- scope
- end
- end
- end
-
- ##
- # Collects attributes from scopes that should be applied when creating
- # an AR instance for the particular class this is called on.
- def scope_attributes # :nodoc:
- if current_scope
- current_scope.scope_for_create
- else
- scope = relation.clone
- scope.default_scoped = true
- scope.scope_for_create
- end
- end
-
- ##
- # Are there default attributes associated with this scope?
- def scope_attributes? # :nodoc:
- current_scope || default_scopes.any?
- end
-
- # Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
- # such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
- #
- # class Shirt < ActiveRecord::Base
- # scope :red, where(:color => 'red')
- # scope :dry_clean_only, joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true)
- # end
- #
- # 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>.
- # Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable;
- # <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
- # all behave as if Shirt.red really was an Array.
- #
- # These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce
- # all shirts that are both red and dry clean only.
- # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
- # returns the number of garments for which these criteria obtain. Similarly with
- # <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
- #
- # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which
- # the \scopes were defined. But they are also available to <tt>has_many</tt> associations. If,
- #
- # class Person < ActiveRecord::Base
- # has_many :shirts
- # end
- #
- # 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:
- #
- # class Shirt < ActiveRecord::Base
- # scope :colored, lambda { |color| where(:color => color) }
- # end
- #
- # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
- #
- # On Ruby 1.9 you can use the 'stabby lambda' syntax:
- #
- # scope :colored, ->(color) { where(:color => color) }
- #
- # 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 lambda, which will re-evaluate the scope each time
- # it is called:
- #
- # class Post < ActiveRecord::Base
- # scope :recent, lambda { where('published_at >= ?', Time.now - 1.week) }
- # end
- #
- # Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
- #
- # class Shirt < ActiveRecord::Base
- # scope :red, where(:color => 'red') do
- # def dom_id
- # 'red_shirts'
- # end
- # end
- # end
- #
- # Scopes can also be used while creating/building a record.
- #
- # class Article < ActiveRecord::Base
- # scope :published, where(:published => true)
- # end
- #
- # 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?
-
- 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 = scoped.merge(options)
-
- extension ? relation.extending(extension) : relation
- end
-
- singleton_class.send(:redefine_method, name, &scope_proc)
- end
-
- protected
-
- def valid_scope_name?(name)
- if respond_to?(name, true)
- logger.warn "Creating scope :#{name}. " \
- "Overwriting existing method #{self.name}.#{name}."
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index d2065d701f..95a2ddcc11 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -12,17 +12,17 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :nested_attributes_options, :instance_writer => false
+ config_attribute :nested_attributes_options
self.nested_attributes_options = {}
end
# = Active Record Nested Attributes
#
# Nested attributes allow you to save attributes on associated records
- # through the parent. By default nested attribute updating is turned off,
- # you can enable it using the accepts_nested_attributes_for class method.
- # When you enable nested attributes an attribute writer is defined on
- # the model.
+ # through the parent. By default nested attribute updating is turned off
+ # and you can enable it using the accepts_nested_attributes_for class
+ # method. When you enable nested attributes an attribute writer is
+ # defined on the model.
#
# The attribute writer is named after the association, which means that
# in the following example, two new methods are added to your model:
@@ -248,10 +248,18 @@ module ActiveRecord
# exception is raised. If omitted, any number associations can be processed.
# Note that the :limit option is only applicable to one-to-many associations.
# [:update_only]
- # Allows you to specify that an existing record may only be updated.
- # A new record may only be created when there is no existing record.
- # This option only works for one-to-one associations and is ignored for
- # collection associations. This option is off by default.
+ # For a one-to-one association, this option allows you to specify how
+ # nested attributes are to be used when an associated record already
+ # exists. In general, an existing record may either be updated with the
+ # new set of attribute values or be replaced by a wholly new record
+ # containing those values. By default the :update_only option is +false+
+ # and the nested attributes are used to update the existing record only
+ # if they include the record's <tt>:id</tt> value. Otherwise a new
+ # record will be instantiated and used to replace the existing one.
+ # However if the :update_only option is +true+, the nested attributes
+ # are used to update the record's attributes always, regardless of
+ # whether the <tt>:id</tt> is present. The option is ignored for collection
+ # associations.
#
# Examples:
# # creates avatar_attributes=
@@ -280,7 +288,7 @@ module ActiveRecord
# def pirate_attributes=(attributes)
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, mass_assignment_options)
# end
- class_eval <<-eoruby, __FILE__, __LINE__ + 1
+ generated_feature_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1
if method_defined?(:#{association_name}_attributes=)
remove_method(:#{association_name}_attributes=)
end
@@ -312,10 +320,13 @@ module ActiveRecord
# Assigns the given attributes to the association.
#
- # If update_only is false and the given attributes include an <tt>:id</tt>
- # that matches the existing record's id, then the existing record will be
- # modified. If update_only is true, a new record is only created when no
- # object exists. Otherwise a new record will be built.
+ # If an associated record does not yet exist, one will be instantiated. If
+ # an associated record already exists, the method's behavior depends on
+ # the value of the update_only option. If update_only is +false+ and the
+ # given attributes include an <tt>:id</tt> that matches the existing record's
+ # id, then the existing record will be modified. If no <tt>:id</tt> is provided
+ # it will be replaced with a new record. If update_only is +true+ the existing
+ # record will be modified regardless of whether an <tt>:id</tt> is provided.
#
# If the given attributes include a matching <tt>:id</tt> attribute, or
# update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
@@ -382,7 +393,7 @@ module ActiveRecord
if attributes_collection.is_a? Hash
keys = attributes_collection.keys
attributes_collection = if keys.include?('id') || keys.include?(:id)
- Array.wrap(attributes_collection)
+ [attributes_collection]
else
attributes_collection.values
end
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
new file mode 100644
index 0000000000..c2d3eeb8ce
--- /dev/null
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+
+module ActiveRecord
+ # = Active Record Null Relation
+ class NullRelation < Relation
+ def exec_queries
+ @records = []
+ end
+
+ def pluck(column_name)
+ []
+ end
+
+ def delete_all(conditions = nil)
+ 0
+ end
+
+ def update_all(updates, conditions = nil, options = {})
+ 0
+ end
+
+ def delete(id_or_array)
+ 0
+ end
+
+ def size
+ 0
+ end
+
+ def empty?
+ true
+ end
+
+ def any?
+ false
+ end
+
+ def many?
+ false
+ end
+
+ def to_sql
+ @to_sql ||= ""
+ end
+
+ def where_values_hash
+ {}
+ end
+
+ def count
+ 0
+ end
+
+ def calculate(operation, column_name, options = {})
+ nil
+ end
+
+ def exists?(id = false)
+ false
+ end
+
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 5e65e46a7d..a1bc39a32d 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -1,6 +1,53 @@
+require 'active_support/concern'
+
module ActiveRecord
# = Active Record Persistence
module Persistence
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Creates an object (or multiple objects) and saves it to the database, if validations pass.
+ # The resulting object is returned whether the object was saved successfully to the database or not.
+ #
+ # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the
+ # attributes on the objects that are to be created.
+ #
+ # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
+ # in the +options+ parameter.
+ #
+ # ==== Examples
+ # # Create a single new object
+ # User.create(:first_name => 'Jamie')
+ #
+ # # Create a single new object using the :admin mass-assignment security role
+ # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
+ #
+ # # Create a single new object bypassing mass-assignment security
+ # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
+ #
+ # # Create an Array of new objects
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
+ #
+ # # Create a single object and pass it into a block to set other attributes.
+ # User.create(:first_name => 'Jamie') do |u|
+ # u.is_admin = false
+ # end
+ #
+ # # Creating an Array of new objects using a block, where the block is executed for each object:
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
+ # u.is_admin = false
+ # end
+ def create(attributes = nil, options = {}, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr, options, &block) }
+ else
+ object = new(attributes, options, &block)
+ object.save
+ object
+ end
+ end
+ end
+
# Returns true if this object hasn't been saved yet -- that is, a record
# for the object doesn't exist in the data store yet; otherwise, returns false.
def new_record?
@@ -12,8 +59,8 @@ module ActiveRecord
@destroyed
end
- # Returns if the record is persisted, i.e. it's not a new record and it was
- # not destroyed.
+ # Returns true if the record is persisted, i.e. it's not a new record and it was
+ # not destroyed, otherwise returns false.
def persisted?
!(new_record? || destroyed?)
end
@@ -68,10 +115,7 @@ module ActiveRecord
# callbacks, Observer methods, or any <tt>:dependent</tt> association
# options, use <tt>#destroy</tt>.
def delete
- if persisted?
- self.class.delete(id)
- IdentityMap.remove(self) if IdentityMap.enabled?
- end
+ self.class.delete(id) if persisted?
@destroyed = true
freeze
end
@@ -79,21 +123,9 @@ module ActiveRecord
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
def destroy
+ raise ReadOnlyRecord if readonly?
destroy_associations
-
- if persisted?
- IdentityMap.remove(self) if IdentityMap.enabled?
- pk = self.class.primary_key
- column = self.class.columns_hash[pk]
- substitute = connection.substitute_at(column, 0)
-
- relation = self.class.unscoped.where(
- self.class.arel_table[pk].eq(substitute))
-
- relation.bind_values = [[column, id]]
- relation.delete_all
- end
-
+ destroy_row if persisted?
@destroyed = true
freeze
end
@@ -114,6 +146,7 @@ module ActiveRecord
became.instance_variable_set("@attributes_cache", @attributes_cache)
became.instance_variable_set("@new_record", new_record?)
became.instance_variable_set("@destroyed", destroyed?)
+ became.instance_variable_set("@errors", errors)
became.type = klass.name unless self.class.descends_from_active_record?
became
end
@@ -128,7 +161,7 @@ module ActiveRecord
#
def update_attribute(name, value)
name = name.to_s
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
+ verify_readonly_attribute(name)
send("#{name}=", value)
save(:validate => false)
end
@@ -143,10 +176,10 @@ module ActiveRecord
# attribute is marked as readonly.
def update_column(name, value)
name = name.to_s
- raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
+ verify_readonly_attribute(name)
raise ActiveRecordError, "can not update on a new record object" unless persisted?
raw_write_attribute(name, value)
- self.class.update_all({ name => value }, self.class.primary_key => id) == 1
+ self.class.where(self.class.primary_key => id).update_all(name => value) == 1
end
# Updates the attributes of the model from the passed-in hash and saves the
@@ -161,7 +194,7 @@ module ActiveRecord
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
- self.assign_attributes(attributes, options)
+ assign_attributes(attributes, options)
save
end
end
@@ -172,7 +205,7 @@ module ActiveRecord
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
- self.assign_attributes(attributes, options)
+ assign_attributes(attributes, options)
save!
end
end
@@ -236,10 +269,15 @@ module ActiveRecord
clear_aggregation_cache
clear_association_cache
- IdentityMap.without do
- fresh_object = self.class.unscoped { self.class.find(self.id, options) }
- @attributes.update(fresh_object.instance_variable_get('@attributes'))
- end
+ fresh_object =
+ if options && options[:lock]
+ self.class.unscoped { self.class.lock.find(id) }
+ else
+ self.class.unscoped { self.class.find(id) }
+ end
+
+ @attributes.update(fresh_object.instance_variable_get('@attributes'))
+ @columns_hash = fresh_object.instance_variable_get('@columns_hash')
@attributes_cache = {}
self
@@ -274,14 +312,15 @@ module ActiveRecord
changes = {}
attributes.each do |column|
- changes[column.to_s] = write_attribute(column.to_s, current_time)
+ column = column.to_s
+ changes[column] = write_attribute(column, current_time)
end
changes[self.class.locking_column] = increment_lock if locking_enabled?
@changed_attributes.except!(*changes.keys)
primary_key = self.class.primary_key
- self.class.unscoped.update_all(changes, { primary_key => self[primary_key] }) == 1
+ self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1
end
end
@@ -291,6 +330,22 @@ module ActiveRecord
def destroy_associations
end
+ def destroy_row
+ relation_for_destroy.delete_all
+ end
+
+ def relation_for_destroy
+ pk = self.class.primary_key
+ column = self.class.columns_hash[pk]
+ substitute = connection.substitute_at(column, 0)
+
+ relation = self.class.unscoped.where(
+ self.class.arel_table[pk].eq(substitute))
+
+ relation.bind_values = [[column, id]]
+ relation
+ end
+
def create_or_update
raise ReadOnlyRecord if readonly?
result = new_record? ? create : update
@@ -300,7 +355,7 @@ module ActiveRecord
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
def update(attribute_names = @attributes.keys)
- attributes_with_values = arel_attributes_values(false, false, attribute_names)
+ attributes_with_values = arel_attributes_with_values_for_update(attribute_names)
return 0 if attributes_with_values.empty?
klass = self.class
stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values)
@@ -310,23 +365,17 @@ module ActiveRecord
# Creates a record with values matching those of the instance attributes
# and returns its id.
def create
- attributes_values = arel_attributes_values(!id.nil?)
+ attributes_values = arel_attributes_with_values_for_create(!id.nil?)
new_id = self.class.unscoped.insert attributes_values
-
self.id ||= new_id if self.class.primary_key
- IdentityMap.add(self) if IdentityMap.enabled?
@new_record = false
id
end
- # Initializes the attributes array with keys matching the columns from the linked table and
- # the values matching the corresponding default value of that column, so
- # that a new instance, or one populated from a passed-in Hash, still has all the attributes
- # that instances loaded from the database would.
- def attributes_from_column_definition
- self.class.column_defaults.dup
+ def verify_readonly_attribute(name)
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
end
end
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 466d148901..9701898415 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -27,47 +27,22 @@ module ActiveRecord
@app = app
end
- class BodyProxy # :nodoc:
- def initialize(original_cache_value, target, connection_id)
- @original_cache_value = original_cache_value
- @target = target
- @connection_id = connection_id
- end
-
- def method_missing(method_sym, *arguments, &block)
- @target.send(method_sym, *arguments, &block)
- end
-
- def respond_to?(method_sym, include_private = false)
- super || @target.respond_to?(method_sym)
- end
-
- def each(&block)
- @target.each(&block)
- end
+ def call(env)
+ enabled = ActiveRecord::Base.connection.query_cache_enabled
+ connection_id = ActiveRecord::Base.connection_id
+ ActiveRecord::Base.connection.enable_query_cache!
- def close
- @target.close if @target.respond_to?(:close)
- ensure
- ActiveRecord::Base.connection_id = @connection_id
+ response = @app.call(env)
+ response[2] = Rack::BodyProxy.new(response[2]) do
+ ActiveRecord::Base.connection_id = connection_id
ActiveRecord::Base.connection.clear_query_cache
- unless @original_cache_value
- ActiveRecord::Base.connection.disable_query_cache!
- end
+ ActiveRecord::Base.connection.disable_query_cache! unless enabled
end
- end
- def call(env)
- old = ActiveRecord::Base.connection.query_cache_enabled
- ActiveRecord::Base.connection.enable_query_cache!
-
- status, headers, body = @app.call(env)
- [status, headers, BodyProxy.new(old, body, ActiveRecord::Base.connection_id)]
+ response
rescue Exception => e
ActiveRecord::Base.connection.clear_query_cache
- unless old
- ActiveRecord::Base.connection.disable_query_cache!
- end
+ ActiveRecord::Base.connection.disable_query_cache! unless enabled
raise e
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
new file mode 100644
index 0000000000..4d8283bcff
--- /dev/null
+++ b/activerecord/lib/active_record/querying.rb
@@ -0,0 +1,69 @@
+require 'active_support/core_ext/module/delegation'
+require 'active_support/deprecation'
+
+module ActiveRecord
+ module Querying
+ delegate :find, :take, :take!, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
+ delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped
+ delegate :find_by, :find_by!, :to => :scoped
+ delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
+ delegate :find_each, :find_in_batches, :to => :scoped
+ delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins,
+ :where, :preload, :eager_load, :includes, :from, :lock, :readonly,
+ :having, :create_with, :uniq, :references, :none, :to => :scoped
+ delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :scoped
+
+ # Executes a custom SQL query against your database and returns all the results. The results will
+ # be returned as an array with columns requested encapsulated as attributes of the model you call
+ # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
+ # a Product object with the attributes you specified in the SQL query.
+ #
+ # If you call a complicated SQL query which spans multiple tables the columns specified by the
+ # SELECT will be attributes of the model, whether or not they are columns of the corresponding
+ # table.
+ #
+ # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
+ # no database agnostic conversions performed. This should be a last resort because using, for example,
+ # MySQL specific terms will lock you to using that particular database engine or require you to
+ # change your call if you switch engines.
+ #
+ # ==== Examples
+ # # A simple SQL query spanning multiple tables
+ # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
+ # > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
+ #
+ # # You can use the same string replacement techniques as you can with ActiveRecord#find
+ # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
+ # > [#<Post:0x36bff9c @attributes={"title"=>"The Cheap Man Buys Twice"}>, ...]
+ def find_by_sql(sql, binds = [])
+ logging_query_plan do
+ result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
+ column_types = {}
+
+ if result_set.respond_to? :column_types
+ column_types = result_set.column_types
+ else
+ ActiveSupport::Deprecation.warn "the object returned from `select_all` must respond to `column_types`"
+ end
+
+ result_set.map { |record| instantiate(record, column_types) }
+ end
+ end
+
+ # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
+ # The use of this method should be restricted to complicated SQL queries that can't be executed
+ # using the ActiveRecord::Calculations class methods. Look into those before using this.
+ #
+ # ==== Parameters
+ #
+ # * +sql+ - An SQL statement which should return a count query from the database, see the example below.
+ #
+ # ==== Examples
+ #
+ # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
+ def count_by_sql(sql)
+ sql = sanitize_conditions(sql)
+ connection.select_value(sql, "#{name} Count").to_i
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 47133e77e8..eb2769f1ef 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -22,6 +22,13 @@ module ActiveRecord
config.app_middleware.insert_after "::ActionDispatch::Callbacks",
"ActiveRecord::ConnectionAdapters::ConnectionManagement"
+ config.action_dispatch.rescue_responses.merge!(
+ 'ActiveRecord::RecordNotFound' => :not_found,
+ 'ActiveRecord::StaleObjectError' => :conflict,
+ 'ActiveRecord::RecordInvalid' => :unprocessable_entity,
+ 'ActiveRecord::RecordNotSaved' => :unprocessable_entity
+ )
+
rake_tasks do
load "active_record/railties/databases.rake"
end
@@ -31,7 +38,8 @@ module ActiveRecord
# first time. Also, make it output to STDERR.
console do |app|
require "active_record/railties/console_sandbox" if app.sandbox?
- ActiveRecord::Base.logger = Logger.new(STDERR)
+ console = ActiveSupport::Logger.new(STDERR)
+ Rails.logger.extend ActiveSupport::Logger.broadcast console
end
initializer "active_record.initialize_timezone" do
@@ -45,11 +53,6 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger }
end
- initializer "active_record.identity_map" do |app|
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::IdentityMap::Middleware" if config.active_record.delete(:identity_map)
- end
-
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
if app.config.active_record.delete(:whitelist_attributes)
@@ -65,7 +68,9 @@ module ActiveRecord
# and then establishes the connection.
initializer "active_record.initialize_database" do |app|
ActiveSupport.on_load(:active_record) do
- self.configurations = app.config.database_configuration
+ unless ENV['DATABASE_URL']
+ self.configurations = app.config.database_configuration
+ end
establish_connection
end
end
@@ -78,23 +83,50 @@ module ActiveRecord
end
end
- initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app|
- ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.to_cleanup do
- ActiveRecord::Base.clear_reloadable_connections!
- ActiveRecord::Base.clear_cache!
+ initializer "active_record.set_reloader_hooks" do |app|
+ hook = lambda do
+ ActiveRecord::Base.clear_reloadable_connections!
+ ActiveRecord::Base.clear_cache!
+ end
+
+ if app.config.reload_classes_only_on_change
+ ActiveSupport.on_load(:active_record) do
+ ActionDispatch::Reloader.to_prepare(&hook)
+ end
+ else
+ ActiveSupport.on_load(:active_record) do
+ ActionDispatch::Reloader.to_cleanup(&hook)
end
end
end
- config.after_initialize do
+ initializer "active_record.add_watchable_files" do |app|
+ config.watchable_files.concat ["#{app.root}/db/schema.rb", "#{app.root}/db/structure.sql"]
+ end
+
+ config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
- instantiate_observers
+ ActiveRecord::Base.instantiate_observers
ActionDispatch::Reloader.to_prepare do
ActiveRecord::Base.instantiate_observers
end
end
+
+ ActiveSupport.on_load(:active_record) do
+ if app.config.use_schema_cache_dump
+ filename = File.join(app.config.paths["db"].first, "schema_cache.dump")
+ if File.file?(filename)
+ cache = Marshal.load File.binread filename
+ if cache.version == ActiveRecord::Migrator.current_version
+ ActiveRecord::Base.connection.schema_cache = cache
+ else
+ warn "schema_cache.dump is expired. Current version is #{ActiveRecord::Migrator.current_version}, but cache version is #{cache.version}."
+ end
+ end
+ end
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 4fb19b14ea..f26e18b1e0 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -1,8 +1,8 @@
require 'active_support/core_ext/object/inclusion'
+require 'active_record'
db_namespace = namespace :db do
task :load_config => :rails_env do
- require 'active_record'
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
@@ -37,11 +37,8 @@ db_namespace = namespace :db do
desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)'
task :create => :load_config do
- # Make the test database at the same time as the development one, if it exists
- if Rails.env.development? && ActiveRecord::Base.configurations['test']
- create_database(ActiveRecord::Base.configurations['test'])
- end
- create_database(ActiveRecord::Base.configurations[Rails.env])
+ configs_for_environment.each { |config| create_database(config) }
+ ActiveRecord::Base.establish_connection(configs_for_environment.first)
end
def mysql_creation_options(config)
@@ -115,8 +112,7 @@ db_namespace = namespace :db do
end
end
else
- # Bug with 1.9.2 Calling return within begin still executes else
- $stderr.puts "#{config['database']} already exists" unless config['adapter'] =~ /sqlite/
+ $stderr.puts "#{config['database']} already exists"
end
end
@@ -138,12 +134,7 @@ db_namespace = namespace :db do
desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)'
task :drop => :load_config do
- config = ActiveRecord::Base.configurations[Rails.env || 'development']
- begin
- drop_database(config)
- rescue Exception => e
- $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
- end
+ configs_for_environment.each { |config| drop_database_and_rescue(config) }
end
def local_database?(config, &block)
@@ -158,8 +149,22 @@ db_namespace = namespace :db do
desc "Migrate the database (options: VERSION=x, VERBOSE=false)."
task :migrate => [:environment, :load_config] do
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
- ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
- db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
+ ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
+ end
+ db_namespace['_dump'].invoke
+ end
+
+ task :_dump do
+ case ActiveRecord::Base.schema_format
+ when :ruby then db_namespace["schema:dump"].invoke
+ when :sql then db_namespace["structure:dump"].invoke
+ else
+ raise "unknown schema format #{ActiveRecord::Base.schema_format}"
+ end
+ # Allow this task to be called as many times as required. An example is the
+ # migrate:redo task, which calls other two internally that depend on this one.
+ db_namespace['_dump'].reenable
end
namespace :migrate do
@@ -182,7 +187,7 @@ db_namespace = namespace :db do
version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
raise 'VERSION is required' unless version
ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version)
- db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['_dump'].invoke
end
# desc 'Runs the "down" for a given migration VERSION.'
@@ -190,7 +195,7 @@ db_namespace = namespace :db do
version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
raise 'VERSION is required' unless version
ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
- db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['_dump'].invoke
end
desc 'Display status of migrations'
@@ -202,11 +207,12 @@ db_namespace = namespace :db do
next # means "return" for rake task
end
db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
+ db_list.map! { |version| "%.3d" % version }
file_list = []
ActiveRecord::Migrator.migrations_paths.each do |path|
Dir.foreach(path) do |file|
- # only files matching "20091231235959_some_name.rb" pattern
- if match_data = /^(\d{14})_(.+)\.rb$/.match(file)
+ # match "20091231235959_some_name.rb" and "001_some_name.rb" pattern
+ if match_data = /^(\d{3,})_(.+)\.rb$/.match(file)
status = db_list.delete(match_data[1]) ? 'up' : 'down'
file_list << [status, match_data[1], match_data[2].humanize]
end
@@ -230,18 +236,21 @@ db_namespace = namespace :db do
task :rollback => [:environment, :load_config] do
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
- db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['_dump'].invoke
end
# desc 'Pushes the schema to the next version (specify steps w/ STEP=n).'
task :forward => [:environment, :load_config] do
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step)
- db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['_dump'].invoke
end
# desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
- task :reset => [ 'db:drop', 'db:setup' ]
+ task :reset => :environment do
+ db_namespace["drop"].invoke
+ db_namespace["setup"].invoke
+ end
# desc "Retrieves the charset for the current environment's database"
task :charset => :environment do
@@ -280,24 +289,23 @@ db_namespace = namespace :db do
# desc "Raises an error if there are pending migrations"
task :abort_if_pending_migrations => :environment do
- if defined? ActiveRecord
- pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations
+ pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations
- if pending_migrations.any?
- puts "You have #{pending_migrations.size} pending migrations:"
- pending_migrations.each do |pending_migration|
- puts ' %4d %s' % [pending_migration.version, pending_migration.name]
- end
- abort %{Run `rake db:migrate` to update your database then try again.}
+ if pending_migrations.any?
+ puts "You have #{pending_migrations.size} pending migrations:"
+ pending_migrations.each do |pending_migration|
+ puts ' %4d %s' % [pending_migration.version, pending_migration.name]
end
+ abort %{Run `rake db:migrate` to update your database then try again.}
end
end
desc 'Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)'
- task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ]
+ task :setup => ['db:schema:load_if_ruby', 'db:structure:load_if_sql', :seed]
desc 'Load the seed data from db/seeds.rb'
- task :seed => 'db:abort_if_pending_migrations' do
+ task :seed do
+ db_namespace['abort_if_pending_migrations'].invoke
Rails.application.load_seed
end
@@ -360,90 +368,142 @@ db_namespace = namespace :db do
abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded}
end
end
+
+ task :load_if_ruby => 'db:create' do
+ db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby
+ end
+
+ namespace :cache do
+ desc 'Create a db/schema_cache.dump file.'
+ task :dump => :environment do
+ con = ActiveRecord::Base.connection
+ filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
+
+ con.schema_cache.clear!
+ con.tables.each { |table| con.schema_cache.add(table) }
+ open(filename, 'wb') { |f| f.write(Marshal.dump(con.schema_cache)) }
+ end
+
+ desc 'Clear a db/schema_cache.dump file.'
+ task :clear => :environment do
+ filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
+ FileUtils.rm(filename) if File.exists?(filename)
+ end
+ end
+
end
namespace :structure do
- desc 'Dump the database structure to an SQL file'
+ desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
+ filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
case abcs[Rails.env]['adapter']
when /mysql/, 'oci', 'oracle'
ActiveRecord::Base.establish_connection(abcs[Rails.env])
- File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
+ File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
when /postgresql/
- ENV['PGHOST'] = abcs[Rails.env]['host'] if abcs[Rails.env]['host']
- ENV['PGPORT'] = abcs[Rails.env]["port"].to_s if abcs[Rails.env]['port']
- ENV['PGPASSWORD'] = abcs[Rails.env]['password'].to_s if abcs[Rails.env]['password']
+ set_psql_env(abcs[Rails.env])
search_path = abcs[Rails.env]['schema_search_path']
unless search_path.blank?
search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.strip}" }.join(" ")
end
- `pg_dump -i -U "#{abcs[Rails.env]['username']}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]['database']}`
+ `pg_dump -i -s -x -O -f #{filename} #{search_path} #{abcs[Rails.env]['database']}`
raise 'Error dumping database' if $?.exitstatus == 1
when /sqlite/
- dbfile = abcs[Rails.env]['database'] || abcs[Rails.env]['dbfile']
- `sqlite3 #{dbfile} .schema > db/#{Rails.env}_structure.sql`
+ dbfile = abcs[Rails.env]['database']
+ `sqlite3 #{dbfile} .schema > #{filename}`
when 'sqlserver'
- `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f db\\#{Rails.env}_structure.sql -A -U`
+ `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f #{filename} -A -U`
when "firebird"
set_firebird_env(abcs[Rails.env])
db_string = firebird_db_string(abcs[Rails.env])
- sh "isql -a #{db_string} > #{Rails.root}/db/#{Rails.env}_structure.sql"
+ sh "isql -a #{db_string} > #{filename}"
else
raise "Task not supported by '#{abcs[Rails.env]["adapter"]}'"
end
if ActiveRecord::Base.connection.supports_migrations?
- File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
+ File.open(filename, "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
end
- end
- end
-
- namespace :test do
- # desc "Recreate the test database from the current schema.rb"
- task :load => 'db:test:purge' do
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
- ActiveRecord::Schema.verbose = false
- db_namespace['schema:load'].invoke
+ db_namespace['structure:dump'].reenable
end
- # desc "Recreate the test database from the current environment's database schema"
- task :clone => %w(db:schema:dump db:test:load)
+ # desc "Recreate the databases from the structure.sql file"
+ task :load => [:environment, :load_config] do
+ env = ENV['RAILS_ENV'] || 'test'
- # desc "Recreate the test databases from the development structure"
- task :clone_structure => [ 'db:structure:dump', 'db:test:purge' ] do
abcs = ActiveRecord::Base.configurations
- case abcs['test']['adapter']
+ filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
+ case abcs[env]['adapter']
when /mysql/
- ActiveRecord::Base.establish_connection(:test)
+ ActiveRecord::Base.establish_connection(abcs[env])
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
- IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split("\n\n").each do |table|
+ IO.read(filename).split("\n\n").each do |table|
ActiveRecord::Base.connection.execute(table)
end
when /postgresql/
- ENV['PGHOST'] = abcs['test']['host'] if abcs['test']['host']
- ENV['PGPORT'] = abcs['test']['port'].to_s if abcs['test']['port']
- ENV['PGPASSWORD'] = abcs['test']['password'].to_s if abcs['test']['password']
- `psql -U "#{abcs['test']['username']}" -f "#{Rails.root}/db/#{Rails.env}_structure.sql" #{abcs['test']['database']} #{abcs['test']['template']}`
+ set_psql_env(abcs[env])
+ `psql -f "#{filename}" #{abcs[env]['database']}`
when /sqlite/
- dbfile = abcs['test']['database'] || abcs['test']['dbfile']
- `sqlite3 #{dbfile} < "#{Rails.root}/db/#{Rails.env}_structure.sql"`
+ dbfile = abcs[env]['database']
+ `sqlite3 #{dbfile} < "#{filename}"`
when 'sqlserver'
- `sqlcmd -S #{abcs['test']['host']} -d #{abcs['test']['database']} -U #{abcs['test']['username']} -P #{abcs['test']['password']} -i db\\#{Rails.env}_structure.sql`
+ `sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i #{filename}`
when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(:test)
- IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl|
+ ActiveRecord::Base.establish_connection(abcs[env])
+ IO.read(filename).split(";\n\n").each do |ddl|
ActiveRecord::Base.connection.execute(ddl)
end
when 'firebird'
- set_firebird_env(abcs['test'])
- db_string = firebird_db_string(abcs['test'])
- sh "isql -i #{Rails.root}/db/#{Rails.env}_structure.sql #{db_string}"
+ set_firebird_env(abcs[env])
+ db_string = firebird_db_string(abcs[env])
+ sh "isql -i #{filename} #{db_string}"
else
- raise "Task not supported by '#{abcs['test']['adapter']}'"
+ raise "Task not supported by '#{abcs[env]['adapter']}'"
+ end
+ end
+
+ task :load_if_sql => 'db:create' do
+ db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql
+ end
+ end
+
+ namespace :test do
+
+ # desc "Recreate the test database from the current schema"
+ task :load => 'db:test:purge' do
+ case ActiveRecord::Base.schema_format
+ when :ruby
+ db_namespace["test:load_schema"].invoke
+ when :sql
+ db_namespace["test:load_structure"].invoke
+ end
+ end
+
+ # desc "Recreate the test database from an existent structure.sql file"
+ task :load_structure => 'db:test:purge' do
+ begin
+ old_env, ENV['RAILS_ENV'] = ENV['RAILS_ENV'], 'test'
+ db_namespace["structure:load"].invoke
+ ensure
+ ENV['RAILS_ENV'] = old_env
end
end
+ # desc "Recreate the test database from an existent schema.rb file"
+ task :load_schema => 'db:test:purge' do
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
+ ActiveRecord::Schema.verbose = false
+ db_namespace["schema:load"].invoke
+ end
+
+ # desc "Recreate the test database from a fresh schema.rb file"
+ task :clone => %w(db:schema:dump db:test:load_schema)
+
+ # desc "Recreate the test database from a fresh structure.sql file"
+ task :clone_structure => [ "db:structure:dump", "db:test:load_structure" ]
+
# desc "Empty the test database"
task :purge => :environment do
abcs = ActiveRecord::Base.configurations
@@ -456,7 +516,7 @@ db_namespace = namespace :db do
drop_database(abcs['test'])
create_database(abcs['test'])
when /sqlite/
- dbfile = abcs['test']['database'] || abcs['test']['dbfile']
+ dbfile = abcs['test']['database']
File.delete(dbfile) if File.exist?(dbfile)
when 'sqlserver'
test = abcs.deep_dup['test']
@@ -479,7 +539,7 @@ db_namespace = namespace :db do
# desc 'Check for pending migrations and load the test schema'
task :prepare => 'db:abort_if_pending_migrations' do
- if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
+ unless ActiveRecord::Base.configurations.blank?
db_namespace[{ :sql => 'test:clone_structure', :ruby => 'test:load' }[ActiveRecord::Base.schema_format]].invoke
end
end
@@ -503,10 +563,10 @@ end
namespace :railties do
namespace :install do
- # desc "Copies missing migrations from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2"
+ # desc "Copies missing migrations from Railties (e.g. engines). You can specify Railties to use with FROM=railtie1,railtie2"
task :migrations => :'db:load_config' do
to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip }
- railties = ActiveSupport::OrderedHash.new
+ railties = {}
Rails.application.railties.all do |railtie|
next unless to_load == :all || to_load.include?(railtie.railtie_name)
@@ -548,6 +608,20 @@ def drop_database(config)
end
end
+def drop_database_and_rescue(config)
+ begin
+ drop_database(config)
+ rescue Exception => e
+ $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
+ end
+end
+
+def configs_for_environment
+ environments = [Rails.env]
+ environments << 'test' if Rails.env.development?
+ ActiveRecord::Base.configurations.values_at(*environments).compact.reject { |config| config['database'].blank? }
+end
+
def session_table_name
ActiveRecord::SessionStore::Session.table_name
end
@@ -560,3 +634,10 @@ end
def firebird_db_string(config)
FireRuby::Database.db_string_for(config.symbolize_keys)
end
+
+def set_psql_env(config)
+ ENV['PGHOST'] = config['host'] if config['host']
+ ENV['PGPORT'] = config['port'].to_s if config['port']
+ ENV['PGPASSWORD'] = config['password'].to_s if config['password']
+ ENV['PGUSER'] = config['username'].to_s if config['username']
+end
diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb
index 6b9af2a0cb..6a38211bff 100644
--- a/activerecord/lib/active_record/railties/jdbcmysql_error.rb
+++ b/activerecord/lib/active_record/railties/jdbcmysql_error.rb
@@ -1,6 +1,6 @@
#FIXME Remove if ArJdbcMysql will give.
-module ArJdbcMySQL
- class Error < StandardError
+module ArJdbcMySQL #:nodoc:
+ class Error < StandardError #:nodoc:
attr_accessor :error_number, :sql_state
def initialize msg
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
new file mode 100644
index 0000000000..836b15e2ce
--- /dev/null
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -0,0 +1,26 @@
+require 'active_support/concern'
+require 'active_support/core_ext/class/attribute'
+
+module ActiveRecord
+ module ReadonlyAttributes
+ extend ActiveSupport::Concern
+
+ included do
+ config_attribute :_attr_readonly
+ self._attr_readonly = []
+ end
+
+ module ClassMethods
+ # Attributes listed as readonly will be used to create a new record but update operations will
+ # ignore these fields.
+ def attr_readonly(*attributes)
+ self._attr_readonly = Set.new(attributes.map { |a| a.to_s }) + (self._attr_readonly || [])
+ end
+
+ # Returns an array of all the attributes that have been specified as readonly.
+ def readonly_attributes
+ self._attr_readonly
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 5285060288..c380b5c029 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -7,7 +7,8 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :reflections
+ extend ActiveModel::Configuration
+ config_attribute :reflections
self.reflections = {}
end
@@ -22,11 +23,11 @@ module ActiveRecord
module ClassMethods
def create_reflection(macro, name, options, active_record)
case macro
- when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
- klass = options[:through] ? ThroughReflection : AssociationReflection
- reflection = klass.new(macro, name, options, active_record)
- when :composed_of
- reflection = AggregateReflection.new(macro, name, options, active_record)
+ when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
+ klass = options[:through] ? ThroughReflection : AssociationReflection
+ reflection = klass.new(macro, name, options, active_record)
+ when :composed_of
+ reflection = AggregateReflection.new(macro, name, options, active_record)
end
self.reflections = self.reflections.merge(name => reflection)
@@ -43,7 +44,8 @@ module ActiveRecord
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
#
def reflect_on_aggregation(aggregation)
- reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
+ reflection = reflections[aggregation]
+ reflection if reflection.is_a?(AggregateReflection)
end
# Returns an array of AssociationReflection objects for all the
@@ -67,7 +69,8 @@ module ActiveRecord
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
def reflect_on_association(association)
- reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
+ reflection = reflections[association]
+ reflection if reflection.is_a?(AssociationReflection)
end
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
@@ -76,7 +79,6 @@ module ActiveRecord
end
end
-
# Abstract base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
class MacroReflection
@@ -124,7 +126,7 @@ module ActiveRecord
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
def class_name
- @class_name ||= options[:class_name] || derive_class_name
+ @class_name ||= (options[:class_name] || derive_class_name).to_s
end
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
@@ -151,6 +153,10 @@ module ActiveRecord
# Holds all the meta-data about an aggregation as it was specified in the
# Active Record class.
class AggregateReflection < MacroReflection #:nodoc:
+ def mapping
+ mapping = options[:mapping] || [name, name]
+ mapping.first.is_a?(Array) ? mapping : [mapping]
+ end
end
# Holds all the meta-data about an association as it was specified in the
@@ -174,7 +180,7 @@ module ActiveRecord
def initialize(macro, name, options, active_record)
super
- @collection = macro.in?([:has_many, :has_and_belongs_to_many])
+ @collection = [:has_many, :has_and_belongs_to_many].include?(macro)
end
# Returns a new, unsaved instance of the associated class. +options+ will
@@ -228,8 +234,8 @@ module ActiveRecord
end
end
- def columns(tbl_name, log_msg)
- @columns ||= klass.connection.columns(tbl_name, log_msg)
+ def columns(tbl_name)
+ @columns ||= klass.connection.columns(tbl_name)
end
def reset_column_information
@@ -262,6 +268,10 @@ module ActiveRecord
[self]
end
+ def nested?
+ false
+ end
+
# An array of arrays of conditions. Each item in the outside array corresponds to a reflection
# in the #chain. The inside arrays are simply conditions (and each condition may itself be
# a hash, array, arel predicate, etc...)
@@ -433,7 +443,7 @@ module ActiveRecord
# of relevant reflections, plus any :source_type or polymorphic :as constraints.
def conditions
@conditions ||= begin
- conditions = source_reflection.conditions
+ conditions = source_reflection.conditions.map { |c| c.dup }
# Add to it the conditions from this reflection if necessary.
conditions.first << options[:conditions] if options[:conditions]
@@ -447,7 +457,6 @@ module ActiveRecord
# Recursively fill out the rest of the array from the through reflection
conditions += through_conditions
- # And return
conditions
end
end
@@ -457,7 +466,7 @@ module ActiveRecord
source_reflection.source_macro
end
- # A through association is nested iff there would be more than one join table
+ # A through association is nested if there would be more than one join table
def nested?
chain.length > 2 || through_reflection.macro == :has_and_belongs_to_many
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index ecefaa633c..05ced3299b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,36 +1,35 @@
+# -*- coding: utf-8 -*-
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/module/delegation'
+require 'active_support/deprecation'
module ActiveRecord
# = Active Record Relation
class Relation
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, :from, :reorder, :reverse_order]
- include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
+ MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
+ :order, :joins, :where, :having, :bind, :references,
+ :extending]
- # These are explicitly delegated to improve performance (avoids method_missing)
- delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
- delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :column_hash,:to => :klass
+ SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
+ :reverse_order, :uniq, :create_with]
+
+ VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
+
+ include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
attr_reader :table, :klass, :loaded
- attr_accessor :extensions, :default_scoped
+ attr_accessor :default_scoped
alias :loaded? :loaded
alias :default_scoped? :default_scoped
- def initialize(klass, table)
- @klass, @table = klass, table
-
+ def initialize(klass, table, values = {})
+ @klass = klass
+ @table = table
+ @values = values
@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", [])}
- @extensions = []
- @create_with_value = {}
end
def insert(values)
@@ -81,6 +80,8 @@ module ActiveRecord
end
def initialize_copy(other)
+ @values = @values.dup
+ @values[:bind] = @values[:bind].dup if @values[:bind]
reset
end
@@ -136,36 +137,51 @@ module ActiveRecord
first || new(attributes, options, &block)
end
- def respond_to?(method, include_private = false)
- arel.respond_to?(method, include_private) ||
- Array.method_defined?(method) ||
- @klass.respond_to?(method, include_private) ||
- super
+ # Runs EXPLAIN on the query or queries triggered by this relation and
+ # returns the result as a string. The string is formatted imitating the
+ # ones printed by the database shell.
+ #
+ # Note that this method actually runs the queries, since the results of some
+ # are needed by the next ones when eager loading is going on.
+ #
+ # Please see further details in the
+ # {Active Record Query Interface guide}[http://edgeguides.rubyonrails.org/active_record_querying.html#running-explain].
+ def explain
+ _, queries = collecting_queries_for_explain { exec_queries }
+ exec_explain(queries)
end
def to_a
+ # We monitor here the entire execution rather than individual SELECTs
+ # because from the point of view of the user fetching the records of a
+ # relation is a single unit of work. You want to know if this call takes
+ # too long, not if the individual queries take too long.
+ #
+ # It could be the case that none of the queries involved surpass the
+ # threshold, and at the same time the sum of them all does. The user
+ # should get a query plan logged in that case.
+ logging_query_plan do
+ exec_queries
+ end
+ end
+
+ def exec_queries
return @records if loaded?
default_scoped = with_default_scope
if default_scoped.equal?(self)
- @records = if @readonly_value.nil? && !@klass.locking_enabled?
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
- else
- IdentityMap.without do
- eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values)
- end
- end
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
- preload = @preload_values
- preload += @includes_values unless eager_loading?
+ preload = preload_values
+ preload += includes_values unless eager_loading?
preload.each do |associations|
ActiveRecord::Associations::Preloader.new(@records, associations).run
end
# @readonly_value is true only if set explicitly. @implicit_readonly is true if there
# are JOINS and no explicit SELECT.
- readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
+ readonly = readonly_value.nil? ? @implicit_readonly : readonly_value
@records.each { |record| record.readonly! } if readonly
else
@records = default_scoped.to_a
@@ -174,6 +190,7 @@ module ActiveRecord
@loaded = true
@records
end
+ private :exec_queries
def as_json(options = nil) #:nodoc:
to_a.as_json(options)
@@ -204,7 +221,7 @@ module ActiveRecord
if block_given?
to_a.many? { |*block_args| yield(*block_args) }
else
- @limit_value ? to_a.many? : size > 1
+ limit_value ? to_a.many? : size > 1
end
end
@@ -219,7 +236,10 @@ 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.send(:with_scope, self, :overwrite) { yield }
+ previous, klass.current_scope = klass.current_scope, self
+ yield
+ ensure
+ klass.current_scope = previous
end
# Updates all records with details given if they match a set of conditions supplied, limits and order can
@@ -240,40 +260,26 @@ module ActiveRecord
# Customer.update_all :wants_email => true
#
# # Update all books with 'Rails' in their title
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'"
- #
- # # Update all avatars migrated more than a week ago
- # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
- #
- # # Update all books that match conditions, but limit it to 5 ordered by date
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
- #
- # # Conditions from the current relation also works
# Book.where('title LIKE ?', '%Rails%').update_all(:author => 'David')
#
- # # The same idea applies to limit and order
+ # # Update all books that match conditions, but limit it to 5 ordered by date
# 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
- stmt = Arel::UpdateManager.new(arel.engine)
-
- stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
- stmt.table(table)
- stmt.key = table[primary_key]
+ def update_all(updates)
+ stmt = Arel::UpdateManager.new(arel.engine)
- if joins_values.any?
- @klass.connection.join_to_update(stmt, arel)
- else
- stmt.take(arel.limit)
- stmt.order(*arel.orders)
- stmt.wheres = arel.constraints
- end
+ stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))
+ stmt.table(table)
+ stmt.key = table[primary_key]
- @klass.connection.update stmt, 'SQL', bind_values
+ if joins_values.any?
+ @klass.connection.join_to_update(stmt, arel)
+ else
+ stmt.take(arel.limit)
+ stmt.order(*arel.orders)
+ stmt.wheres = arel.constraints
end
+
+ @klass.connection.update stmt, 'SQL', bind_values
end
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -337,7 +343,7 @@ module ActiveRecord
end
end
- # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
+ # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
# therefore all callbacks and filters are fired off before the object is deleted. This method is
# less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
#
@@ -364,17 +370,12 @@ module ActiveRecord
end
end
- # Deletes the records matching +conditions+ without instantiating the records first, and hence not
- # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
- # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
- # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
- # the number of rows affected.
- #
- # ==== Parameters
- #
- # * +conditions+ - Conditions are specified the same way as with +find+ method.
- #
- # ==== Example
+ # Deletes the records matching +conditions+ without instantiating the records
+ # first, and hence not calling the +destroy+ method nor invoking callbacks. This
+ # is a single SQL DELETE statement that goes straight to the database, much more
+ # efficient than +destroy_all+. Be careful with relations though, in particular
+ # <tt>:dependent</tt> rules defined on associations are not honored. Returns the
+ # number of rows affected.
#
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
@@ -383,13 +384,27 @@ module ActiveRecord
# Both calls delete the affected posts all at once with a single DELETE statement.
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
# +after_destroy+ callbacks, use the +destroy_all+ method instead.
+ #
+ # If a limit scope is supplied, +delete_all+ raises an ActiveRecord error:
+ #
+ # Post.limit(100).delete_all
+ # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit scope
def delete_all(conditions = nil)
- IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
+ raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
+
if conditions
where(conditions).delete_all
else
- statement = arel.compile_delete
- affected = @klass.connection.delete(statement, 'SQL', bind_values)
+ stmt = Arel::DeleteManager.new(arel.engine)
+ stmt.from(table)
+
+ if joins_values.any?
+ @klass.connection.join_to_delete(stmt, arel, table[primary_key])
+ else
+ stmt.wheres = arel.constraints
+ end
+
+ affected = @klass.connection.delete(stmt, 'SQL', bind_values)
reset
affected
@@ -417,7 +432,6 @@ 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
@@ -435,7 +449,7 @@ module ActiveRecord
end
def to_sql
- @to_sql ||= klass.connection.to_sql(arel)
+ @to_sql ||= klass.connection.to_sql(arel, bind_values.dup)
end
def where_values_hash
@@ -443,7 +457,12 @@ module ActiveRecord
node.left.relation.name == table_name
}
- Hash[equalities.map { |where| [where.left.name, where.right] }]
+ binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
+
+ Hash[equalities.map { |where|
+ name = where.left.name
+ [name, binds.fetch(name.to_s) { where.right }]
+ }]
end
def scope_for_create
@@ -452,8 +471,8 @@ module ActiveRecord
def eager_loading?
@should_eager_load ||=
- @eager_load_values.any? ||
- @includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
+ eager_load_values.any? ||
+ includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?)
end
# Joins that are also marked for preloading. In which case we should just eager load them.
@@ -461,7 +480,7 @@ module ActiveRecord
# represent the same association, but that aren't matched by this. Also, we could have
# nested hashes which partially match, e.g. { :a => :b } & { :a => [:b, :c] }
def joined_includes_values
- @includes_values & @joins_values
+ includes_values & joins_values
end
def ==(other)
@@ -477,6 +496,10 @@ module ActiveRecord
to_a.inspect
end
+ def pretty_print(q)
+ q.pp(self.to_a)
+ end
+
def with_default_scope #:nodoc:
if default_scoped? && default_scope = klass.send(:build_default_scope)
default_scope = default_scope.merge(self)
@@ -487,18 +510,12 @@ module ActiveRecord
end
end
- protected
+ def blank?
+ to_a.blank?
+ end
- def method_missing(method, *args, &block)
- if Array.method_defined?(method)
- to_a.send(method, *args, &block)
- elsif @klass.respond_to?(method)
- scoping { @klass.send(method, *args, &block) }
- elsif arel.respond_to?(method)
- arel.send(method, *args, &block)
- else
- super
- end
+ def values
+ @values.dup
end
private
@@ -516,8 +533,30 @@ module ActiveRecord
# always convert table names to downcase as in Oracle quoted table names are in uppercase
joined_tables = joined_tables.flatten.compact.map { |t| t.downcase }.uniq
-
- (tables_in_string(to_sql) - joined_tables).any?
+ string_tables = tables_in_string(to_sql)
+
+ if (references_values - joined_tables).any?
+ true
+ elsif (string_tables - joined_tables).any?
+ ActiveSupport::Deprecation.warn(
+ "It looks like you are eager loading table(s) (one of: #{string_tables.join(', ')}) " \
+ "that are referenced in a string SQL snippet. For example: \n" \
+ "\n" \
+ " Post.includes(:comments).where(\"comments.title = 'foo'\")\n" \
+ "\n" \
+ "Currently, Active Record recognises the table in the string, and knows to JOIN the " \
+ "comments table to the query, rather than loading comments in a separate query. " \
+ "However, doing this without writing a full-blown SQL parser is inherently flawed. " \
+ "Since we don't want to write an SQL parser, we are removing this functionality. " \
+ "From now on, you must explicitly tell Active Record when you are referencing a table " \
+ "from a string:\n" \
+ "\n" \
+ " Post.includes(:comments).where(\"comments.title = 'foo'\").references(:comments)\n\n"
+ )
+ true
+ else
+ false
+ end
end
def tables_in_string(string)
@@ -526,6 +565,5 @@ module ActiveRecord
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
end
-
end
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 2fd89882ff..15f838a5ab 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -46,19 +46,14 @@ module ActiveRecord
# group.each { |person| person.party_all_night! }
# end
def find_in_batches(options = {})
+ options.assert_valid_keys(:start, :batch_size)
+
relation = self
unless arel.orders.blank? && arel.taken.blank?
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
- if (finder_options = options.except(:start, :batch_size)).present?
- raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
- raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
-
- relation = apply_finder_options(finder_options)
- end
-
start = options.delete(:start).to_i
batch_size = options.delete(:batch_size) || 1000
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index af86771d2d..31d99f0192 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -3,56 +3,19 @@ require 'active_support/core_ext/object/try'
module ActiveRecord
module Calculations
- # Count operates using three different approaches.
+ # Count the records.
#
- # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
- # * Count using column: By passing a column name to count, it will return a count of all the
- # rows for the model with supplied column present.
- # * Count using options will find the row count matched by the options used.
+ # Person.count
+ # # => the total count of all people
#
- # The third approach, count using options, accepts an option hash as the only parameter. The options are:
+ # Person.count(:age)
+ # # => returns the total count of all people whose age is present in database
#
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
- # See conditions in the intro to ActiveRecord::Base.
- # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id"
- # (rarely needed) or named associations in the same form used for the <tt>:include</tt> option, which will
- # perform an INNER JOIN on the associated table(s). If the value is a string, then the records
- # will be returned read-only since they will have attributes that do not correspond to the table's columns.
- # Pass <tt>:readonly => false</tt> to override.
- # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs.
- # The symbols named refer to already defined associations. When using named associations, count
- # returns the number of DISTINCT items for the model you're counting.
- # See eager loading under Associations.
- # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
- # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example,
- # want to do a join but not include the joined columns.
- # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as
- # SELECT COUNT(DISTINCT posts.id) ...
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an
- # alternate table name (or even the name of a database view).
+ # Person.count(:all)
+ # # => performs a COUNT(*) (:all is an alias for '*')
#
- # Examples for counting all:
- # Person.count # returns the total count of all people
- #
- # Examples for counting by column:
- # Person.count(:age) # returns the total count of all people whose age is present in database
- #
- # Examples for count with options:
- # Person.count(:conditions => "age > 26")
- #
- # # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
- # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job)
- #
- # # finds the number of rows matching the conditions and joins.
- # Person.count(:conditions => "age > 26 AND job.salary > 60000",
- # :joins => "LEFT JOIN jobs on jobs.person_id = person.id")
- #
- # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
- # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
- #
- # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
- # Use Person.count instead.
+ # Person.count(:age, distinct: true)
+ # # => counts the number of different age values
def count(column_name = nil, options = {})
column_name, options = nil, column_name if column_name.is_a?(Hash)
calculate(:count, column_name, options)
@@ -98,21 +61,22 @@ module ActiveRecord
end
# This calculates aggregate values in the given column. Methods for count, sum, average,
- # minimum, and maximum have been added as shortcuts. Options such as <tt>:conditions</tt>,
- # <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
+ # minimum, and maximum have been added as shortcuts.
#
# There are two basic forms of output:
+ #
# * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
# for AVG, and the given column's type for everything else.
- # * Grouped values: This returns an ordered hash of the values and groups them by the
- # <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
#
- # values = Person.maximum(:age, :group => 'last_name')
+ # * Grouped values: This returns an ordered hash of the values and groups them. It
+ # takes either a column name, or the name of a belongs_to association.
+ #
+ # values = Person.group('last_name').maximum(:age)
# puts values["Drake"]
# => 43
#
# drake = Family.find_by_last_name('Drake')
- # values = Person.maximum(:age, :group => :family) # Person belongs_to :family
+ # values = Person.group(:family).maximum(:age) # Person belongs_to :family
# puts values[drake]
# => 43
#
@@ -120,52 +84,94 @@ module ActiveRecord
# ...
# end
#
- # Options:
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
- # See conditions in the intro to ActiveRecord::Base.
- # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
- # the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
- # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
- # (Rarely needed).
- # The records will be returned read-only since they will have attributes that do not correspond to the
- # table's columns.
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example
- # want to do a join, but not include the joined columns.
- # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as
- # SELECT COUNT(DISTINCT posts.id) ...
- #
# Examples:
# Person.calculate(:count, :all) # The same as Person.count
# Person.average(:age) # SELECT AVG(age) FROM people...
- # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for
- # # everyone with a last name other than 'Drake'
#
# # Selects the minimum age for any family without any minors
- # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name)
+ # Person.group(:last_name).having("min(age) > 17").minimum(:age)
#
# Person.sum("2 * age")
def calculate(operation, column_name, options = {})
- if options.except(:distinct).present?
- apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct])
- else
- relation = with_default_scope
-
- if relation.equal?(self)
- if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
- construct_relation_for_association_calculations.calculate(operation, column_name, options)
- else
- perform_calculation(operation, column_name, options)
- end
+ relation = with_default_scope
+
+ if relation.equal?(self)
+ if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
+ construct_relation_for_association_calculations.calculate(operation, column_name, options)
else
- relation.calculate(operation, column_name, options)
+ perform_calculation(operation, column_name, options)
end
+ else
+ relation.calculate(operation, column_name, options)
end
rescue ThrowResult
0
end
+ # Use <tt>pluck</tt> as a shortcut to select a single attribute without
+ # loading a bunch of records just to grab one attribute you want.
+ #
+ # Person.pluck(:name)
+ #
+ # instead of
+ #
+ # Person.all.map(&:name)
+ #
+ # Pluck returns an <tt>Array</tt> of attribute values type-casted to match
+ # the plucked column name, if it can be deduced. Plucking a SQL fragment
+ # returns String values by default.
+ #
+ # Examples:
+ #
+ # Person.pluck(:id)
+ # # SELECT people.id FROM people
+ # # => [1, 2, 3]
+ #
+ # Person.uniq.pluck(:role)
+ # # SELECT DISTINCT role FROM people
+ # # => ['admin', 'member', 'guest']
+ #
+ # Person.where(:age => 21).limit(5).pluck(:id)
+ # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
+ # # => [2, 3]
+ #
+ # Person.pluck('DATEDIFF(updated_at, created_at)')
+ # # SELECT DATEDIFF(updated_at, created_at) FROM people
+ # # => ['0', '27761', '173']
+ #
+ def pluck(column_name)
+ if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
+ column_name = "#{table_name}.#{column_name}"
+ end
+
+ result = klass.connection.select_all(select(column_name).arel, nil, bind_values)
+
+ key = result.columns.first
+ column = klass.column_types.fetch(key) {
+ result.column_types.fetch(key) {
+ Class.new { def type_cast(v); v; end }.new
+ }
+ }
+
+ result.map do |attributes|
+ raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one?
+
+ value = klass.initialize_attributes(attributes).values.first
+
+ column.type_cast(value)
+ end
+ end
+
+ # Pluck all the ID's for the relation using the table's primary key
+ #
+ # Examples:
+ #
+ # Person.ids # SELECT people.id FROM people
+ # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
+ def ids
+ pluck primary_key
+ end
+
private
def perform_calculation(operation, column_name, options = {})
@@ -185,7 +191,7 @@ module ActiveRecord
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
end
- if @group_values.any?
+ if group_values.any?
execute_grouped_calculation(operation, column_name, distinct)
else
execute_simple_calculation(operation, column_name, distinct)
@@ -223,11 +229,12 @@ module ActiveRecord
query_builder = relation.arel
end
- type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation)
+ result = @klass.connection.select_value(query_builder, nil, relation.bind_values)
+ type_cast_calculated_value(result, column_for(column_name), operation)
end
def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
- group_attr = @group_values
+ group_attr = group_values
association = @klass.reflect_on_association(group_attr.first.to_sym)
associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
group_fields = Array(associated ? association.foreign_key : group_attr)
@@ -250,7 +257,7 @@ module ActiveRecord
operation,
distinct).as(aggregate_alias)
]
- select_values += @select_values unless @having_values.empty?
+ select_values += select_values unless having_values.empty?
select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
"#{field} AS #{aliaz}"
@@ -259,7 +266,7 @@ module ActiveRecord
relation = except(:group).group(group.join(','))
relation.select_values = select_values
- calculated_data = @klass.connection.select_all(relation)
+ calculated_data = @klass.connection.select_all(relation, nil, bind_values)
if association
key_ids = calculated_data.collect { |row| row[group_aliases.first] }
@@ -267,7 +274,7 @@ module ActiveRecord
key_records = Hash[key_records.map { |r| [r.id, r] }]
end
- ActiveSupport::OrderedHash[calculated_data.map do |row|
+ Hash[calculated_data.map do |row|
key = group_columns.map { |aliaz, column|
type_cast_calculated_value(row[aliaz], column)
}
@@ -315,9 +322,9 @@ module ActiveRecord
end
def select_for_count
- if @select_values.present?
- select = @select_values.join(", ")
- select if select !~ /(,|\*)/
+ if select_values.present?
+ select = select_values.join(", ")
+ select if select !~ /[,*]/
end
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
new file mode 100644
index 0000000000..f5fdf437bf
--- /dev/null
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -0,0 +1,49 @@
+require 'active_support/core_ext/module/delegation'
+
+module ActiveRecord
+ module Delegation
+ # Set up common delegations for performance (avoids method_missing)
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :to => :to_a
+ delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
+ :connection, :columns_hash, :auto_explain_threshold_in_seconds, :to => :klass
+
+ def self.delegate_to_scoped_klass(method)
+ if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(*args, &block)
+ scoping { @klass.#{method}(*args, &block) }
+ end
+ RUBY
+ else
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{method}(*args, &block)
+ scoping { @klass.send(#{method.inspect}, *args, &block) }
+ end
+ RUBY
+ end
+ end
+
+ def respond_to?(method, include_private = false)
+ super || Array.method_defined?(method) ||
+ @klass.respond_to?(method, include_private) ||
+ arel.respond_to?(method, include_private)
+ end
+
+ protected
+
+ def method_missing(method, *args, &block)
+ if Array.method_defined?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :to_a
+ to_a.send(method, *args, &block)
+ elsif @klass.respond_to?(method)
+ ::ActiveRecord::Delegation.delegate_to_scoped_klass(method)
+ scoping { @klass.send(method, *args, &block) }
+ elsif arel.respond_to?(method)
+ ::ActiveRecord::Delegation.delegate method, :to => :arel
+ arel.send(method, *args, &block)
+ else
+ super
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7eeb3dde70..4fedd33d64 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -3,83 +3,24 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActiveRecord
module FinderMethods
- # Find operates with four different retrieval approaches:
- #
- # * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
- # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
- # * Find first - This will return the first record matched by the options used. These options can either be specific
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
- # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
- # * Find last - This will return the last record matched by the options used. These options can either be specific
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
- # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
- # * Find all - This will return all the records matched by the options used.
- # If no records are found, an empty array is returned. Use
- # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
- #
- # All approaches accept an options hash as their last parameter.
- #
- # ==== Options
- #
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>["user_name = ?", username]</tt>,
- # or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
- # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a
- # <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
- # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
- # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5,
- # it would skip rows 0 through 4.
- # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
- # named associations in the same form used for the <tt>:include</tt> option, which will perform an
- # <tt>INNER JOIN</tt> on the associated table(s),
- # or an array containing a mixture of both strings and named associations.
- # If the value is a string, then the records will be returned read-only since they will
- # have attributes that do not correspond to the table's columns.
- # Pass <tt>:readonly => false</tt> to override.
- # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
- # to already defined associations. See eager loading under Associations.
- # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you,
- # for example, want to do a join but not include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed
- # to an alternate table name (or even the name of a database view).
- # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
- # * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
- # <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
+ # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised. If the primary key
+ # is an integer, find by id coerces its arguments using +to_i+.
#
# ==== Examples
#
- # # find by id
# Person.find(1) # returns the object for ID = 1
+ # Person.find("1") # returns the object for ID = 1
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
# Person.find([1]) # returns an array for the object with ID = 1
# Person.where("administrator = 1").order("created_on DESC").find(1)
#
# Note that returned records may not be in the same order as the ids you
- # provide since database rows are unordered. Give an explicit <tt>:order</tt>
+ # provide since database rows are unordered. Give an explicit <tt>order</tt>
# to ensure the results are sorted.
#
- # ==== Examples
- #
- # # find first
- # Person.first # returns the first object fetched by SELECT * FROM people
- # Person.where(["user_name = ?", user_name]).first
- # Person.where(["user_name = :u", { :u => user_name }]).first
- # Person.order("created_on DESC").offset(5).first
- #
- # # find last
- # Person.last # returns the last object fetched by SELECT * FROM people
- # Person.where(["user_name = ?", user_name]).last
- # Person.order("created_on DESC").offset(5).last
- #
- # # find all
- # Person.all # returns an array of objects for all the rows fetched by SELECT * FROM people
- # Person.where(["category IN (?)", categories]).limit(50).all
- # Person.where({ :friends => ["Bob", "Steve", "Fred"] }).all
- # Person.offset(10).limit(10).all
- # Person.includes([:account, :friends]).all
- # Person.group("category").all
+ # ==== Find with lock
#
# Example for find with a lock: Imagine two concurrent transactions:
# each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
@@ -93,30 +34,66 @@ module ActiveRecord
# person.save!
# end
def find(*args)
- return to_a.find { |*block_args| yield(*block_args) } if block_given?
-
- options = args.extract_options!
-
- if options.present?
- apply_finder_options(options).find(*args)
+ if block_given?
+ to_a.find { |*block_args| yield(*block_args) }
else
- case args.first
- when :first, :last, :all
- send(args.first)
- else
- find_with_ids(*args)
- end
+ find_with_ids(*args)
end
end
- # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
- # same arguments to this method as you can to <tt>find(:first)</tt>.
- def first(*args)
- if args.any?
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
- limit(*args).to_a
+ # Finds the first record matching the specified conditions. There
+ # is no implied ording so if order matters, you should specify it
+ # yourself.
+ #
+ # If no record is found, returns <tt>nil</tt>.
+ #
+ # Post.find_by name: 'Spartacus', rating: 4
+ # Post.find_by "published_at < ?", 2.weeks.ago
+ #
+ def find_by(*args)
+ where(*args).take
+ end
+
+ # Like <tt>find_by</tt>, except that if no record is found, raises
+ # an <tt>ActiveRecord::RecordNotFound</tt> error.
+ def find_by!(*args)
+ where(*args).take!
+ end
+
+ # Gives a record (or N records if a parameter is supplied) without any implied
+ # order. The order will depend on the database implementation.
+ # If an order is supplied it will be respected.
+ #
+ # Examples:
+ #
+ # Person.take # returns an object fetched by SELECT * FROM people
+ # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
+ # Person.where(["name LIKE '%?'", name]).take
+ def take(limit = nil)
+ limit ? limit(limit).to_a : find_take
+ end
+
+ # Same as +take+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>take!</tt> accepts no arguments.
+ def take!
+ take or raise RecordNotFound
+ end
+
+ # Find the first record (or first N records if a parameter is supplied).
+ # If no order is defined it will order by primary key.
+ #
+ # Examples:
+ #
+ # Person.first # returns the first object fetched by SELECT * FROM people
+ # Person.where(["user_name = ?", user_name]).first
+ # Person.where(["user_name = :u", { :u => user_name }]).first
+ # Person.order("created_on DESC").offset(5).first
+ def first(limit = nil)
+ if limit
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc).limit(limit).to_a
else
- apply_finder_options(args.first).first
+ limit(limit).to_a
end
else
find_first
@@ -129,18 +106,20 @@ module ActiveRecord
first or raise RecordNotFound
end
- # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
- # same arguments to this method as you can to <tt>find(:last)</tt>.
- def last(*args)
- if args.any?
- if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
- if order_values.empty? && reorder_value.nil?
- order("#{primary_key} DESC").limit(*args).reverse
- else
- to_a.last(*args)
- end
+ # Find the last record (or last N records if a parameter is supplied).
+ # If no order is defined it will order by primary key.
+ #
+ # Examples:
+ #
+ # Person.last # returns the last object fetched by SELECT * FROM people
+ # Person.where(["user_name = ?", user_name]).last
+ # Person.order("created_on DESC").offset(5).last
+ def last(limit = nil)
+ if limit
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].desc).limit(limit).reverse
else
- apply_finder_options(args.first).last
+ to_a.last(limit)
end
else
find_last
@@ -153,10 +132,16 @@ module ActiveRecord
last or raise RecordNotFound
end
- # A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
- # same arguments to this method as you can to <tt>find(:all)</tt>.
- def all(*args)
- args.any? ? apply_finder_options(args.first).to_a : to_a
+ # Examples:
+ #
+ # Person.all # returns an array of objects for all the rows fetched by SELECT * FROM people
+ # Person.where(["category IN (?)", categories]).limit(50).all
+ # Person.where({ :friends => ["Bob", "Steve", "Fred"] }).all
+ # Person.offset(10).limit(10).all
+ # Person.includes([:account, :friends]).all
+ # Person.group("category").all
+ def all
+ to_a
end
# Returns true if a record exists in the table that matches the +id+ or
@@ -185,13 +170,12 @@ module ActiveRecord
# Person.exists?(['name LIKE ?', "%#{query}%"])
# Person.exists?
def exists?(id = false)
+ id = id.id if ActiveRecord::Model === id
return false if id.nil?
- id = id.id if ActiveRecord::Base === id
-
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
- relation = relation.except(:select).select("1").limit(1)
+ relation = relation.except(:select, :order).select("1").limit(1)
case id
when Array, Hash
@@ -200,7 +184,7 @@ module ActiveRecord
relation = relation.where(table[primary_key].eq(id)) if id
end
- connection.select_value(relation, "#{name} Exists") ? true : false
+ connection.select_value(relation, "#{name} Exists", relation.bind_values)
end
protected
@@ -208,19 +192,19 @@ module ActiveRecord
def find_with_associations
join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
- rows = connection.select_all(relation, 'SQL', relation.bind_values)
+ rows = connection.select_all(relation, 'SQL', relation.bind_values.dup)
join_dependency.instantiate(rows)
rescue ThrowResult
[]
end
def construct_join_dependency_for_association_find
- including = (@eager_load_values + @includes_values).uniq
+ including = (eager_load_values + includes_values).uniq
ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
end
def construct_relation_for_association_calculations
- including = (@eager_load_values + @includes_values).uniq
+ including = (eager_load_values + includes_values).uniq
join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
relation = except(:includes, :eager_load, :preload)
apply_join_dependency(relation, join_dependency)
@@ -258,44 +242,6 @@ module ActiveRecord
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
- def find_by_attributes(match, attributes, *args)
- conditions = Hash[attributes.map {|a| [a, args[attributes.index(a)]]}]
- result = where(conditions).send(match.finder)
-
- if match.bang? && result.blank?
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{conditions.to_a.collect {|p| p.join(' = ')}.join(', ')}"
- else
- yield(result) if block_given?
- result
- end
- end
-
- def find_or_instantiator_by_attributes(match, attributes, *args)
- options = args.size > 1 && args.last(2).all?{ |a| a.is_a?(Hash) } ? args.extract_options! : {}
- protected_attributes_for_create, unprotected_attributes_for_create = {}, {}
- args.each_with_index do |arg, i|
- if arg.is_a?(Hash)
- protected_attributes_for_create = args[i].with_indifferent_access
- else
- unprotected_attributes_for_create[attributes[i]] = args[i]
- end
- end
-
- conditions = (protected_attributes_for_create.merge(unprotected_attributes_for_create)).slice(*attributes).symbolize_keys
-
- record = where(conditions).first
-
- unless record
- record = @klass.new(protected_attributes_for_create, options) do |r|
- r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
- end
- yield(record) if block_given?
- record.save if match.instantiator == :create
- end
-
- record
- end
-
def find_with_ids(*ids)
return to_a.find { |*block_args| yield(*block_args) } if block_given?
@@ -318,21 +264,11 @@ 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_at(column, @bind_values.length)
+ substitute = connection.substitute_at(column, bind_values.length)
relation = where(table[primary_key].eq(substitute))
- relation.bind_values = [[column, id]]
- record = relation.first
+ relation.bind_values += [[column, id]]
+ record = relation.take
unless record
conditions = arel.where_sql
@@ -347,15 +283,15 @@ module ActiveRecord
result = where(table[primary_key].in(ids)).all
expected_size =
- if @limit_value && ids.size > @limit_value
- @limit_value
+ if limit_value && ids.size > limit_value
+ limit_value
else
ids.size
end
# 11 ids with limit 3, offset 9 should give 2 results.
- if @offset_value && (ids.size - @offset_value < expected_size)
- expected_size = ids.size - @offset_value
+ if offset_value && (ids.size - offset_value < expected_size)
+ expected_size = ids.size - offset_value
end
if result.size == expected_size
@@ -370,11 +306,24 @@ module ActiveRecord
end
end
+ def find_take
+ if loaded?
+ @records.first
+ else
+ @take ||= limit(1).to_a.first
+ end
+ end
+
def find_first
if loaded?
@records.first
else
- @first ||= limit(1).to_a[0]
+ @first ||=
+ if order_values.empty? && primary_key
+ order(arel_table[primary_key].asc).limit(1).to_a.first
+ else
+ limit(1).to_a.first
+ end
end
end
@@ -386,7 +335,7 @@ module ActiveRecord
if offset_value || limit_value
to_a.last
else
- reverse_order.limit(1).to_a[0]
+ reverse_order.limit(1).to_a.first
end
end
end
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
new file mode 100644
index 0000000000..36f98c6480
--- /dev/null
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -0,0 +1,122 @@
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
+
+module ActiveRecord
+ class Relation
+ class HashMerger
+ attr_reader :relation, :hash
+
+ def initialize(relation, hash)
+ hash.assert_valid_keys(*Relation::VALUE_METHODS)
+
+ @relation = relation
+ @hash = hash
+ end
+
+ def merge
+ Merger.new(relation, other).merge
+ end
+
+ # Applying values to a relation has some side effects. E.g.
+ # interpolation might take place for where values. So we should
+ # build a relation to merge in rather than directly merging
+ # the values.
+ def other
+ other = Relation.new(relation.klass, relation.table)
+ hash.each { |k, v| other.send("#{k}!", v) }
+ other
+ end
+ end
+
+ class Merger
+ attr_reader :relation, :values
+
+ def initialize(relation, other)
+ if other.default_scoped? && other.klass != relation.klass
+ other = other.with_default_scope
+ end
+
+ @relation = relation
+ @values = other.values
+ end
+
+ def normal_values
+ Relation::SINGLE_VALUE_METHODS +
+ Relation::MULTI_VALUE_METHODS -
+ [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from]
+ end
+
+ def merge
+ normal_values.each do |name|
+ value = values[name]
+ relation.send("#{name}!", value) unless value.blank?
+ end
+
+ merge_multi_values
+ merge_single_values
+
+ relation
+ end
+
+ private
+
+ def merge_multi_values
+ relation.where_values = merged_wheres
+ relation.bind_values = merged_binds
+
+ if values[:reordering]
+ # override any order specified in the original relation
+ relation.reorder! values[:order]
+ elsif values[:order]
+ # merge in order_values from r
+ relation.order! values[:order]
+ end
+
+ relation.extend(*values[:extending]) unless values[:extending].blank?
+ end
+
+ def merge_single_values
+ relation.from_value = values[:from] unless relation.from_value
+ relation.lock_value = values[:lock] unless relation.lock_value
+ relation.reverse_order_value = values[:reverse_order]
+
+ unless values[:create_with].blank?
+ relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
+ end
+ end
+
+ def merged_binds
+ if values[:bind]
+ (relation.bind_values + values[:bind]).uniq(&:first)
+ else
+ relation.bind_values
+ end
+ end
+
+ def merged_wheres
+ if values[:where]
+ merged_wheres = relation.where_values + values[:where]
+
+ unless relation.where_values.empty?
+ # Remove duplicates, last one wins.
+ seen = Hash.new { |h,table| h[table] = {} }
+ merged_wheres = merged_wheres.reverse.reject { |w|
+ nuke = false
+ if w.respond_to?(:operator) && w.operator == :==
+ name = w.left.name
+ table = w.left.relation.name
+ nuke = seen[table][name]
+ seen[table][name] = true
+ end
+ nuke
+ }.reverse
+ end
+
+ merged_wheres
+ else
+ relation.where_values
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 7e8ddd1b5d..6a0cdd5917 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,7 +1,7 @@
module ActiveRecord
class PredicateBuilder # :nodoc:
def self.build_from_hash(engine, attributes, default_table)
- predicates = attributes.map do |column, value|
+ attributes.map do |column, value|
table = default_table
if value.is_a?(Hash)
@@ -15,42 +15,60 @@ module ActiveRecord
table = Arel::Table.new(table_name, engine)
end
- attribute = table[column.to_sym]
-
- case value
- when ActiveRecord::Relation
- value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
- attribute.in(value.arel.ast)
- when Array, ActiveRecord::Associations::CollectionProxy
- values = value.to_a.map { |x|
- x.is_a?(ActiveRecord::Base) ? x.id : x
- }
-
- if values.include?(nil)
- values = values.compact
- if values.empty?
- attribute.eq nil
- else
- attribute.in(values.compact).or attribute.eq(nil)
- end
+ build(table[column.to_sym], value)
+ end
+ end.flatten
+ end
+
+ def self.references(attributes)
+ attributes.map do |key, value|
+ if value.is_a?(Hash)
+ key
+ else
+ key = key.to_s
+ key.split('.').first.to_sym if key.include?('.')
+ end
+ end.compact
+ end
+
+ private
+ def self.build(attribute, value)
+ case value
+ when Array, ActiveRecord::Associations::CollectionProxy
+ values = value.to_a.map {|x| x.is_a?(ActiveRecord::Model) ? x.id : x}
+ ranges, values = values.partition {|v| v.is_a?(Range)}
+
+ values_predicate = if values.include?(nil)
+ values = values.compact
+
+ case values.length
+ when 0
+ attribute.eq(nil)
+ when 1
+ attribute.eq(values.first).or(attribute.eq(nil))
else
- attribute.in(values)
+ attribute.in(values).or(attribute.eq(nil))
end
-
- when Range, Arel::Relation
- attribute.in(value)
- when ActiveRecord::Base
- attribute.eq(value.id)
- when Class
- # FIXME: I think we need to deprecate this behavior
- attribute.eq(value.name)
else
- attribute.eq(value)
+ attribute.in(values)
end
+
+ array_predicates = ranges.map { |range| attribute.in(range) }
+ array_predicates << values_predicate
+ array_predicates.inject { |composite, predicate| composite.or(predicate) }
+ when ActiveRecord::Relation
+ value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
+ attribute.in(value.arel.ast)
+ when Range
+ attribute.in(value)
+ when ActiveRecord::Model
+ attribute.eq(value.id)
+ when Class
+ # FIXME: I think we need to deprecate this behavior
+ attribute.eq(value.name)
+ else
+ attribute.eq(value)
end
end
-
- predicates.flatten
- end
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 670ba0987d..19fe8155d9 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -5,40 +5,90 @@ module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern
- 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, :reorder_value, :reverse_order_value
+ Relation::MULTI_VALUE_METHODS.each do |name|
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}_values # def select_values
+ @values[:#{name}] || [] # @values[:select] || []
+ end # end
+ #
+ def #{name}_values=(values) # def select_values=(values)
+ @values[:#{name}] = values # @values[:select] = values
+ end # end
+ CODE
+ end
+
+ (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}_value # def readonly_value
+ @values[:#{name}] # @values[:readonly]
+ end # end
+ #
+ def #{name}_value=(value) # def readonly_value=(value)
+ @values[:#{name}] = value # @values[:readonly] = value
+ end # end
+ CODE
+ end
+
+ def create_with_value
+ @values[:create_with] || {}
+ end
+
+ def create_with_value=(value)
+ @values[:create_with] = value
+ end
+
+ alias extensions extending_values
def includes(*args)
- args.reject! {|a| a.blank? }
+ args.empty? ? self : spawn.includes!(*args)
+ end
- return self if args.empty?
+ def includes!(*args)
+ args.reject! {|a| a.blank? }
- relation = clone
- relation.includes_values = (relation.includes_values + args).flatten.uniq
- relation
+ self.includes_values = (includes_values + args).flatten.uniq
+ self
end
def eager_load(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.eager_load!(*args)
+ end
- relation = clone
- relation.eager_load_values += args
- relation
+ def eager_load!(*args)
+ self.eager_load_values += args
+ self
end
def preload(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.preload!(*args)
+ end
+
+ def preload!(*args)
+ self.preload_values += args
+ self
+ end
- relation = clone
- relation.preload_values += args
- relation
+ # Used to indicate that an association is referenced by an SQL string, and should
+ # therefore be JOINed in any query rather than loaded separately.
+ #
+ # For example:
+ #
+ # User.includes(:posts).where("posts.name = 'foo'")
+ # # => Doesn't JOIN the posts table, resulting in an error.
+ #
+ # User.includes(:posts).where("posts.name = 'foo'").references(:posts)
+ # # => Query now knows the string references posts, so adds a JOIN
+ def references(*args)
+ args.blank? ? self : spawn.references!(*args)
+ end
+
+ def references!(*args)
+ self.references_values = (references_values + args.flatten.map(&:to_s)).uniq
+ self
end
# Works in two unique ways.
- #
+ #
# First: takes a block so it can be used just like Array#select.
#
# Model.scoped.select { |m| m.field == value }
@@ -56,129 +106,244 @@ module ActiveRecord
# array, it actually returns a relation object and can have other query
# methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
#
- # This method will also take multiple parameters:
+ # The argument to the method can also be an array of fields.
#
- # >> Model.select(:field, :other_field, :and_one_more)
+ # >> Model.select([:field, :other_field, :and_one_more])
# => [#<Model field: "value", other_field: "value", and_one_more: "value">]
#
- # Any attributes that do not have fields retrieved by a select
- # will return `nil` when the getter method for that attribute is used:
+ # Accessing attributes of an object that do not have fields retrieved by a select
+ # will throw <tt>ActiveModel::MissingAttributeError</tt>:
#
# >> Model.select(:field).first.other_field
- # => nil
+ # => ActiveModel::MissingAttributeError: missing attribute: other_field
def select(value = Proc.new)
if block_given?
- to_a.select {|*block_args| value.call(*block_args) }
+ to_a.select { |*block_args| value.call(*block_args) }
else
- relation = clone
- relation.select_values += Array.wrap(value)
- relation
+ spawn.select!(value)
end
end
+ def select!(value)
+ self.select_values += Array.wrap(value)
+ self
+ end
+
def group(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.group!(*args)
+ end
- relation = clone
- relation.group_values += args.flatten
- relation
+ def group!(*args)
+ self.group_values += args.flatten
+ self
end
def order(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.order!(*args)
+ end
+
+ def order!(*args)
+ args = args.flatten
- relation = clone
- relation.order_values += args.flatten
- relation
+ references = args.reject { |arg| Arel::Node === arg }
+ .map { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }
+ .compact
+ references!(references) if references.any?
+
+ self.order_values += args
+ self
end
+ # Replaces any existing order defined on the relation with the specified order.
+ #
+ # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
+ #
+ # Subsequent calls to order on the same relation will be appended. For example:
+ #
+ # User.order('email DESC').reorder('id ASC').order('name ASC')
+ #
+ # generates a query with 'ORDER BY id ASC, name ASC'.
+ #
def reorder(*args)
- return self if args.blank?
+ args.blank? ? self : spawn.reorder!(*args)
+ end
- relation = clone
- relation.reorder_value = args.flatten
- relation
+ def reorder!(*args)
+ self.reordering_value = true
+ self.order_values = args.flatten
+ self
end
def joins(*args)
- return self if args.compact.blank?
-
- relation = clone
+ args.compact.blank? ? self : spawn.joins!(*args)
+ end
+ def joins!(*args)
args.flatten!
- relation.joins_values += args
- relation
+ self.joins_values += args
+ self
end
def bind(value)
- relation = clone
- relation.bind_values += [value]
- relation
+ spawn.bind!(value)
+ end
+
+ def bind!(value)
+ self.bind_values += [value]
+ self
end
def where(opts, *rest)
- return self if opts.blank?
+ opts.blank? ? self : spawn.where!(opts, *rest)
+ end
- relation = clone
- relation.where_values += build_where(opts, rest)
- relation
+ def where!(opts, *rest)
+ references!(PredicateBuilder.references(opts)) if Hash === opts
+
+ self.where_values += build_where(opts, rest)
+ self
end
def having(opts, *rest)
- return self if opts.blank?
+ opts.blank? ? self : spawn.having!(opts, *rest)
+ end
+
+ def having!(opts, *rest)
+ references!(PredicateBuilder.references(opts)) if Hash === opts
- relation = clone
- relation.having_values += build_where(opts, rest)
- relation
+ self.having_values += build_where(opts, rest)
+ self
end
def limit(value)
- relation = clone
- relation.limit_value = value
- relation
+ spawn.limit!(value)
+ end
+
+ def limit!(value)
+ self.limit_value = value
+ self
end
def offset(value)
- relation = clone
- relation.offset_value = value
- relation
+ spawn.offset!(value)
+ end
+
+ def offset!(value)
+ self.offset_value = value
+ self
end
def lock(locks = true)
- relation = clone
+ spawn.lock!(locks)
+ end
+ def lock!(locks = true)
case locks
when String, TrueClass, NilClass
- relation.lock_value = locks || true
+ self.lock_value = locks || true
else
- relation.lock_value = false
+ self.lock_value = false
end
- relation
+ self
+ end
+
+ # Returns a chainable relation with zero records, specifically an
+ # instance of the NullRelation class.
+ #
+ # The returned NullRelation inherits from Relation and implements the
+ # Null Object pattern so it is an object with defined null behavior:
+ # it always returns an empty array of records and does not query the database.
+ #
+ # Any subsequent condition chained to the returned relation will continue
+ # generating an empty relation and will not fire any query to the database.
+ #
+ # Used in cases where a method or scope could return zero records but the
+ # result needs to be chainable.
+ #
+ # For example:
+ #
+ # @posts = current_user.visible_posts.where(:name => params[:name])
+ # # => the visible_posts method is expected to return a chainable Relation
+ #
+ # def visible_posts
+ # case role
+ # when 'Country Manager'
+ # Post.where(:country => country)
+ # when 'Reviewer'
+ # Post.published
+ # when 'Bad User'
+ # Post.none # => returning [] instead breaks the previous code
+ # end
+ # end
+ #
+ def none
+ NullRelation.new(@klass, @table)
end
def readonly(value = true)
- relation = clone
- relation.readonly_value = value
- relation
+ spawn.readonly!(value)
+ end
+
+ def readonly!(value = true)
+ self.readonly_value = value
+ self
end
def create_with(value)
- relation = clone
- relation.create_with_value = value ? create_with_value.merge(value) : {}
- relation
+ spawn.create_with!(value)
+ end
+
+ def create_with!(value)
+ self.create_with_value = value ? create_with_value.merge(value) : {}
+ self
+ end
+
+ # Specifies table from which the records will be fetched. For example:
+ #
+ # Topic.select('title').from('posts')
+ # #=> SELECT title FROM posts
+ #
+ # Can accept other relation objects. For example:
+ #
+ # Topic.select('title').from(Topics.approved)
+ # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery
+ #
+ # Topics.select('a.title').from(Topics.approved, :a)
+ # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a
+ #
+ def from(value, subquery_name = nil)
+ spawn.from!(value, subquery_name)
+ end
+
+ def from!(value, subquery_name = nil)
+ self.from_value = [value, subquery_name]
+ self
+ end
+
+ # Specifies whether the records should be unique or not. For example:
+ #
+ # User.select(:name)
+ # # => Might return two records with the same name
+ #
+ # User.select(:name).uniq
+ # # => Returns 1 record per unique name
+ #
+ # User.select(:name).uniq.uniq(false)
+ # # => You can also remove the uniqueness
+ def uniq(value = true)
+ spawn.uniq!(value)
end
- def from(value)
- relation = clone
- relation.from_value = value
- relation
+ def uniq!(value = true)
+ self.uniq_value = value
+ self
end
# Used to extend a scope with additional methods, either through
- # a module or through a block provided.
- #
+ # a module or through a block provided.
+ #
# The object returned is a relation, which can be further extended.
#
# === Using a module
@@ -200,7 +365,7 @@ module ActiveRecord
#
# scope = Model.scoped.extending do
# def page(number)
- # # pagination code goes here
+ # # pagination code goes here
# end
# end
# scope.page(params[:page])
@@ -209,23 +374,33 @@ module ActiveRecord
#
# scope = Model.scoped.extending(Pagination) do
# def per_page(number)
- # # pagination code goes here
+ # # pagination code goes here
# end
# end
- def extending(*modules)
- modules << Module.new(&Proc.new) if block_given?
+ def extending(*modules, &block)
+ if modules.any? || block
+ spawn.extending!(*modules, &block)
+ else
+ self
+ end
+ end
- return self if modules.empty?
+ def extending!(*modules, &block)
+ modules << Module.new(&block) if block_given?
- relation = clone
- relation.send(:apply_modules, modules.flatten)
- relation
+ self.extending_values = modules.flatten
+ extend(*extending_values) if extending_values.any?
+
+ self
end
def reverse_order
- relation = clone
- relation.reverse_order_value = !relation.reverse_order_value
- relation
+ spawn.reverse_order!
+ end
+
+ def reverse_order!
+ self.reverse_order_value = !reverse_order_value
+ self
end
def arel
@@ -235,25 +410,26 @@ module ActiveRecord
def build_arel
arel = table.from table
- build_joins(arel, @joins_values) unless @joins_values.empty?
+ build_joins(arel, joins_values) unless joins_values.empty?
- collapse_wheres(arel, (@where_values - ['']).uniq)
+ collapse_wheres(arel, (where_values - ['']).uniq)
- arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
+ arel.having(*having_values.uniq.reject{|h| h.blank?}) unless having_values.empty?
- arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
- arel.skip(@offset_value) if @offset_value
+ arel.take(connection.sanitize_limit(limit_value)) if limit_value
+ arel.skip(offset_value.to_i) if offset_value
- arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
+ arel.group(*group_values.uniq.reject{|g| g.blank?}) unless group_values.empty?
- order = @reorder_value ? @reorder_value : @order_values
- order = reverse_sql_order(order) if @reverse_order_value
+ order = order_values
+ order = reverse_sql_order(order) if reverse_order_value
arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
- build_select(arel, @select_values.uniq)
+ build_select(arel, select_values.uniq)
- arel.from(@from_value) if @from_value
- arel.lock(@lock_value) if @lock_value
+ arel.distinct(uniq_value)
+ arel.from(build_from) if from_value
+ arel.lock(lock_value) if lock_value
arel
end
@@ -301,6 +477,17 @@ module ActiveRecord
end
end
+ def build_from
+ opts, name = from_value
+ case opts
+ when Relation
+ name ||= 'subquery'
+ opts.arel.as(name.to_s)
+ else
+ opts
+ end
+ end
+
def build_joins(manager, joins)
buckets = joins.group_by do |join|
case join
@@ -355,13 +542,6 @@ module ActiveRecord
end
end
- def apply_modules(modules)
- unless modules.empty?
- @extensions += modules
- modules.each {|extension| extend(extension) }
- end
- end
-
def reverse_sql_order(order_query)
order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index ba882beca9..80d087a9ea 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -1,66 +1,42 @@
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/slice'
+require 'active_record/relation/merger'
module ActiveRecord
module SpawnMethods
- def merge(r)
- return self unless r
- return to_a & r if r.is_a?(Array)
- 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")
-
- unless value.empty?
- if method == :includes
- merged_relation = merged_relation.includes(value)
- else
- merged_relation.send(:"#{method}_values=", value)
- end
- end
- end
-
- (Relation::MULTI_VALUE_METHODS - [:joins, :where]).each do |method|
- value = r.send(:"#{method}_values")
- merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present?
- end
-
- merged_relation.joins_values += r.joins_values
-
- merged_wheres = @where_values + r.where_values
-
- unless @where_values.empty?
- # Remove duplicates, last one wins.
- seen = Hash.new { |h,table| h[table] = {} }
- merged_wheres = merged_wheres.reverse.reject { |w|
- nuke = false
- if w.respond_to?(:operator) && w.operator == :==
- name = w.left.name
- table = w.left.relation.name
- nuke = seen[table][name]
- seen[table][name] = true
- end
- nuke
- }.reverse
- end
-
- merged_relation.where_values = merged_wheres
+ # This is overridden by Associations::CollectionProxy
+ def spawn #:nodoc:
+ clone
+ end
- (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with]).each do |method|
- value = r.send(:"#{method}_value")
- merged_relation.send(:"#{method}_value=", value) unless value.nil?
+ # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an <tt>ActiveRecord::Relation</tt>.
+ # Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array.
+ #
+ # ==== Examples
+ #
+ # Post.where(:published => true).joins(:comments).merge( Comment.where(:spam => false) )
+ # # Performs a single join query with both where conditions.
+ #
+ # recent_posts = Post.order('created_at DESC').first(5)
+ # Post.where(:published => true).merge(recent_posts)
+ # # Returns the intersection of all published posts with the 5 most recently created posts.
+ # # (This is just an example. You'd probably want to do this with a single query!)
+ #
+ def merge(other)
+ if other.is_a?(Array)
+ to_a & other
+ elsif other
+ spawn.merge!(other)
+ else
+ self
end
+ end
- merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
-
- merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty?
-
- # Apply scope extension modules
- merged_relation.send :apply_modules, r.extensions
-
- merged_relation
+ def merge!(other)
+ klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
+ klass.new(self, other).merge
end
# Removes from the query the condition(s) specified in +skips+.
@@ -71,20 +47,9 @@ module ActiveRecord
# Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
#
def except(*skips)
- result = self.class.new(@klass, table)
+ result = Relation.new(klass, table, values.except(*skips))
result.default_scoped = default_scoped
-
- ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method|
- result.send(:"#{method}_values=", send(:"#{method}_values"))
- end
-
- (Relation::SINGLE_VALUE_METHODS - skips).each do |method|
- result.send(:"#{method}_value=", send(:"#{method}_value"))
- end
-
- # Apply scope extension modules
- result.send(:apply_modules, extensions)
-
+ result.extend(*extending_values) if extending_values.any?
result
end
@@ -96,44 +61,11 @@ module ActiveRecord
# Post.order('id asc').only(:where, :order) # uses the specified order
#
def only(*onlies)
- result = self.class.new(@klass, table)
+ result = Relation.new(klass, table, values.slice(*onlies))
result.default_scoped = default_scoped
-
- ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method|
- result.send(:"#{method}_values=", send(:"#{method}_values"))
- end
-
- (Relation::SINGLE_VALUE_METHODS & onlies).each do |method|
- result.send(:"#{method}_value=", send(:"#{method}_value"))
- end
-
- # Apply scope extension modules
- result.send(:apply_modules, extensions)
-
+ result.extend(*extending_values) if extending_values.any?
result
end
- VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :extend,
- :order, :select, :readonly, :group, :having, :from, :lock ]
-
- def apply_finder_options(options)
- relation = clone
- return relation unless options
-
- options.assert_valid_keys(VALID_FIND_OPTIONS)
- finders = options.dup
- finders.delete_if { |key, value| value.nil? && key != :limit }
-
- ([:joins, :select, :group, :order, :having, :limit, :offset, :from, :lock, :readonly] & finders.keys).each do |finder|
- relation = relation.send(finder, finders[finder])
- end
-
- relation = relation.where(finders[:conditions]) if options.has_key?(:conditions)
- relation = relation.includes(finders[:include]) if options.has_key?(:include)
- relation = relation.extending(finders[:extend]) if options.has_key?(:extend)
-
- relation
- end
-
end
end
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 9ceab2eabc..fd276ccf5d 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -8,12 +8,13 @@ module ActiveRecord
class Result
include Enumerable
- attr_reader :columns, :rows
+ attr_reader :columns, :rows, :column_types
def initialize(columns, rows)
- @columns = columns
- @rows = rows
- @hash_rows = nil
+ @columns = columns
+ @rows = rows
+ @hash_rows = nil
+ @column_types = {}
end
def each
@@ -24,6 +25,32 @@ module ActiveRecord
hash_rows
end
+ alias :map! :map
+ alias :collect! :map
+
+ # Returns true if there are no records.
+ def empty?
+ rows.empty?
+ end
+
+ def to_ary
+ hash_rows
+ end
+
+ def [](idx)
+ hash_rows[idx]
+ end
+
+ def last
+ hash_rows.last
+ end
+
+ def initialize_copy(other)
+ @columns = columns.dup
+ @rows = rows.dup
+ @hash_rows = nil
+ end
+
private
def hash_rows
@hash_rows ||= @rows.map { |row|
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
new file mode 100644
index 0000000000..5530be3219
--- /dev/null
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -0,0 +1,194 @@
+require 'active_support/concern'
+
+module ActiveRecord
+ module Sanitization
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def quote_value(value, column = nil) #:nodoc:
+ connection.quote(value,column)
+ end
+
+ # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
+ def sanitize(object) #:nodoc:
+ connection.quote(object)
+ end
+
+ protected
+
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a WHERE clause.
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
+ # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
+ # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
+ def sanitize_sql_for_conditions(condition, table_name = self.table_name)
+ return nil if condition.blank?
+
+ case condition
+ when Array; sanitize_sql_array(condition)
+ when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
+ else condition
+ end
+ end
+ alias_method :sanitize_sql, :sanitize_sql_for_conditions
+
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a SET clause.
+ # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
+ def sanitize_sql_for_assignment(assignments)
+ case assignments
+ when Array; sanitize_sql_array(assignments)
+ when Hash; sanitize_sql_hash_for_assignment(assignments)
+ else assignments
+ end
+ end
+
+ # Accepts a hash of SQL conditions and replaces those attributes
+ # that correspond to a +composed_of+ relationship with their expanded
+ # aggregate attribute values.
+ # Given:
+ # class Person < ActiveRecord::Base
+ # composed_of :address, :class_name => "Address",
+ # :mapping => [%w(address_street street), %w(address_city city)]
+ # end
+ # Then:
+ # { :address => Address.new("813 abc st.", "chicago") }
+ # # => { :address_street => "813 abc st.", :address_city => "chicago" }
+ def expand_hash_conditions_for_aggregates(attrs)
+ expanded_attrs = {}
+ attrs.each do |attr, value|
+ if aggregation = reflect_on_aggregation(attr.to_sym)
+ mapping = aggregation.mapping
+ mapping.each do |field_attr, aggregate_attr|
+ if mapping.size == 1 && !value.respond_to?(aggregate_attr)
+ expanded_attrs[field_attr] = value
+ else
+ expanded_attrs[field_attr] = value.send(aggregate_attr)
+ end
+ end
+ else
+ expanded_attrs[attr] = value
+ end
+ end
+ expanded_attrs
+ end
+
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
+ # { :name => "foo'bar", :group_id => 4 }
+ # # => "name='foo''bar' and group_id= 4"
+ # { :status => nil, :group_id => [1,2,3] }
+ # # => "status IS NULL and group_id IN (1,2,3)"
+ # { :age => 13..18 }
+ # # => "age BETWEEN 13 AND 18"
+ # { 'other_records.id' => 7 }
+ # # => "`other_records`.`id` = 7"
+ # { :other_records => { :id => 7 } }
+ # # => "`other_records`.`id` = 7"
+ # And for value objects on a composed_of relationship:
+ # { :address => Address.new("123 abc st.", "chicago") }
+ # # => "address_street='123 abc st.' and address_city='chicago'"
+ def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
+ attrs = expand_hash_conditions_for_aggregates(attrs)
+
+ table = Arel::Table.new(table_name).alias(default_table_name)
+ PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
+ connection.visitor.accept b
+ }.join(' AND ')
+ end
+ alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
+
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
+ # { :status => nil, :group_id => 1 }
+ # # => "status = NULL , group_id = 1"
+ def sanitize_sql_hash_for_assignment(attrs)
+ attrs.map do |attr, value|
+ "#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
+ end.join(', ')
+ end
+
+ # Accepts an array of conditions. The array has each value
+ # sanitized and interpolated into the SQL statement.
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
+ def sanitize_sql_array(ary)
+ statement, *values = ary
+ if values.first.is_a?(Hash) && statement =~ /:\w+/
+ replace_named_bind_variables(statement, values.first)
+ elsif statement.include?('?')
+ replace_bind_variables(statement, values)
+ elsif statement.blank?
+ statement
+ else
+ statement % values.collect { |value| connection.quote_string(value.to_s) }
+ end
+ end
+
+ alias_method :sanitize_conditions, :sanitize_sql
+
+ def replace_bind_variables(statement, values) #:nodoc:
+ raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
+ bound = values.dup
+ c = connection
+ statement.gsub('?') { quote_bound_value(bound.shift, c) }
+ end
+
+ def replace_named_bind_variables(statement, bind_vars) #:nodoc:
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do
+ if $1 == ':' # skip postgresql casts
+ $& # return the whole match
+ elsif bind_vars.include?(match = $2.to_sym)
+ quote_bound_value(bind_vars[match])
+ else
+ raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
+ end
+ end
+ end
+
+ def expand_range_bind_variables(bind_vars) #:nodoc:
+ expanded = []
+
+ bind_vars.each do |var|
+ next if var.is_a?(Hash)
+
+ if var.is_a?(Range)
+ expanded << var.first
+ expanded << var.last
+ else
+ expanded << var
+ end
+ end
+
+ expanded
+ end
+
+ def quote_bound_value(value, c = connection) #:nodoc:
+ if value.respond_to?(:map) && !value.acts_like?(:string)
+ if value.respond_to?(:empty?) && value.empty?
+ c.quote(nil)
+ else
+ value.map { |v| c.quote(v) }.join(',')
+ end
+ else
+ c.quote(value)
+ end
+ end
+
+ def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
+ unless expected == provided
+ raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
+ end
+ end
+ end
+
+ # TODO: Deprecate this
+ def quoted_id #:nodoc:
+ quote_value(id, column_for_attribute(self.class.primary_key))
+ end
+
+ private
+
+ # Quote strings appropriately for SQL statements.
+ def quote_value(value, column = nil)
+ self.class.connection.quote(value, column)
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index d815ab05ac..599e68379a 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -34,6 +34,15 @@ module ActiveRecord
ActiveRecord::Migrator.migrations_paths
end
+ def define(info, &block)
+ instance_eval(&block)
+
+ unless info[:version].blank?
+ initialize_schema_migrations_table
+ assume_migrated_upto_version(info[:version], migrations_paths)
+ end
+ end
+
# Eval the given block. All methods available to the current connection
# adapter are available within the block, so you can easily use the
# database definition DSL to build up your schema (+create_table+,
@@ -46,13 +55,7 @@ module ActiveRecord
# ...
# end
def self.define(info={}, &block)
- schema = new
- schema.instance_eval(&block)
-
- unless info[:version].blank?
- initialize_schema_migrations_table
- assume_migrated_upto_version(info[:version], schema.migrations_paths)
- end
+ new.define(info, &block)
end
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 6fe305f843..7cbe2db408 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -40,7 +40,7 @@ module ActiveRecord
def header(stream)
define_params = @version ? ":version => #{@version}" : ""
- if stream.respond_to?(:external_encoding)
+ if stream.respond_to?(:external_encoding) && stream.external_encoding
stream.puts "# encoding: #{stream.external_encoding.name}"
end
@@ -55,7 +55,7 @@ module ActiveRecord
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
-# It's strongly recommended to check this file into your version control system.
+# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(#{define_params}) do
@@ -112,7 +112,7 @@ HEADER
# AR has an optimization which handles zero-scale decimals as integers. This
# code ensures that the dumper still dumps the column as a decimal.
- spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
+ spec[:type] = if column.type == :integer && /^(numeric|decimal)/ =~ column.sql_type
'decimal'
else
column.type.to_s
@@ -127,10 +127,14 @@ HEADER
end.compact
# find all migration keys used in this table
- keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map{ |k| k.keys }.flatten
+ keys = [:name, :limit, :precision, :scale, :default, :null]
# figure out the lengths for each column based on above keys
- lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
+ lengths = keys.map { |key|
+ column_specs.map { |spec|
+ spec[key] ? spec[key].length + 2 : 0
+ }.max
+ }
# the string we're going to sprintf our values against, with standardized column widths
format_string = lengths.map{ |len| "%-#{len}s" }
@@ -190,6 +194,11 @@ HEADER
index_lengths = (index.lengths || []).compact
statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
+ index_orders = (index.orders || {})
+ statement_parts << (':order => ' + index.orders.inspect) unless index_orders.empty?
+
+ statement_parts << (':where => ' + index.where.inspect) if index.where
+
' ' + statement_parts.join(', ')
end
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
new file mode 100644
index 0000000000..236ec563d2
--- /dev/null
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -0,0 +1,34 @@
+require 'active_record/scoping/default'
+require 'active_record/scoping/named'
+require 'active_record/base'
+
+module ActiveRecord
+ class SchemaMigration < ActiveRecord::Base
+ attr_accessible :version
+
+ def self.table_name
+ Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
+ end
+
+ def self.create_table
+ unless connection.table_exists?(table_name)
+ connection.create_table(table_name, :id => false) do |t|
+ t.column :version, :string, :null => false
+ end
+ connection.add_index table_name, :version, :unique => true,
+ :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
+ end
+ end
+
+ def self.drop_table
+ if connection.table_exists?(table_name)
+ connection.remove_index table_name, :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
+ connection.drop_table(table_name)
+ end
+ end
+
+ def version
+ super.to_i
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
new file mode 100644
index 0000000000..66a486ae0a
--- /dev/null
+++ b/activerecord/lib/active_record/scoping.rb
@@ -0,0 +1,31 @@
+require 'active_support/concern'
+
+module ActiveRecord
+ module Scoping
+ extend ActiveSupport::Concern
+
+ included do
+ include Default
+ include Named
+ end
+
+ module ClassMethods
+ def current_scope #:nodoc:
+ Thread.current["#{self}_current_scope"]
+ end
+
+ def current_scope=(scope) #:nodoc:
+ Thread.current["#{self}_current_scope"] = scope
+ end
+ end
+
+ def populate_with_current_scope_attributes
+ return unless self.class.scope_attributes?
+
+ self.class.scope_attributes.each do |att,value|
+ send("#{att}=", value) if respond_to?("#{att}=")
+ end
+ end
+
+ end
+end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
new file mode 100644
index 0000000000..db833fc7f1
--- /dev/null
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -0,0 +1,146 @@
+require 'active_support/concern'
+require 'active_support/deprecation'
+
+module ActiveRecord
+ module Scoping
+ module Default
+ extend ActiveSupport::Concern
+
+ included do
+ # Stores the default scope for the class
+ config_attribute :default_scopes
+ self.default_scopes = []
+ end
+
+ module ClassMethods
+ # Returns a scope for the model without the default_scope.
+ #
+ # class Post < ActiveRecord::Base
+ # def self.default_scope
+ # where :published => true
+ # end
+ # end
+ #
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
+ #
+ # This method also accepts a block. All queries inside the block will
+ # not use the default_scope:
+ #
+ # Post.unscoped {
+ # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
+ # }
+ #
+ # It is recommended that you use the block form of unscoped because chaining
+ # unscoped with <tt>scope</tt> does not work. Assuming that
+ # <tt>published</tt> is a <tt>scope</tt>, the following two statements
+ # are equal: the default_scope is applied on both.
+ #
+ # Post.unscoped.published
+ # Post.published
+ def unscoped #:nodoc:
+ block_given? ? relation.scoping { yield } : relation
+ end
+
+ def before_remove_const #:nodoc:
+ self.current_scope = nil
+ end
+
+ protected
+
+ # Use this macro in your model to set a default scope for all operations on
+ # the model.
+ #
+ # class Article < ActiveRecord::Base
+ # default_scope { where(:published => true) }
+ # end
+ #
+ # Article.all # => SELECT * FROM articles WHERE published = true
+ #
+ # The <tt>default_scope</tt> is also applied while creating/building a record. It is not
+ # applied while updating a record.
+ #
+ # Article.new.published # => true
+ # Article.create.published # => true
+ #
+ # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
+ # macro, and it will be called when building the default scope.)
+ #
+ # If you use multiple <tt>default_scope</tt> declarations in your model then they will
+ # be merged together:
+ #
+ # class Article < ActiveRecord::Base
+ # default_scope { where(:published => true) }
+ # default_scope { where(:rating => 'G') }
+ # end
+ #
+ # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
+ #
+ # This is also the case with inheritance and module includes where the parent or module
+ # defines a <tt>default_scope</tt> and the child or including class defines a second one.
+ #
+ # If you need to do more complex things with a default scope, you can alternatively
+ # define it as a class method:
+ #
+ # class Article < ActiveRecord::Base
+ # def self.default_scope
+ # # Should return a scope, you can call 'super' here etc.
+ # end
+ # end
+ def default_scope(scope = nil)
+ scope = Proc.new if block_given?
+
+ if scope.is_a?(Relation) || !scope.respond_to?(:call)
+ ActiveSupport::Deprecation.warn(
+ "Calling #default_scope without a block is deprecated. For example instead " \
+ "of `default_scope where(color: 'red')`, please use " \
+ "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \
+ "self.default_scope.)"
+ )
+ end
+
+ self.default_scopes = default_scopes + [scope]
+ end
+
+ def build_default_scope #:nodoc:
+ if !Base.is_a?(method(:default_scope).owner)
+ # The user has defined their own default scope method, so call that
+ evaluate_default_scope { default_scope }
+ elsif default_scopes.any?
+ evaluate_default_scope do
+ default_scopes.inject(relation) do |default_scope, scope|
+ if !scope.is_a?(Relation) && scope.respond_to?(:call)
+ default_scope.merge(unscoped { scope.call })
+ else
+ default_scope.merge(scope)
+ end
+ end
+ end
+ end
+ end
+
+ def ignore_default_scope? #:nodoc:
+ Thread.current["#{self}_ignore_default_scope"]
+ end
+
+ def ignore_default_scope=(ignore) #:nodoc:
+ Thread.current["#{self}_ignore_default_scope"] = ignore
+ end
+
+ # The ignore_default_scope flag is used to prevent an infinite recursion situation where
+ # a default scope references a scope which has a default scope which references a scope...
+ def evaluate_default_scope
+ return if ignore_default_scope?
+
+ begin
+ self.ignore_default_scope = true
+ yield
+ ensure
+ self.ignore_default_scope = false
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
new file mode 100644
index 0000000000..2af476c1ba
--- /dev/null
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -0,0 +1,200 @@
+require 'active_support/core_ext/array'
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/class/attribute'
+require 'active_support/deprecation'
+
+module ActiveRecord
+ # = Active Record Named \Scopes
+ module Scoping
+ module Named
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Returns an anonymous \scope.
+ #
+ # posts = Post.scoped
+ # posts.size # Fires "select count(*) from posts" and returns the count
+ # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
+ #
+ # fruits = Fruit.scoped
+ # fruits = fruits.where(:color => 'red') if options[:red_only]
+ # fruits = fruits.limit(10) if limited?
+ #
+ # Anonymous \scopes tend to be useful when procedurally generating complex
+ # queries, where passing intermediate values (\scopes) around as first-class
+ # objects is convenient.
+ #
+ # You can define a \scope that applies to all finders using
+ # ActiveRecord::Base.default_scope.
+ def scoped(options = nil)
+ if current_scope
+ scope = current_scope.clone
+ else
+ scope = relation
+ scope.default_scoped = true
+ scope
+ end
+
+ scope.merge!(options) if options
+ scope
+ end
+
+ ##
+ # Collects attributes from scopes that should be applied when creating
+ # an AR instance for the particular class this is called on.
+ def scope_attributes # :nodoc:
+ if current_scope
+ current_scope.scope_for_create
+ else
+ scope = relation
+ scope.default_scoped = true
+ scope.scope_for_create
+ end
+ end
+
+ ##
+ # Are there default attributes associated with this scope?
+ def scope_attributes? # :nodoc:
+ current_scope || default_scopes.any?
+ end
+
+ # Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
+ # such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
+ #
+ # class Shirt < ActiveRecord::Base
+ # scope :red, where(:color => 'red')
+ # scope :dry_clean_only, joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true)
+ # end
+ #
+ # 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>.
+ # Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable;
+ # <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
+ # all behave as if Shirt.red really was an Array.
+ #
+ # These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce
+ # all shirts that are both red and dry clean only.
+ # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
+ # returns the number of garments for which these criteria obtain. Similarly with
+ # <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
+ #
+ # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which
+ # the \scopes were defined. But they are also available to <tt>has_many</tt> associations. If,
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :shirts
+ # end
+ #
+ # 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:
+ #
+ # class Shirt < ActiveRecord::Base
+ # scope :colored, lambda { |color| where(:color => color) }
+ # end
+ #
+ # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
+ #
+ # On Ruby 1.9 you can use the 'stabby lambda' syntax:
+ #
+ # scope :colored, ->(color) { where(:color => color) }
+ #
+ # 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.current - 1.week)
+ # end
+ #
+ # The example above would be 'frozen' to the <tt>Time.current</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 lambda, which will re-evaluate the scope each time
+ # it is called:
+ #
+ # class Post < ActiveRecord::Base
+ # scope :recent, lambda { where('published_at >= ?', Time.current - 1.week) }
+ # end
+ #
+ # Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
+ #
+ # class Shirt < ActiveRecord::Base
+ # scope :red, where(:color => 'red') do
+ # def dom_id
+ # 'red_shirts'
+ # end
+ # end
+ # end
+ #
+ # Scopes can also be used while creating/building a record.
+ #
+ # class Article < ActiveRecord::Base
+ # scope :published, where(:published => true)
+ # end
+ #
+ # 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, body, &block)
+ extension = Module.new(&block) if block
+
+ # Check body.is_a?(Relation) to prevent the relation actually being
+ # loaded by respond_to?
+ if body.is_a?(Relation) || !body.respond_to?(:call)
+ ActiveSupport::Deprecation.warn(
+ "Using #scope without passing a callable object is deprecated. For " \
+ "example `scope :red, where(color: 'red')` should be changed to " \
+ "`scope :red, -> { where(color: 'red') }`. There are numerous gotchas " \
+ "in the former usage and it makes the implementation more complicated " \
+ "and buggy. (If you prefer, you can just define a class method named " \
+ "`self.red`.)"
+ )
+ end
+
+ singleton_class.send(:define_method, name) do |*args|
+ options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body
+ relation = scoped.merge(options)
+
+ extension ? relation.extending(extension) : relation
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 5ad40d8cd9..41e3b92499 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -7,8 +7,8 @@ module ActiveRecord #:nodoc:
def serializable_hash(options = nil)
options = options.try(:clone) || {}
- options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
- options[:except] |= Array.wrap(self.class.inheritance_column)
+ options[:except] = Array(options[:except]).map { |n| n.to_s }
+ options[:except] |= Array(self.class.inheritance_column)
super(options)
end
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index 0e7f57aa43..2e60521638 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/conversions'
module ActiveRecord #:nodoc:
@@ -163,8 +162,9 @@ module ActiveRecord #:nodoc:
#
# class IHaveMyOwnXML < ActiveRecord::Base
# def to_xml(options = {})
+ # require 'builder'
# options[:indent] ||= 2
- # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+ # xml = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
# xml.instruct! unless options[:skip_instruct]
# xml.level_one do
# xml.tag!(:second_level, 'content')
@@ -179,7 +179,7 @@ module ActiveRecord #:nodoc:
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
def initialize(*args)
super
- options[:except] = Array.wrap(options[:except]) | Array.wrap(@serializable.class.inheritance_column)
+ options[:except] = Array(options[:except]) | Array(@serializable.class.inheritance_column)
end
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 76c37cc367..5a256b040b 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -51,20 +51,20 @@ module ActiveRecord
class SessionStore < ActionDispatch::Session::AbstractStore
module ClassMethods # :nodoc:
def marshal(data)
- ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
+ ::Base64.encode64(Marshal.dump(data)) if data
end
def unmarshal(data)
- Marshal.load(ActiveSupport::Base64.decode64(data)) if data
+ Marshal.load(::Base64.decode64(data)) if data
end
def drop_table!
- connection_pool.clear_table_cache!(table_name)
+ connection.schema_cache.clear_table_cache!(table_name)
connection.drop_table table_name
end
def create_table!
- connection_pool.clear_table_cache!(table_name)
+ connection.schema_cache.clear_table_cache!(table_name)
connection.create_table(table_name) do |t|
t.string session_id_column, :limit => 255
t.text data_column_name
@@ -116,10 +116,10 @@ module ActiveRecord
define_method(:session_id) { sessid }
define_method(:session_id=) { |session_id| self.sessid = session_id }
else
- class << self; remove_method :find_by_session_id; end
+ class << self; remove_possible_method :find_by_session_id; end
def self.find_by_session_id(session_id)
- find :first, :conditions => {:session_id=>session_id}
+ where(session_id: session_id).first
end
end
end
@@ -169,11 +169,11 @@ module ActiveRecord
# are implemented as class methods that you may override. By default,
# marshaling data is
#
- # ActiveSupport::Base64.encode64(Marshal.dump(data))
+ # ::Base64.encode64(Marshal.dump(data))
#
# and unmarshaling data is
#
- # Marshal.load(ActiveSupport::Base64.decode64(data))
+ # Marshal.load(::Base64.decode64(data))
#
# This marshaling behavior is intended to store the widest range of
# binary session data in a +text+ column. For higher performance,
@@ -201,10 +201,10 @@ module ActiveRecord
class << self
alias :data_column_name :data_column
-
+
# Use the ActiveRecord::Base.connection by default.
attr_writer :connection
-
+
# Use the ActiveRecord::Base.connection_pool by default.
attr_writer :connection_pool
@@ -218,12 +218,12 @@ module ActiveRecord
# Look up a session by id and unmarshal its data if found.
def find_by_session_id(session_id)
- if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id)}")
+ if record = connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{connection.quote(session_id.to_s)}")
new(:session_id => session_id, :marshaled_data => record['data'])
end
end
end
-
+
delegate :connection, :connection=, :connection_pool, :connection_pool=, :to => self
attr_reader :session_id, :new_record
@@ -241,6 +241,11 @@ module ActiveRecord
@new_record = @marshaled_data.nil?
end
+ # Returns true if the record is persisted, i.e. it's not a new record
+ def persisted?
+ !@new_record
+ end
+
# Lazy-unmarshal session state.
def data
unless @data
@@ -287,7 +292,7 @@ module ActiveRecord
connect = connection
connect.delete <<-end_sql, 'Destroy session'
DELETE FROM #{table_name}
- WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
+ WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id.to_s)}
end_sql
end
end
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index d5910df891..ce2ea85ef9 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -10,15 +10,21 @@ module ActiveRecord
# Make sure that you declare the database column used for the serialized store as a text, so there's
# plenty of room.
#
+ # You can set custom coder to encode/decode your serialized attributes to/from different formats.
+ # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
+ #
+ # String keys should be used for direct access to virtual attributes because of most of the coders do not
+ # distinguish symbols and strings as keys.
+ #
# Examples:
#
# class User < ActiveRecord::Base
- # store :settings, accessors: [ :color, :homepage ]
+ # store :settings, accessors: [ :color, :homepage ], coder: JSON
# end
- #
+ #
# u = User.new(color: 'black', homepage: '37signals.com')
- # u.color # Accessor stored attribute
- # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
+ # u.color # Accessor stored attribute
+ # u.settings['country'] = 'Denmark' # Any attribute, even if not specified with an accessor
#
# # Add additional accessors to an existing store through store_accessor
# class SuperUser < User
@@ -26,24 +32,27 @@ module ActiveRecord
# end
module Store
extend ActiveSupport::Concern
-
+
module ClassMethods
def store(store_attribute, options = {})
- serialize store_attribute, Hash
+ serialize store_attribute, options.fetch(:coder, Hash)
store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
end
def store_accessor(store_attribute, *keys)
- Array(keys).flatten.each do |key|
+ keys.flatten.each do |key|
define_method("#{key}=") do |value|
- send(store_attribute)[key] = value
+ send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
+ send(store_attribute)[key.to_s] = value
+ send("#{store_attribute}_will_change!")
end
-
+
define_method(key) do
- send(store_attribute)[key]
+ send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash)
+ send(store_attribute)[key.to_s]
end
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index ffe9b08dce..fcaa4b74a6 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -1,23 +1,14 @@
+require 'active_support/deprecation'
+require 'active_support/test_case'
+
+ActiveSupport::Deprecation.warn('ActiveRecord::TestCase is deprecated, please use ActiveSupport::TestCase')
module ActiveRecord
# = Active Record Test Case
#
# Defines some test assertions to test against SQL queries.
class TestCase < ActiveSupport::TestCase #:nodoc:
- setup :cleanup_identity_map
-
- def setup
- cleanup_identity_map
- end
-
- def cleanup_identity_map
- ActiveRecord::IdentityMap.clear
- end
-
- # Backport skip to Ruby 1.8. test/unit doesn't support it, so just
- # make it a noop.
- unless instance_methods.map(&:to_s).include?("skip")
- def skip(message)
- end
+ def teardown
+ SQLCounter.log.clear
end
def assert_date_from_db(expected, actual, message = nil)
@@ -31,43 +22,63 @@ module ActiveRecord
end
def assert_sql(*patterns_to_match)
- ActiveRecord::SQLCounter.log = []
+ SQLCounter.log = []
yield
- ActiveRecord::SQLCounter.log
+ SQLCounter.log
ensure
failed_patterns = []
patterns_to_match.each do |pattern|
- failed_patterns << pattern unless ActiveRecord::SQLCounter.log.any?{ |sql| pattern === sql }
+ failed_patterns << pattern unless SQLCounter.log.any?{ |sql| pattern === sql }
end
- assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
end
def assert_queries(num = 1)
- ActiveRecord::SQLCounter.log = []
+ SQLCounter.log = []
yield
ensure
- assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
+ assert_equal num, SQLCounter.log.size, "#{SQLCounter.log.size} instead of #{num} queries were executed.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
end
def assert_no_queries(&block)
- prev_ignored_sql = ActiveRecord::SQLCounter.ignored_sql
- ActiveRecord::SQLCounter.ignored_sql = []
+ prev_ignored_sql = SQLCounter.ignored_sql
+ SQLCounter.ignored_sql = []
assert_queries(0, &block)
ensure
- ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql
+ SQLCounter.ignored_sql = prev_ignored_sql
end
- def with_kcode(kcode)
- if RUBY_VERSION < '1.9'
- orig_kcode, $KCODE = $KCODE, kcode
- begin
- yield
- ensure
- $KCODE = orig_kcode
- end
- else
- yield
- end
+ end
+
+ class SQLCounter
+ class << self
+ attr_accessor :ignored_sql, :log
+ end
+
+ self.log = []
+
+ self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
+
+ # FIXME: this needs to be refactored so specific database can add their own
+ # ignored SQL. This ignored SQL is for Oracle.
+ ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
+
+
+ attr_reader :ignore
+
+ def initialize(ignore = Regexp.union(self.class.ignored_sql))
+ @ignore = ignore
+ end
+
+ def call(name, start, finish, message_id, values)
+ sql = values[:sql]
+
+ # FIXME: this seems bad. we should probably have a better way to indicate
+ # the query was cached
+ return if 'CACHE' == values[:name] || ignore =~ sql
+ self.class.log << sql
end
end
+
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 4d5e469a7f..c717fdea47 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -33,7 +33,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- class_attribute :record_timestamps, :instance_writer => false
+ config_attribute :record_timestamps, :instance_writer => true
self.record_timestamps = true
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index ae97a3f3ca..30e1035300 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -211,7 +211,7 @@ module ActiveRecord
def after_commit(*args, &block)
options = args.last
if options.is_a?(Hash) && options[:on]
- options[:if] = Array.wrap(options[:if])
+ options[:if] = Array(options[:if])
options[:if] << "transaction_include_action?(:#{options[:on]})"
end
set_callback(:commit, :after, *args, &block)
@@ -220,7 +220,7 @@ module ActiveRecord
def after_rollback(*args, &block)
options = args.last
if options.is_a?(Hash) && options[:on]
- options[:if] = Array.wrap(options[:if])
+ options[:if] = Array(options[:if])
options[:if] << "transaction_include_action?(:#{options[:on]})"
end
set_callback(:rollback, :after, *args, &block)
@@ -251,7 +251,6 @@ module ActiveRecord
remember_transaction_record_state
yield
rescue Exception
- IdentityMap.remove(self) if IdentityMap.enabled?
restore_transaction_record_state
raise
ensure
@@ -270,7 +269,6 @@ 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
@@ -292,7 +290,15 @@ module ActiveRecord
status = nil
self.class.transaction do
add_to_transaction
- status = yield
+ begin
+ status = yield
+ rescue ActiveRecord::Rollback
+ if defined?(@_start_transaction_state)
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
+ end
+ status = nil
+ end
+
raise ActiveRecord::Rollback unless status
end
status
@@ -301,20 +307,16 @@ module ActiveRecord
protected
# Save the new record state and id of a record so it can be restored later if a transaction fails.
- def remember_transaction_record_state #:nodoc
+ def remember_transaction_record_state #:nodoc:
@_start_transaction_state ||= {}
@_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
- unless @_start_transaction_state.include?(:new_record)
- @_start_transaction_state[:new_record] = @new_record
- end
- unless @_start_transaction_state.include?(:destroyed)
- @_start_transaction_state[:destroyed] = @destroyed
- end
+ @_start_transaction_state[:new_record] = @new_record
+ @_start_transaction_state[:destroyed] = @destroyed
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
end
# Clear the new record state and id of a record.
- def clear_transaction_record_state #:nodoc
+ def clear_transaction_record_state #:nodoc:
if defined?(@_start_transaction_state)
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
@@ -322,7 +324,7 @@ module ActiveRecord
end
# Restore the new record state and id of a record that was previously saved by a call to save_record_state.
- def restore_transaction_record_state(force = false) #:nodoc
+ def restore_transaction_record_state(force = false) #:nodoc:
if defined?(@_start_transaction_state)
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
if @_start_transaction_state[:level] < 1
@@ -341,12 +343,12 @@ module ActiveRecord
end
# Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
- def transaction_record_state(state) #:nodoc
+ def transaction_record_state(state) #:nodoc:
@_start_transaction_state[state] if defined?(@_start_transaction_state)
end
# Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
- def transaction_include_action?(action) #:nodoc
+ def transaction_include_action?(action) #:nodoc:
case action
when :create
transaction_record_state(:new_record)
diff --git a/activerecord/lib/active_record/translation.rb b/activerecord/lib/active_record/translation.rb
new file mode 100644
index 0000000000..ddcb5f2a7a
--- /dev/null
+++ b/activerecord/lib/active_record/translation.rb
@@ -0,0 +1,22 @@
+module ActiveRecord
+ module Translation
+ include ActiveModel::Translation
+
+ # Set the lookup ancestors for ActiveModel.
+ def lookup_ancestors #:nodoc:
+ klass = self
+ classes = [klass]
+ return classes if klass == ActiveRecord::Base
+
+ while klass != klass.base_class
+ classes << klass = klass.superclass
+ end
+ classes
+ end
+
+ # Set the i18n scope to overwrite ActiveModel.
+ def i18n_scope #:nodoc:
+ :activerecord
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 4b075183c3..d06020b3ce 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -14,7 +14,7 @@ module ActiveRecord
def initialize(record)
@record = record
errors = @record.errors.full_messages.join(", ")
- super(I18n.t("activerecord.errors.messages.record_invalid", :errors => errors))
+ super(I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", :errors => errors, :default => :"errors.messages.record_invalid"))
end
end
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 7af0352a31..afce149da9 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -2,8 +2,9 @@ module ActiveRecord
module Validations
class AssociatedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
- return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.nil? || r.valid? }.all?
- record.errors.add(attribute, :invalid, options.merge(:value => value))
+ if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?(record.validation_context) }.any?
+ record.errors.add(attribute, :invalid, options.merge(:value => value))
+ end
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 2e2ea8c42b..9e4b588ac2 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/array/prepend_and_append'
module ActiveRecord
module Validations
@@ -25,13 +25,24 @@ module ActiveRecord
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|
+ Array(options[:scope]).each do |scope_item|
scope_value = record.send(scope_item)
+ reflection = record.class.reflect_on_association(scope_item)
+ if reflection
+ scope_value = record.send(reflection.foreign_key)
+ scope_item = reflection.foreign_key
+ end
relation = relation.and(table[scope_item].eq(scope_value))
end
- if finder_class.unscoped.where(relation).exists?
- record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
+ relation = finder_class.unscoped.where(relation)
+
+ if options[:conditions]
+ relation = relation.merge(options[:conditions])
+ end
+
+ if relation.exists?
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope, :conditions).merge(:value => value))
end
end
@@ -46,21 +57,28 @@ module ActiveRecord
class_hierarchy = [record.class]
while class_hierarchy.first != @klass
- class_hierarchy.insert(0, class_hierarchy.first.superclass)
+ class_hierarchy.prepend(class_hierarchy.first.superclass)
end
class_hierarchy.detect { |klass| !klass.abstract_class? }
end
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?
+ reflection = klass.reflect_on_association(attribute)
+ if reflection
+ column = klass.columns_hash[reflection.foreign_key]
+ attribute = reflection.foreign_key
+ value = value.attributes[reflection.primary_key_column.name]
+ else
+ column = klass.columns_hash[attribute.to_s]
+ end
+ value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text?
if !options[:case_sensitive] && value && column.text?
# will use SQL LOWER function before comparison, unless it detects a case insensitive collation
relation = klass.connection.case_insensitive_comparison(table, attribute, column, value)
else
- value = klass.connection.case_sensitive_modifier(value)
+ value = klass.connection.case_sensitive_modifier(value) unless value.nil?
relation = table[attribute].eq(value)
end
@@ -81,7 +99,7 @@ module ActiveRecord
#
# class Person < ActiveRecord::Base
# validates_uniqueness_of :user_name, :scope => :account_id
- # end
+ # end
#
# Or even multiple scope parameters. For example, making sure that a teacher can only be on the schedule once
# per semester for a particular class.
@@ -90,6 +108,14 @@ module ActiveRecord
# validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
# end
#
+ # It is also possible to limit the uniqueness constraint to a set of records matching certain conditions.
+ # In this example archived articles are not being taken into consideration when validating uniqueness
+ # of the title attribute:
+ #
+ # class Article < ActiveRecord::Base
+ # validates_uniqueness_of :title, :conditions => where('status != ?', 'archived')
+ # end
+ #
# When the record is created, a check is performed to make sure that no record exists in the database
# with the given value for the specified attribute (that maps to a column). When the record is updated,
# the same check is made but disregarding the record itself.
@@ -97,6 +123,8 @@ module ActiveRecord
# Configuration options:
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
# * <tt>:scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint.
+ # * <tt>:conditions</tt> - Specify the conditions to be included as a <tt>WHERE</tt> SQL fragment to limit
+ # the uniqueness constraint lookup. (e.g. <tt>:conditions => where('status = ?', 'active')</tt>)
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the 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+).
@@ -168,7 +196,6 @@ module ActiveRecord
# The following bundled adapters throw the ActiveRecord::RecordNotUnique exception:
# * ActiveRecord::ConnectionAdapters::MysqlAdapter
# * ActiveRecord::ConnectionAdapters::Mysql2Adapter
- # * ActiveRecord::ConnectionAdapters::SQLiteAdapter
# * ActiveRecord::ConnectionAdapters::SQLite3Adapter
# * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
#
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 838aa8fb1e..0c35adc11d 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
diff --git a/activerecord/lib/rails/generators/active_record.rb b/activerecord/lib/rails/generators/active_record.rb
index 4b3d1db216..297cd094c2 100644
--- a/activerecord/lib/rails/generators/active_record.rb
+++ b/activerecord/lib/rails/generators/active_record.rb
@@ -1,14 +1,12 @@
require 'rails/generators/named_base'
require 'rails/generators/migration'
require 'rails/generators/active_model'
-require 'rails/generators/active_record/migration'
require 'active_record'
module ActiveRecord
module Generators
class Base < Rails::Generators::NamedBase #:nodoc:
include Rails::Generators::Migration
- extend ActiveRecord::Generators::Migration
# Set the current directory as base for the inherited generators.
def self.base_root
diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb
deleted file mode 100644
index 7f2f2e06a5..0000000000
--- a/activerecord/lib/rails/generators/active_record/migration.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module ActiveRecord
- module Generators
- module Migration
- # Implement the required interface for Rails::Generators::Migration.
- def next_migration_number(dirname) #:nodoc:
- next_migration_number = current_migration_number(dirname) + 1
- if ActiveRecord::Base.timestamped_migrations
- [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
- else
- "%.3d" % next_migration_number
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index f6159deeeb..1509e34473 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -3,7 +3,7 @@ require 'rails/generators/active_record'
module ActiveRecord
module Generators
class MigrationGenerator < Base
- argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
+ argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]"
def create_migration_file
set_local_assigns!
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index ce8d7eed42..b1a0f83b5f 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -2,14 +2,17 @@ class <%= migration_class_name %> < ActiveRecord::Migration
<%- if migration_action == 'add' -%>
def change
<% attributes.each do |attribute| -%>
- add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %>
+ add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
+ <%- if attribute.has_index? -%>
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
+ <%- end -%>
<%- end -%>
end
<%- else -%>
def up
<% attributes.each do |attribute| -%>
<%- if migration_action -%>
- <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end %>
+ <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %>
<%- end -%>
<%- end -%>
end
@@ -17,7 +20,10 @@ class <%= migration_class_name %> < ActiveRecord::Migration
def down
<% attributes.reverse.each do |attribute| -%>
<%- if migration_action -%>
- <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end %>
+ add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
+ <%- if attribute.has_index? -%>
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
+ <%- end -%>
<%- end -%>
<%- end -%>
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index f7caa43ac8..8e6ef20285 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -3,7 +3,7 @@ require 'rails/generators/active_record'
module ActiveRecord
module Generators
class ModelGenerator < Base
- argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
+ argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]"
check_class_collision
@@ -14,6 +14,7 @@ module ActiveRecord
def create_migration_file
return unless options[:migration] && options[:parent].nil?
+ attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false
migration_template "migration.rb", "db/migrate/create_#{table_name}.rb"
end
@@ -26,6 +27,14 @@ module ActiveRecord
template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
end
+ def attributes_with_index
+ attributes.select { |a| !a.reference? && a.has_index? }
+ end
+
+ def accessible_attributes
+ attributes.reject(&:reference?)
+ end
+
hook_for :test_framework
protected
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
index 851930344a..3a3cf86d73 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
@@ -2,16 +2,14 @@ class <%= migration_class_name %> < ActiveRecord::Migration
def change
create_table :<%= table_name %> do |t|
<% attributes.each do |attribute| -%>
- t.<%= attribute.type %> :<%= attribute.name %>
+ t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
<% end -%>
<% if options[:timestamps] %>
t.timestamps
<% end -%>
end
-<% if options[:indexes] -%>
-<% attributes.select {|attr| attr.reference? }.each do |attribute| -%>
- add_index :<%= table_name %>, :<%= attribute.name %>_id
-<% end -%>
+<% attributes_with_index.each do |attribute| -%>
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
<% end -%>
end
end
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb
index 5c47f8b241..d56f9f57a4 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb
@@ -3,5 +3,10 @@ class <%= class_name %> < <%= parent_class_name.classify %>
<% attributes.select {|attr| attr.reference? }.each do |attribute| -%>
belongs_to :<%= attribute.name %>
<% end -%>
+<% if !accessible_attributes.empty? -%>
+ attr_accessible <%= accessible_attributes.map {|a| ":#{a.name}" }.sort.join(', ') %>
+<% else -%>
+ # attr_accessible :title, :body
+<% end -%>
end
<% end -%>
diff --git a/activerecord/test/active_record/connection_adapters/fake_adapter.rb b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
index 1c2942170e..69dfd2503e 100644
--- a/activerecord/test/active_record/connection_adapters/fake_adapter.rb
+++ b/activerecord/test/active_record/connection_adapters/fake_adapter.rb
@@ -9,11 +9,16 @@ module ActiveRecord
class FakeAdapter < AbstractAdapter
attr_accessor :tables, :primary_keys
+ @columns = Hash.new { |h,k| h[k] = [] }
+ class << self
+ attr_reader :columns
+ end
+
def initialize(connection, logger)
super
@tables = []
@primary_keys = {}
- @columns = Hash.new { |h,k| h[k] = [] }
+ @columns = self.class.columns
end
def primary_key(table)
@@ -28,7 +33,7 @@ module ActiveRecord
options[:null])
end
- def columns(table_name, message)
+ def columns(table_name)
@columns[table_name]
end
end
diff --git a/activerecord/test/assets/test.txt b/activerecord/test/assets/test.txt
new file mode 100644
index 0000000000..6754f0612e
--- /dev/null
+++ b/activerecord/test/assets/test.txt
@@ -0,0 +1 @@
+%00
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 94497e37c7..852fc0e26e 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -1,170 +1,163 @@
require "cases/helper"
-class AdapterTest < ActiveRecord::TestCase
- def setup
- @connection = ActiveRecord::Base.connection
- end
-
- def test_tables
- tables = @connection.tables
- assert tables.include?("accounts")
- assert tables.include?("authors")
- assert tables.include?("tasks")
- assert tables.include?("topics")
- end
+module ActiveRecord
+ class AdapterTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
- def test_table_exists?
- assert @connection.table_exists?("accounts")
- assert !@connection.table_exists?("nonexistingtable")
- end
+ def test_tables
+ tables = @connection.tables
+ assert tables.include?("accounts")
+ assert tables.include?("authors")
+ assert tables.include?("tasks")
+ assert tables.include?("topics")
+ end
- def test_indexes
- idx_name = "accounts_idx"
-
- if @connection.respond_to?(:indexes)
- indexes = @connection.indexes("accounts")
- assert indexes.empty?
-
- @connection.add_index :accounts, :firm_id, :name => idx_name
- indexes = @connection.indexes("accounts")
- assert_equal "accounts", indexes.first.table
- # OpenBase does not have the concept of a named index
- # Indexes are merely properties of columns.
- assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter)
- assert !indexes.first.unique
- assert_equal ["firm_id"], indexes.first.columns
- else
- warn "#{@connection.class} does not respond to #indexes"
+ def test_table_exists?
+ assert @connection.table_exists?("accounts")
+ assert !@connection.table_exists?("nonexistingtable")
+ assert !@connection.table_exists?(nil)
end
- ensure
- @connection.remove_index(:accounts, :name => idx_name) rescue nil
- end
+ def test_indexes
+ idx_name = "accounts_idx"
+
+ if @connection.respond_to?(:indexes)
+ indexes = @connection.indexes("accounts")
+ assert indexes.empty?
+
+ @connection.add_index :accounts, :firm_id, :name => idx_name
+ indexes = @connection.indexes("accounts")
+ assert_equal "accounts", indexes.first.table
+ # OpenBase does not have the concept of a named index
+ # Indexes are merely properties of columns.
+ assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter)
+ assert !indexes.first.unique
+ assert_equal ["firm_id"], indexes.first.columns
+ else
+ warn "#{@connection.class} does not respond to #indexes"
+ end
- def test_current_database
- if @connection.respond_to?(:current_database)
- assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database
+ ensure
+ @connection.remove_index(:accounts, :name => idx_name) rescue nil
end
- end
- if current_adapter?(:MysqlAdapter)
- def test_charset
- assert_not_nil @connection.charset
- assert_not_equal 'character_set_database', @connection.charset
- assert_equal @connection.show_variable('character_set_database'), @connection.charset
+ def test_current_database
+ if @connection.respond_to?(:current_database)
+ assert_equal ARTest.connection_config['arunit']['database'], @connection.current_database
+ end
end
- def test_collation
- assert_not_nil @connection.collation
- assert_not_equal 'collation_database', @connection.collation
- assert_equal @connection.show_variable('collation_database'), @connection.collation
- end
+ if current_adapter?(:MysqlAdapter)
+ def test_charset
+ assert_not_nil @connection.charset
+ assert_not_equal 'character_set_database', @connection.charset
+ assert_equal @connection.show_variable('character_set_database'), @connection.charset
+ end
- def test_show_nonexistent_variable_returns_nil
- assert_nil @connection.show_variable('foo_bar_baz')
- end
+ def test_collation
+ assert_not_nil @connection.collation
+ assert_not_equal 'collation_database', @connection.collation
+ assert_equal @connection.show_variable('collation_database'), @connection.collation
+ end
- def test_not_specifying_database_name_for_cross_database_selects
- begin
- assert_nothing_raised do
- ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database))
+ def test_show_nonexistent_variable_returns_nil
+ assert_nil @connection.show_variable('foo_bar_baz')
+ end
- config = ARTest.connection_config
- ActiveRecord::Base.connection.execute(
- "SELECT #{config['arunit']['database']}.pirates.*, #{config['arunit2']['database']}.courses.* " \
- "FROM #{config['arunit']['database']}.pirates, #{config['arunit2']['database']}.courses"
- )
+ def test_not_specifying_database_name_for_cross_database_selects
+ begin
+ assert_nothing_raised do
+ ActiveRecord::Model.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database))
+
+ config = ARTest.connection_config
+ ActiveRecord::Model.connection.execute(
+ "SELECT #{config['arunit']['database']}.pirates.*, #{config['arunit2']['database']}.courses.* " \
+ "FROM #{config['arunit']['database']}.pirates, #{config['arunit2']['database']}.courses"
+ )
+ end
+ ensure
+ ActiveRecord::Model.establish_connection 'arunit'
end
- ensure
- ActiveRecord::Base.establish_connection 'arunit'
end
end
- end
- def test_table_alias
- def @connection.test_table_alias_length() 10; end
- class << @connection
- alias_method :old_table_alias_length, :table_alias_length
- alias_method :table_alias_length, :test_table_alias_length
- end
+ def test_table_alias
+ def @connection.test_table_alias_length() 10; end
+ class << @connection
+ alias_method :old_table_alias_length, :table_alias_length
+ alias_method :table_alias_length, :test_table_alias_length
+ end
- assert_equal 'posts', @connection.table_alias_for('posts')
- assert_equal 'posts_comm', @connection.table_alias_for('posts_comments')
- assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts')
+ assert_equal 'posts', @connection.table_alias_for('posts')
+ assert_equal 'posts_comm', @connection.table_alias_for('posts_comments')
+ assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts')
- class << @connection
- remove_method :table_alias_length
- alias_method :table_alias_length, :old_table_alias_length
+ class << @connection
+ remove_method :table_alias_length
+ alias_method :table_alias_length, :old_table_alias_length
+ end
end
- end
- # test resetting sequences in odd tables in postgreSQL
- if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
- require 'models/movie'
- require 'models/subscriber'
+ # test resetting sequences in odd tables in postgreSQL
+ if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
+ require 'models/movie'
+ require 'models/subscriber'
- def test_reset_empty_table_with_custom_pk
- Movie.delete_all
- Movie.connection.reset_pk_sequence! 'movies'
- assert_equal 1, Movie.create(:name => 'fight club').id
- end
+ def test_reset_empty_table_with_custom_pk
+ Movie.delete_all
+ Movie.connection.reset_pk_sequence! 'movies'
+ assert_equal 1, Movie.create(:name => 'fight club').id
+ end
- if ActiveRecord::Base.connection.adapter_name != "FrontBase"
- def test_reset_table_with_non_integer_pk
- Subscriber.delete_all
- Subscriber.connection.reset_pk_sequence! 'subscribers'
- sub = Subscriber.new(:name => 'robert drake')
- sub.id = 'bob drake'
- assert_nothing_raised { sub.save! }
+ if ActiveRecord::Base.connection.adapter_name != "FrontBase"
+ def test_reset_table_with_non_integer_pk
+ Subscriber.delete_all
+ Subscriber.connection.reset_pk_sequence! 'subscribers'
+ sub = Subscriber.new(:name => 'robert drake')
+ sub.id = 'bob drake'
+ assert_nothing_raised { sub.save! }
+ end
end
end
- end
- def test_uniqueness_violations_are_translated_to_specific_exception
- @connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
- assert_raises(ActiveRecord::RecordNotUnique) do
+ def test_uniqueness_violations_are_translated_to_specific_exception
@connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
+ assert_raises(ActiveRecord::RecordNotUnique) do
+ @connection.execute "INSERT INTO subscribers(nick) VALUES('me')"
+ end
end
- end
- def test_foreign_key_violations_are_translated_to_specific_exception
- unless @connection.adapter_name == 'SQLite'
- assert_raises(ActiveRecord::InvalidForeignKey) do
- # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
- if @connection.prefetch_primary_key?
- id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
- @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
- else
- @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ def test_foreign_key_violations_are_translated_to_specific_exception
+ unless @connection.adapter_name == 'SQLite'
+ assert_raises(ActiveRecord::InvalidForeignKey) do
+ # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
+ if @connection.prefetch_primary_key?
+ id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
+ @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
+ else
+ @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ end
end
end
end
- end
- def test_disable_referential_integrity
- assert_nothing_raised do
- @connection.disable_referential_integrity do
- # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
- if @connection.prefetch_primary_key?
- id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
- @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
- else
- @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ def test_disable_referential_integrity
+ assert_nothing_raised do
+ @connection.disable_referential_integrity do
+ # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
+ if @connection.prefetch_primary_key?
+ id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
+ @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
+ else
+ @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ end
+ # should deleted created record as otherwise disable_referential_integrity will try to enable contraints after executed block
+ # and will fail (at least on Oracle)
+ @connection.execute "DELETE FROM fk_test_has_fk"
end
- # should deleted created record as otherwise disable_referential_integrity will try to enable contraints after executed block
- # and will fail (at least on Oracle)
- @connection.execute "DELETE FROM fk_test_has_fk"
end
end
end
-
- def test_deprecated_visitor_for
- visitor_klass = Class.new(Arel::Visitors::ToSql)
- Arel::Visitors::VISITORS['fuuu'] = visitor_klass
- pool = stub(:spec => stub(:config => { :adapter => 'fuuu' }))
- visitor = assert_deprecated {
- ActiveRecord::ConnectionAdapters::AbstractAdapter.visitor_for(pool)
- }
- assert visitor.is_a?(visitor_klass)
- end
end
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index 2a89430da9..5e1c52c9ba 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -3,13 +3,13 @@ require "cases/helper"
class MysqlConnectionTest < ActiveRecord::TestCase
def setup
super
- @connection = ActiveRecord::Base.connection
+ @connection = ActiveRecord::Model.connection
end
def test_mysql_reconnect_attribute_after_connection_with_reconnect_true
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => true}))
- assert ActiveRecord::Base.connection.raw_connection.reconnect
+ ActiveRecord::Model.establish_connection(orig_connection.merge({:reconnect => true}))
+ assert ActiveRecord::Model.connection.raw_connection.reconnect
end
end
@@ -25,8 +25,8 @@ class MysqlConnectionTest < ActiveRecord::TestCase
def test_mysql_reconnect_attribute_after_connection_with_reconnect_false
run_without_connection do |orig_connection|
- ActiveRecord::Base.establish_connection(orig_connection.merge({:reconnect => false}))
- assert !ActiveRecord::Base.connection.raw_connection.reconnect
+ ActiveRecord::Model.establish_connection(orig_connection.merge({:reconnect => false}))
+ assert !ActiveRecord::Model.connection.raw_connection.reconnect
end
end
@@ -114,20 +114,33 @@ class MysqlConnectionTest < ActiveRecord::TestCase
# Test that MySQL allows multiple results for stored procedures
if defined?(Mysql) && Mysql.const_defined?(:CLIENT_MULTI_RESULTS)
def test_multi_results
- rows = ActiveRecord::Base.connection.select_rows('CALL ten();')
+ rows = ActiveRecord::Model.connection.select_rows('CALL ten();')
assert_equal 10, rows[0][0].to_i, "ten() did not return 10 as expected: #{rows.inspect}"
assert @connection.active?, "Bad connection use by 'MysqlAdapter.select_rows'"
end
end
+ def test_mysql_default_in_strict_mode
+ result = @connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [["STRICT_ALL_TABLES"]], result.rows
+ end
+
+ def test_mysql_strict_mode_disabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false}))
+ result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [['']], result.rows
+ end
+ end
+
private
def run_without_connection
- original_connection = ActiveRecord::Base.remove_connection
+ original_connection = ActiveRecord::Model.remove_connection
begin
yield original_connection
ensure
- ActiveRecord::Base.establish_connection(original_connection)
+ ActiveRecord::Model.establish_connection(original_connection)
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
index 146b77a95c..475a292f85 100644
--- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb
@@ -17,11 +17,7 @@ module ActiveRecord
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
+ assert_equal Encoding::UTF_8, @conn.client_encoding
end
def test_exec_insert_number
@@ -41,17 +37,55 @@ module ActiveRecord
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)
+ # 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
+ # The strings in this file are utf-8, so transcode to utf-8
+ value.encode!(Encoding::UTF_8)
assert_equal str, value
end
+ def test_tables_quoting
+ begin
+ @conn.tables(nil, "foo-bar", nil)
+ flunk
+ rescue => e
+ # assertion for *quoted* database properly
+ assert_match(/database 'foo-bar'/, e.inspect)
+ end
+ end
+
+ def test_pk_and_sequence_for
+ pk, seq = @conn.pk_and_sequence_for('ex')
+ assert_equal 'id', pk
+ assert_equal @conn.default_sequence_name('ex', 'id'), seq
+ end
+
+ def test_pk_and_sequence_for_with_non_standard_primary_key
+ @conn.exec_query('drop table if exists ex_with_non_standard_pk')
+ @conn.exec_query(<<-eosql)
+ CREATE TABLE `ex_with_non_standard_pk` (
+ `code` INT(11) DEFAULT NULL auto_increment,
+ PRIMARY KEY (`code`))
+ eosql
+ pk, seq = @conn.pk_and_sequence_for('ex_with_non_standard_pk')
+ assert_equal 'code', pk
+ assert_equal @conn.default_sequence_name('ex_with_non_standard_pk', 'code'), seq
+ end
+
+ def test_pk_and_sequence_for_with_custom_index_type_pk
+ @conn.exec_query('drop table if exists ex_with_custom_index_type_pk')
+ @conn.exec_query(<<-eosql)
+ CREATE TABLE `ex_with_custom_index_type_pk` (
+ `id` INT(11) DEFAULT NULL auto_increment,
+ PRIMARY KEY USING BTREE (`id`))
+ eosql
+ pk, seq = @conn.pk_and_sequence_for('ex_with_custom_index_type_pk')
+ assert_equal 'id', pk
+ assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq
+ end
+
private
def insert(ctx, data)
binds = data.map { |name, value|
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 292c7efebb..6faceaf7c0 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -130,7 +130,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
end
def test_associations_work_with_reserved_words
- assert_nothing_raised { Select.find(:all, :include => [:groups]) }
+ assert_nothing_raised { Select.scoped(:includes => [:groups]).all }
end
#the following functions were added to DRY test cases
diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb
index a2155d1dd1..d94bb629a7 100644
--- a/activerecord/test/cases/adapters/mysql/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/schema_test.rb
@@ -13,14 +13,18 @@ module ActiveRecord
table = Post.table_name
@db_name = db
- @omgpost = Class.new(Post) do
- set_table_name "#{db}.#{table}"
+ @omgpost = Class.new(ActiveRecord::Base) do
+ self.table_name = "#{db}.#{table}"
def self.name; 'Post'; end
end
end
def test_schema
- assert @omgpost.find(:first)
+ assert @omgpost.first
+ end
+
+ def test_primary_key
+ assert_equal 'id', @omgpost.primary_key
end
def test_table_exists?
diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
index cd9c1041dc..5e8065d80d 100644
--- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb
@@ -9,7 +9,7 @@ module ActiveRecord
def test_update_question_marks
str = "foo?bar"
- x = Topic.find :first
+ x = Topic.first
x.title = str
x.content = str
x.save!
@@ -28,7 +28,7 @@ module ActiveRecord
def test_update_null_bytes
str = "foo\0bar"
- x = Topic.find :first
+ x = Topic.first
x.title = str
x.content = str
x.save!
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 26091c713b..684c7f5929 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -3,7 +3,7 @@ require "cases/helper"
class MysqlConnectionTest < ActiveRecord::TestCase
def setup
super
- @connection = ActiveRecord::Base.connection
+ @connection = ActiveRecord::Model.connection
end
def test_no_automatic_reconnection_after_timeout
@@ -29,14 +29,30 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert @connection.active?
end
+ # TODO: Below is a straight up copy/paste from mysql/connection_test.rb
+ # I'm not sure what the correct way is to share these tests between
+ # adapters in minitest.
+ def test_mysql_default_in_strict_mode
+ result = @connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [["STRICT_ALL_TABLES"]], result.rows
+ end
+
+ def test_mysql_strict_mode_disabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false}))
+ result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [['']], result.rows
+ end
+ end
+
private
def run_without_connection
- original_connection = ActiveRecord::Base.remove_connection
+ original_connection = ActiveRecord::Model.remove_connection
begin
yield original_connection
ensure
- ActiveRecord::Base.establish_connection(original_connection)
+ ActiveRecord::Model.establish_connection(original_connection)
end
end
end
diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb
new file mode 100644
index 0000000000..68ed361aeb
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb
@@ -0,0 +1,26 @@
+require "cases/helper"
+require 'models/developer'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class Mysql2Adapter
+ class ExplainTest < ActiveRecord::TestCase
+ fixtures :developers
+
+ def test_explain_for_one_query
+ explain = Developer.where(:id => 1).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %(developers | const), explain
+ end
+
+ def test_explain_with_eager_loading
+ explain = Developer.where(:id => 1).includes(:audit_logs).explain
+ assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain
+ assert_match %(developers | const), explain
+ assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` IN (1)), explain
+ assert_match %(audit_logs | ALL), explain
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
index 3a9744e78f..32d4282623 100644
--- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -130,7 +130,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
end
def test_associations_work_with_reserved_words
- assert_nothing_raised { Select.find(:all, :include => [:groups]) }
+ assert_nothing_raised { Select.scoped(:includes => [:groups]).all }
end
#the following functions were added to DRY test cases
diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb
index 858d1da2dd..2c0ed73c92 100644
--- a/activerecord/test/cases/adapters/mysql2/schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb
@@ -13,14 +13,18 @@ module ActiveRecord
table = Post.table_name
@db_name = db
- @omgpost = Class.new(Post) do
- set_table_name "#{db}.#{table}"
+ @omgpost = Class.new(ActiveRecord::Base) do
+ self.table_name = "#{db}.#{table}"
def self.name; 'Post'; end
end
end
def test_schema
- assert @omgpost.find(:first)
+ assert @omgpost.first
+ end
+
+ def test_primary_key
+ assert_equal 'id', @omgpost.primary_key
end
def test_table_exists?
@@ -31,6 +35,17 @@ module ActiveRecord
def test_table_exists_wrong_schema
assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
end
+
+ def test_tables_quoting
+ begin
+ @connection.tables(nil, "foo-bar", nil)
+ flunk
+ rescue => e
+ # assertion for *quoted* database properly
+ assert_match(/database 'foo-bar'/, e.inspect)
+ end
+ end
+
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index e4746d4aa3..447d729e52 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -21,6 +21,18 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1)
end
+ def test_add_index
+ # add_index calls index_name_exists? which can't work since execute is stubbed
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) do |*|
+ false
+ end
+
+ expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
+ assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'")
+
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?)
+ end
+
private
def method_missing(method_symbol, *arguments)
ActiveRecord::Base.connection.send(method_symbol, *arguments)
diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb
index 21b97b3b39..4baec749ff 100644
--- a/activerecord/test/cases/adapters/postgresql/connection_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb
@@ -2,6 +2,9 @@ require "cases/helper"
module ActiveRecord
class PostgresqlConnectionTest < ActiveRecord::TestCase
+ class NonExistentTable < ActiveRecord::Base
+ end
+
def setup
super
@connection = ActiveRecord::Base.connection
@@ -10,5 +13,17 @@ module ActiveRecord
def test_encoding
assert_not_nil @connection.encoding
end
+
+ # Ensure, we can set connection params using the example of Generic
+ # Query Optimizer (geqo). It is 'on' per default.
+ def test_connection_options
+ params = ActiveRecord::Base.connection_config.dup
+ params[:options] = "-c geqo=off"
+ NonExistentTable.establish_connection(params)
+
+ # Verify the connection param has been applied.
+ expect = NonExistentTable.connection.query('show geqo').first.first
+ assert_equal 'off', expect
+ end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index ce08e4c6a7..34660577da 100644
--- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -86,9 +86,9 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
end
def test_data_type_of_network_address_types
- assert_equal :string, @first_network_address.column_for_attribute(:cidr_address).type
- assert_equal :string, @first_network_address.column_for_attribute(:inet_address).type
- assert_equal :string, @first_network_address.column_for_attribute(:mac_address).type
+ assert_equal :cidr, @first_network_address.column_for_attribute(:cidr_address).type
+ assert_equal :inet, @first_network_address.column_for_attribute(:inet_address).type
+ assert_equal :macaddr, @first_network_address.column_for_attribute(:mac_address).type
end
def test_data_type_of_bit_string_types
@@ -134,9 +134,12 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
assert_equal '-1 years -2 days', @first_time.time_interval
end
- def test_network_address_values
- assert_equal '192.168.0.0/24', @first_network_address.cidr_address
- assert_equal '172.16.1.254', @first_network_address.inet_address
+ def test_network_address_values_ipaddr
+ cidr_address = IPAddr.new '192.168.0.0/24'
+ inet_address = IPAddr.new '172.16.1.254'
+
+ assert_equal cidr_address, @first_network_address.cidr_address
+ assert_equal inet_address, @first_network_address.inet_address
assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address
end
@@ -200,8 +203,8 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
end
def test_update_network_address
- new_cidr_address = '10.1.2.3/32'
- new_inet_address = '10.0.0.0/8'
+ new_inet_address = '10.1.2.3/32'
+ new_cidr_address = '10.0.0.0/8'
new_mac_address = 'bc:de:f0:12:34:56'
assert @first_network_address.cidr_address = new_cidr_address
assert @first_network_address.inet_address = new_inet_address
diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb
new file mode 100644
index 0000000000..619d581d5f
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb
@@ -0,0 +1,35 @@
+require "cases/helper"
+require 'models/developer'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapter
+ class ExplainTest < ActiveRecord::TestCase
+ fixtures :developers
+
+ def test_explain_for_one_query
+ explain = Developer.where(:id => 1).explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match %(QUERY PLAN), explain
+ assert_match %(Index Scan using developers_pkey on developers), explain
+ end
+
+ def test_explain_with_eager_loading
+ explain = Developer.where(:id => 1).includes(:audit_logs).explain
+ assert_match %(QUERY PLAN), explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match %(Index Scan using developers_pkey on developers), explain
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
+ assert_match %(Seq Scan on audit_logs), explain
+ end
+
+ def test_dont_explain_for_set_search_path
+ queries = Thread.current[:available_queries_for_explain] = []
+ ActiveRecord::Base.connection.schema_search_path = "public"
+ assert queries.empty?
+ end
+
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
new file mode 100644
index 0000000000..23bafde17b
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb
@@ -0,0 +1,157 @@
+# encoding: utf-8
+
+require "cases/helper"
+require 'active_record/base'
+require 'active_record/connection_adapters/postgresql_adapter'
+
+class PostgresqlHstoreTest < ActiveRecord::TestCase
+ class Hstore < ActiveRecord::Base
+ self.table_name = 'hstores'
+ end
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ begin
+ @connection.transaction do
+ @connection.create_table('hstores') do |t|
+ t.hstore 'tags', :default => ''
+ end
+ end
+ rescue ActiveRecord::StatementInvalid
+ return skip "do not test on PG without hstore"
+ end
+ @column = Hstore.columns.find { |c| c.name == 'tags' }
+ end
+
+ def teardown
+ @connection.execute 'drop table if exists hstores'
+ end
+
+ def test_column
+ assert_equal :hstore, @column.type
+ end
+
+ def test_type_cast_hstore
+ assert @column
+
+ data = "\"1\"=>\"2\""
+ hash = @column.class.string_to_hstore data
+ assert_equal({'1' => '2'}, hash)
+ assert_equal({'1' => '2'}, @column.type_cast(data))
+
+ assert_equal({}, @column.type_cast(""))
+ assert_equal({'key'=>nil}, @column.type_cast('key => NULL'))
+ assert_equal({'c'=>'}','"a"'=>'b "a b'}, @column.type_cast(%q(c=>"}", "\"a\""=>"b \"a b")))
+ end
+
+ def test_gen1
+ assert_equal(%q(" "=>""), @column.class.hstore_to_string({' '=>''}))
+ end
+
+ def test_gen2
+ assert_equal(%q(","=>""), @column.class.hstore_to_string({','=>''}))
+ end
+
+ def test_gen3
+ assert_equal(%q("="=>""), @column.class.hstore_to_string({'='=>''}))
+ end
+
+ def test_gen4
+ assert_equal(%q(">"=>""), @column.class.hstore_to_string({'>'=>''}))
+ end
+
+ def test_parse1
+ assert_equal({'a'=>nil,'b'=>nil,'c'=>'NuLl','null'=>'c'}, @column.type_cast('a=>null,b=>NuLl,c=>"NuLl",null=>c'))
+ end
+
+ def test_parse2
+ assert_equal({" " => " "}, @column.type_cast("\\ =>\\ "))
+ end
+
+ def test_parse3
+ assert_equal({"=" => ">"}, @column.type_cast("==>>"))
+ end
+
+ def test_parse4
+ assert_equal({"=a"=>"q=w"}, @column.type_cast('\=a=>q=w'))
+ end
+
+ def test_parse5
+ assert_equal({"=a"=>"q=w"}, @column.type_cast('"=a"=>q\=w'))
+ end
+
+ def test_parse6
+ assert_equal({"\"a"=>"q>w"}, @column.type_cast('"\"a"=>q>w'))
+ end
+
+ def test_parse7
+ assert_equal({"\"a"=>"q\"w"}, @column.type_cast('\"a=>q"w'))
+ end
+
+ def test_rewrite
+ @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
+ x = Hstore.first
+ x.tags = { '"a\'' => 'b' }
+ assert x.save!
+ end
+
+
+ def test_select
+ @connection.execute "insert into hstores (tags) VALUES ('1=>2')"
+ x = Hstore.first
+ assert_equal({'1' => '2'}, x.tags)
+ end
+
+ def test_select_multikey
+ @connection.execute "insert into hstores (tags) VALUES ('1=>2,2=>3')"
+ x = Hstore.first
+ assert_equal({'1' => '2', '2' => '3'}, x.tags)
+ end
+
+ def test_create
+ assert_cycle('a' => 'b', '1' => '2')
+ end
+
+ def test_nil
+ assert_cycle('a' => nil)
+ end
+
+ def test_quotes
+ assert_cycle('a' => 'b"ar', '1"foo' => '2')
+ end
+
+ def test_whitespace
+ assert_cycle('a b' => 'b ar', '1"foo' => '2')
+ end
+
+ def test_backslash
+ assert_cycle('a\\b' => 'b\\ar', '1"foo' => '2')
+ end
+
+ def test_comma
+ assert_cycle('a, b' => 'bar', '1"foo' => '2')
+ end
+
+ def test_arrow
+ assert_cycle('a=>b' => 'bar', '1"foo' => '2')
+ end
+
+ def test_quoting_special_characters
+ assert_cycle('ca' => 'cà', 'ac' => 'àc')
+ end
+
+ private
+ def assert_cycle hash
+ # test creation
+ x = Hstore.create!(:tags => hash)
+ x.reload
+ assert_equal(hash, x.tags)
+
+ # test updating
+ x = Hstore.create!(:tags => {})
+ x.tags = hash
+ x.save!
+ x.reload
+ assert_equal(hash, x.tags)
+ 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 d57794daf8..92e31a3e44 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -49,6 +49,33 @@ module ActiveRecord
assert_equal expect, id
end
+ def test_insert_sql_with_returning_disabled
+ connection = connection_without_insert_returning
+ id = connection.insert_sql("insert into postgresql_partitioned_table_parent (number) VALUES (1)")
+ expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
+ assert_equal expect, id
+ end
+
+ def test_exec_insert_with_returning_disabled
+ connection = connection_without_insert_returning
+ result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id', 'postgresql_partitioned_table_parent_id_seq')
+ expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
+ assert_equal expect, result.rows.first.first
+ end
+
+ def test_exec_insert_with_returning_disabled_and_no_sequence_name_given
+ connection = connection_without_insert_returning
+ result = connection.exec_insert("insert into postgresql_partitioned_table_parent (number) VALUES (1)", nil, [], 'id')
+ expect = connection.query('select max(id) from postgresql_partitioned_table_parent').first.first
+ assert_equal expect, result.rows.first.first
+ end
+
+ def test_sql_for_insert_with_returning_disabled
+ connection = connection_without_insert_returning
+ result = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
+ assert_equal ['sql', 'binds'], result
+ end
+
def test_serial_sequence
assert_equal 'public.accounts_id_seq',
@connection.serial_sequence('accounts', 'id')
@@ -179,6 +206,17 @@ module ActiveRecord
assert_equal Arel.sql('$2'), bind
end
+ def test_partial_index
+ @connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
+ index = @connection.indexes('ex').find { |idx| idx.name == 'partial' }
+ assert_equal "(number > 100)", index.where
+ end
+
+ def test_distinct_with_nulls
+ assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls first"])
+ assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"])
+ end
+
private
def insert(ctx, data)
binds = data.map { |name, value|
@@ -193,6 +231,10 @@ module ActiveRecord
ctx.exec_insert(sql, 'SQL', binds)
end
+
+ def connection_without_insert_returning
+ ActiveRecord::Base.postgresql_connection(ActiveRecord::Base.configurations['arunit'].merge(:insert_returning => false))
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
index 172055f15c..f8a605b67c 100644
--- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb
@@ -19,6 +19,18 @@ module ActiveRecord
assert_equal 'f', @conn.type_cast(false, nil)
assert_equal 'f', @conn.type_cast(false, c)
end
+
+ def test_quote_float_nan
+ nan = 0.0/0
+ c = Column.new(nil, 1, 'float')
+ assert_equal "'NaN'", @conn.quote(nan, c)
+ end
+
+ def test_quote_float_infinity
+ infinity = 1.0/0
+ c = Column.new(nil, 1, 'float')
+ assert_equal "'Infinity'", @conn.quote(infinity, c)
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index b01eabc840..9208f53997 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -10,36 +10,41 @@ class SchemaTest < ActiveRecord::TestCase
INDEX_A_NAME = 'a_index_things_on_name'
INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema'
INDEX_C_NAME = 'c_index_full_text_search'
+ INDEX_D_NAME = 'd_index_things_on_description_desc'
INDEX_A_COLUMN = 'name'
INDEX_B_COLUMN_S1 = 'email'
INDEX_B_COLUMN_S2 = 'moment'
INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))}
+ INDEX_D_COLUMN = 'description'
COLUMNS = [
'id integer',
'name character varying(50)',
'email character varying(50)',
+ 'description character varying(100)',
'moment timestamp without time zone default now()'
]
PK_TABLE_NAME = 'table_with_pk'
+ UNMATCHED_SEQUENCE_NAME = 'unmatched_primary_key_default_value_seq'
+ UNMATCHED_PK_TABLE_NAME = 'table_with_unmatched_sequence_for_pk'
class Thing1 < ActiveRecord::Base
- set_table_name "test_schema.things"
+ self.table_name = "test_schema.things"
end
class Thing2 < ActiveRecord::Base
- set_table_name "test_schema2.things"
+ self.table_name = "test_schema2.things"
end
class Thing3 < ActiveRecord::Base
- set_table_name 'test_schema."things.table"'
+ self.table_name = 'test_schema."things.table"'
end
class Thing4 < ActiveRecord::Base
- set_table_name 'test_schema."Things"'
+ self.table_name = 'test_schema."Things"'
end
class Thing5 < ActiveRecord::Base
- set_table_name 'things'
+ self.table_name = 'things'
end
def setup
@@ -54,7 +59,11 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S2});"
@connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});"
@connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});"
+ @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
+ @connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
+ @connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
+ @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
end
def teardown
@@ -62,6 +71,57 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
end
+ def test_schema_names
+ assert_equal ["public", "test_schema", "test_schema2"], @connection.schema_names
+ end
+
+ def test_create_schema
+ begin
+ @connection.create_schema "test_schema3"
+ assert @connection.schema_names.include? "test_schema3"
+ ensure
+ @connection.drop_schema "test_schema3"
+ end
+ end
+
+ def test_raise_create_schema_with_existing_schema
+ begin
+ @connection.create_schema "test_schema3"
+ assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.create_schema "test_schema3"
+ end
+ ensure
+ @connection.drop_schema "test_schema3"
+ end
+ end
+
+ def test_drop_schema
+ begin
+ @connection.create_schema "test_schema3"
+ ensure
+ @connection.drop_schema "test_schema3"
+ end
+ assert !@connection.schema_names.include?("test_schema3")
+ end
+
+ def test_raise_drop_schema_with_nonexisting_schema
+ assert_raises(ActiveRecord::StatementInvalid) do
+ @connection.drop_schema "test_schema3"
+ end
+ end
+
+ def test_schema_change_with_prepared_stmt
+ altered = false
+ @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
+ @connection.exec_query "alter table developers add column zomg int", 'sql', []
+ altered = true
+ @connection.exec_query "select * from developers where id = $1", 'sql', [[nil, 1]]
+ ensure
+ # We are not using DROP COLUMN IF EXISTS because that syntax is only
+ # supported by pg 9.X
+ @connection.exec_query("alter table developers drop column zomg", 'sql', []) if altered
+ end
+
def test_table_exists?
[Thing1, Thing2, Thing3, Thing4].each do |klass|
name = klass.table_name
@@ -162,13 +222,13 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_raise_on_unquoted_schema_name
- assert_raise(ActiveRecord::StatementInvalid) do
+ assert_raises(ActiveRecord::StatementInvalid) do
with_schema_search_path '$user,public'
end
end
def test_without_schema_search_path
- assert_raise(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) }
+ assert_raises(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) }
end
def test_ignore_nil_schema_search_path
@@ -176,11 +236,15 @@ class SchemaTest < ActiveRecord::TestCase
end
def test_dump_indexes_for_schema_one
- do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1)
+ do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
end
def test_dump_indexes_for_schema_two
- do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2)
+ do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN)
+ end
+
+ def test_dump_indexes_for_schema_multiple_schemas_in_search_path
+ do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
end
def test_with_uppercase_index_name
@@ -220,12 +284,12 @@ class SchemaTest < ActiveRecord::TestCase
def test_pk_and_sequence_for_with_schema_specified
[
%("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"),
- %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"),
- %(#{SCHEMA_NAME}.#{PK_TABLE_NAME})
+ %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
].each do |given|
pk, seq = @connection.pk_and_sequence_for(given)
assert_equal 'id', pk, "primary key should be found when table referenced as #{given}"
- assert_equal "#{SCHEMA_NAME}.#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}"
+ assert_equal "#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}")
+ assert_equal "#{UNMATCHED_SEQUENCE_NAME}", seq, "sequence name should be found when table referenced as #{given}" if given == %("#{SCHEMA_NAME}"."#{UNMATCHED_PK_TABLE_NAME}")
end
end
@@ -280,13 +344,16 @@ class SchemaTest < ActiveRecord::TestCase
@connection.schema_search_path = "'$user', public"
end
- def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name)
+ def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name)
with_schema_search_path(this_schema_name) do
indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name}
- assert_equal 2,indexes.size
+ assert_equal 3,indexes.size
do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name)
+ do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name)
+
+ assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN]
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
index a82c6f67d6..f1c4b85126 100644
--- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb
@@ -2,6 +2,16 @@ require 'cases/helper'
module ActiveRecord::ConnectionAdapters
class PostgreSQLAdapter < AbstractAdapter
+ class InactivePGconn
+ def query(*args)
+ raise PGError
+ end
+
+ def status
+ PGconn::CONNECTION_BAD
+ end
+ end
+
class StatementPoolTest < ActiveRecord::TestCase
def test_cache_is_per_pid
return skip('must support fork') unless Process.respond_to?(:fork)
@@ -18,6 +28,12 @@ module ActiveRecord::ConnectionAdapters
Process.waitpid pid
assert $?.success?, 'process should exit successfully'
end
+
+ def test_dealloc_does_not_raise_on_inactive_connection
+ cache = StatementPool.new InactivePGconn.new, 10
+ cache['foo'] = 'bar'
+ assert_nothing_raised { cache.clear }
+ end
end
end
end
diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb
index 5f08f79171..9e7b08ef34 100644
--- a/activerecord/test/cases/adapters/postgresql/utils_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb
@@ -1,3 +1,5 @@
+require 'cases/helper'
+
class PostgreSQLUtilsTest < ActiveSupport::TestCase
include ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils
diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb
index 303ba9245a..66e07b71a0 100644
--- a/activerecord/test/cases/adapters/postgresql/view_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/view_test.rb
@@ -14,7 +14,7 @@ class ViewTest < ActiveRecord::TestCase
]
class ThingView < ActiveRecord::Base
- set_table_name 'test_schema.view_things'
+ self.table_name = 'test_schema.view_things'
end
def setup
diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index 575b4806c1..7eef4ace81 100644
--- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
@@ -57,6 +57,14 @@ class CopyTableTest < ActiveRecord::TestCase
end
end
+ def test_copy_table_with_unconventional_primary_key
+ test_copy_table('owners', 'owners_unconventional') do |from, to, options|
+ original_pk = @connection.primary_key('owners')
+ copied_pk = @connection.primary_key('owners_unconventional')
+ assert_equal original_pk, copied_pk
+ end
+ end
+
protected
def copy_table(from, to, options = {})
@connection.copy_table(from, to, {:temporary => true}.merge(options))
diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
new file mode 100644
index 0000000000..b227bce680
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb
@@ -0,0 +1,26 @@
+require "cases/helper"
+require 'models/developer'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class SQLite3Adapter
+ class ExplainTest < ActiveRecord::TestCase
+ fixtures :developers
+
+ def test_explain_for_one_query
+ explain = Developer.where(:id => 1).explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ end
+
+ def test_explain_with_eager_loading
+ explain = Developer.where(:id => 1).includes(:audit_logs).explain
+ assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = 1), explain
+ assert_match(/(SEARCH )?TABLE developers USING (INTEGER )?PRIMARY KEY/, explain)
+ assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" IN (1)), explain
+ assert_match(/(SCAN )?TABLE audit_logs/, explain)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
index e0152e7ccf..2ba9143cd5 100644
--- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb
@@ -1,10 +1,11 @@
require "cases/helper"
require 'bigdecimal'
require 'yaml'
+require 'securerandom'
module ActiveRecord
module ConnectionAdapters
- class SQLiteAdapter
+ class SQLite3Adapter
class QuotingTest < ActiveRecord::TestCase
def setup
@conn = Base.sqlite3_connection :database => ':memory:',
@@ -12,6 +13,14 @@ module ActiveRecord
:timeout => 100
end
+ def test_type_cast_binary_encoding_without_logger
+ @conn.extend(Module.new { def logger; end })
+ column = Struct.new(:type, :name).new(:string, "foo")
+ binary = SecureRandom.hex
+ expected = binary.dup.encode!('utf-8')
+ assert_equal expected, @conn.type_cast(binary, column)
+ end
+
def test_type_cast_symbol
assert_equal 'foo', @conn.type_cast(:foo, nil)
end
@@ -70,9 +79,9 @@ module ActiveRecord
assert_equal bd.to_f, @conn.type_cast(bd, nil)
end
- def test_type_cast_unknown
+ def test_type_cast_unknown_should_raise_error
obj = Class.new.new
- assert_equal YAML.dump(obj), @conn.type_cast(obj, nil)
+ assert_raise(TypeError) { @conn.type_cast(obj, nil) }
end
def test_quoted_id
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index eb6f071dc1..8a7f44d0a3 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -23,8 +23,6 @@ module ActiveRecord
end
def test_column_types
- return skip('only test encoding on 1.9') unless "<3".encoding_aware?
-
owner = Owner.create!(:name => "hello".encode('ascii-8bit'))
owner.reload
select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', '
@@ -37,6 +35,11 @@ module ActiveRecord
assert(!result.rows.first.include?("blob"), "should not store blobs")
end
+ def test_time_column
+ owner = Owner.create!(:eats_at => Time.utc(1995,1,1,6,0))
+ assert_match(/1995-01-01/, owner.reload.eats_at.to_s)
+ end
+
def test_exec_insert
column = @conn.columns('items').find { |col| col.name == 'number' }
vals = [[column, 10]]
@@ -60,18 +63,6 @@ module ActiveRecord
end
end
- def test_connection_no_adapter
- assert_raises(ArgumentError) do
- Base.sqlite3_connection :database => ':memory:'
- end
- end
-
- def test_connection_wrong_adapter
- assert_raises(ArgumentError) do
- Base.sqlite3_connection :database => ':memory:',:adapter => 'vuvuzela'
- end
- end
-
def test_bad_timeout
assert_raises(TypeError) do
Base.sqlite3_connection :database => ':memory:',
@@ -144,8 +135,6 @@ module ActiveRecord
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,
@@ -158,6 +147,7 @@ module ActiveRecord
binary.save!
assert_equal str, binary.data
+ ensure
DualEncoding.connection.drop_table('dual_encodings')
end
diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
index ae272e2c4b..2f04c60a9a 100644
--- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
module ActiveRecord::ConnectionAdapters
- class SQLiteAdapter
+ class SQLite3Adapter
class StatementPoolTest < ActiveRecord::TestCase
def test_cache_is_per_pid
return skip('must support fork') unless Process.respond_to?(:fork)
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 588adc38e3..b2eac0349b 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -37,6 +37,13 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
end
+
+ def test_schema_subclass
+ Class.new(ActiveRecord::Schema).define(:version => 9) do
+ create_table :fruits
+ end
+ assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
+ end
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 1160d236c9..2c7a240915 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -73,14 +73,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_eager_loading_with_primary_key
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
- citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key)
+ citibank_result = Client.scoped(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key).first
assert citibank_result.association_cache.key?(:firm_with_primary_key)
end
def test_eager_loading_with_primary_key_as_symbol
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
- citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key_symbols)
+ citibank_result = Client.scoped(:where => {:name => "Citibank"}, :includes => :firm_with_primary_key_symbols).first
assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols)
end
@@ -168,6 +168,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
sponsor.sponsorable = Member.new :name => "Bert"
assert_equal Member, sponsor.association(:sponsorable).send(:klass)
+ assert_equal "members", sponsor.association(:sponsorable).aliased_table_name
end
def test_with_polymorphic_and_condition
@@ -181,7 +182,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_with_select
assert_equal Company.find(2).firm_with_select.attributes.size, 1
- assert_equal Company.find(2, :include => :firm_with_select ).firm_with_select.attributes.size, 1
+ assert_equal Company.scoped(:includes => :firm_with_select ).find(2).firm_with_select.attributes.size, 1
end
def test_belongs_to_counter
@@ -333,7 +334,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_new_record_with_foreign_key_but_no_object
c = Client.new("firm_id" => 1)
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id
+ assert_equal Firm.scoped(:order => "id").first, c.firm_with_basic_id
end
def test_setting_foreign_key_after_nil_target_loaded
@@ -393,9 +394,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
end
def test_association_assignment_sticks
- post = Post.find(:first)
+ post = Post.first
- author1, author2 = Author.find(:all, :limit => 2)
+ author1, author2 = Author.scoped(:limit => 2).all
assert_not_nil author1
assert_not_nil author2
@@ -497,14 +498,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised do
Account.find(@account.id).save!
- Account.find(@account.id, :include => :firm).save!
+ Account.scoped(:includes => :firm).find(@account.id).save!
end
@account.firm.delete
assert_nothing_raised do
Account.find(@account.id).save!
- Account.find(@account.id, :include => :firm).save!
+ Account.scoped(:includes => :firm).find(@account.id).save!
end
end
@@ -517,7 +518,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
authors(:david).destroy
end
- assert_equal [], AuthorAddress.find_all_by_id([author_address.id, author_address_extra.id])
+ assert_equal [], AuthorAddress.where(id: [author_address.id, author_address_extra.id])
assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids
end
@@ -704,4 +705,27 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal toy, sponsor.reload.sponsorable
end
+
+ test "stale tracking doesn't care about the type" do
+ apple = Firm.create("name" => "Apple")
+ citibank = Account.create("credit_limit" => 10)
+
+ citibank.firm_id = apple.id
+ citibank.firm # load it
+
+ citibank.firm_id = apple.id.to_s
+
+ assert !citibank.association(:firm).stale_target?
+ end
+
+ def test_reflect_the_most_recent_change
+ author1, author2 = Author.limit(2)
+ post = Post.new(:title => "foo", :body=> "bar")
+
+ post.author = author1
+ post.author_id = author2.id
+
+ assert post.save
+ assert_equal post.author_id, author2.id
+ end
end
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index ff376a68d8..01f7f18397 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -16,7 +16,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
:categorizations, :people, :categories, :edges, :vertices
def test_eager_association_loading_with_cascaded_two_levels
- authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
+ authors = Author.scoped(:includes=>{:posts=>:comments}, :order=>"authors.id").all
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
@@ -24,7 +24,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_cascaded_two_levels_and_one_level
- authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id")
+ authors = Author.scoped(:includes=>[{:posts=>:comments}, :categorizations], :order=>"authors.id").all
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
@@ -61,7 +61,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_cascaded_eager_association_loading_with_duplicated_includes
- categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null")
+ categories = Category.includes(:categorizations).includes(:categorizations => :author).where("categorizations.id is not null").references(:categorizations)
assert_nothing_raised do
assert_equal 3, categories.count
assert_equal 3, categories.all.size
@@ -69,7 +69,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_cascaded_eager_association_loading_with_twice_includes_edge_cases
- categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null")
+ categories = Category.includes(:categorizations => :author).includes(:categorizations => :post).where("posts.id is not null").references(:posts)
assert_nothing_raised do
assert_equal 3, categories.count
assert_equal 3, categories.all.size
@@ -84,7 +84,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
- authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
+ authors = Author.scoped(:includes=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id").all
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal 3, authors[1].posts.size
@@ -92,7 +92,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference
- authors = Author.find(:all, :include=>{:posts=>[:comments, :author]}, :order=>"authors.id")
+ authors = Author.scoped(:includes=>{:posts=>[:comments, :author]}, :order=>"authors.id").all
assert_equal 3, authors.size
assert_equal 5, authors[0].posts.size
assert_equal authors(:david).name, authors[0].name
@@ -100,13 +100,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_cascaded_two_levels_with_condition
- authors = Author.find(:all, :include=>{:posts=>:comments}, :conditions=>"authors.id=1", :order=>"authors.id")
+ authors = Author.scoped(:includes=>{:posts=>:comments}, :where=>"authors.id=1", :order=>"authors.id").all
assert_equal 1, authors.size
assert_equal 5, authors[0].posts.size
end
def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong
- firms = Firm.find(:all, :include=>{:account=>{:firm=>:account}}, :order=>"companies.id")
+ firms = Firm.scoped(:includes=>{:account=>{:firm=>:account}}, :order=>"companies.id").all
assert_equal 2, firms.size
assert_equal firms.first.account, firms.first.account.firm.account
assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account }
@@ -114,7 +114,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_has_many_sti
- topics = Topic.find(:all, :include => :replies, :order => 'topics.id')
+ topics = Topic.scoped(:includes => :replies, :order => 'topics.id').all
first, second, = topics(:first).replies.size, topics(:second).replies.size
assert_no_queries do
assert_equal first, topics[0].replies.size
@@ -127,7 +127,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
silly.parent_id = 1
assert silly.save
- topics = Topic.find(:all, :include => :replies, :order => 'topics.id, replies_topics.id')
+ topics = Topic.scoped(:includes => :replies, :order => ['topics.id', 'replies_topics.id']).all
assert_no_queries do
assert_equal 2, topics[0].replies.size
assert_equal 0, topics[1].replies.size
@@ -135,14 +135,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_belongs_to_sti
- replies = Reply.find(:all, :include => :topic, :order => 'topics.id')
+ replies = Reply.scoped(:includes => :topic, :order => 'topics.id').all
assert replies.include?(topics(:second))
assert !replies.include?(topics(:first))
assert_equal topics(:first), assert_no_queries { replies.first.topic }
end
def test_eager_association_loading_with_multiple_stis_and_order
- author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :conditions => 'posts.id = 4')
+ author = Author.scoped(:includes => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :where => 'posts.id = 4').first
assert_equal authors(:david), author
assert_no_queries do
author.posts.first.special_comments
@@ -151,7 +151,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_of_stis_with_multiple_references
- authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
+ authors = Author.scoped(:includes => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :where => 'posts.id = 4').all
assert_equal [authors(:david)], authors
assert_no_queries do
authors.first.posts.first.special_comments.first.post.special_comments
@@ -160,7 +160,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_where_first_level_returns_nil
- authors = Author.find(:all, :include => {:post_about_thinking => :comments}, :order => 'authors.id DESC')
+ authors = Author.scoped(:includes => {:post_about_thinking => :comments}, :order => 'authors.id DESC').all
assert_equal [authors(:bob), authors(:mary), authors(:david)], authors
assert_no_queries do
authors[2].post_about_thinking.comments.first
@@ -168,12 +168,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through
- source = Vertex.find(:first, :include=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id')
+ source = Vertex.scoped(:includes=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id').first
assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first }
end
def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many
- sink = Vertex.find(:first, :include=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC')
+ sink = Vertex.scoped(:includes=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC').first
assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first }
end
end
diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
index d75791cab9..75a6295350 100644
--- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
+++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb
@@ -4,7 +4,7 @@ require 'models/tagging'
module Namespaced
class Post < ActiveRecord::Base
- set_table_name 'posts'
+ self.table_name = 'posts'
has_one :tagging, :as => :taggable, :class_name => 'Tagging'
end
end
@@ -24,12 +24,11 @@ class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = false
- post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
+ post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff')
assert_nil post.tagging
- ActiveRecord::IdentityMap.clear
ActiveRecord::Base.store_full_sti_class = true
- post = Namespaced::Post.find_by_title( 'Great stuff', :include => :tagging )
+ post = Namespaced::Post.includes(:tagging).find_by_title('Great stuff')
assert_instance_of Tagging, post.tagging
ensure
ActiveRecord::Base.store_full_sti_class = old
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index 2cf9f89c3c..bb0d6bc70b 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -6,7 +6,6 @@ require 'models/comment'
require 'models/category'
require 'models/categorization'
require 'models/tagging'
-require 'active_support/core_ext/array/random_access'
module Remembered
extend ActiveSupport::Concern
@@ -93,8 +92,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
end
def test_include_query
- res = 0
- res = ShapeExpression.find :all, :include => [ :shape, { :paint => :non_poly } ]
+ res = ShapeExpression.scoped(:includes => [ :shape, { :paint => :non_poly } ]).all
assert_equal NUM_SHAPE_EXPRESSIONS, res.size
assert_queries(0) do
res.each do |se|
@@ -124,7 +122,7 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase
assert_nothing_raised do
# @davey_mcdave doesn't have any author_favorites
includes = {:posts => :comments, :categorizations => :category, :author_favorites => :favorite_author }
- Author.all :include => includes, :conditions => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name'
+ Author.scoped(:includes => includes, :where => {:authors => {:name => @davey_mcdave.name}}, :order => 'categories.name').to_a
end
end
end
diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb
index 07d0b24613..5805e71249 100644
--- a/activerecord/test/cases/associations/eager_singularization_test.rb
+++ b/activerecord/test/cases/associations/eager_singularization_test.rb
@@ -103,43 +103,43 @@ class EagerSingularizationTest < ActiveRecord::TestCase
def test_eager_no_extra_singularization_belongs_to
return unless @have_tables
assert_nothing_raised do
- Virus.find(:all, :include => :octopus)
+ Virus.scoped(:includes => :octopus).all
end
end
def test_eager_no_extra_singularization_has_one
return unless @have_tables
assert_nothing_raised do
- Octopus.find(:all, :include => :virus)
+ Octopus.scoped(:includes => :virus).all
end
end
def test_eager_no_extra_singularization_has_many
return unless @have_tables
assert_nothing_raised do
- Bus.find(:all, :include => :passes)
+ Bus.scoped(:includes => :passes).all
end
end
def test_eager_no_extra_singularization_has_and_belongs_to_many
return unless @have_tables
assert_nothing_raised do
- Crisis.find(:all, :include => :messes)
- Mess.find(:all, :include => :crises)
+ Crisis.scoped(:includes => :messes).all
+ Mess.scoped(:includes => :crises).all
end
end
def test_eager_no_extra_singularization_has_many_through_belongs_to
return unless @have_tables
assert_nothing_raised do
- Crisis.find(:all, :include => :successes)
+ Crisis.scoped(:includes => :successes).all
end
end
def test_eager_no_extra_singularization_has_many_through_has_many
return unless @have_tables
assert_nothing_raised do
- Crisis.find(:all, :include => :compresses)
+ Crisis.scoped(:includes => :compresses).all
end
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index c6e451fc57..08467900f9 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -35,39 +35,42 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_one_through_join_model_with_conditions_on_the_through
- member = Member.find(members(:some_other_guy).id, :include => :favourite_club)
+ member = Member.scoped(:includes => :favourite_club).find(members(:some_other_guy).id)
assert_nil member.favourite_club
end
def test_loading_with_one_association
- posts = Post.find(:all, :include => :comments)
+ posts = Post.scoped(:includes => :comments).all
post = posts.find { |p| p.id == 1 }
assert_equal 2, post.comments.size
assert post.comments.include?(comments(:greetings))
- post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'")
+ post = Post.scoped(:includes => :comments, :where => "posts.title = 'Welcome to the weblog'").first
assert_equal 2, post.comments.size
assert post.comments.include?(comments(:greetings))
- posts = Post.find(:all, :include => :last_comment)
+ posts = Post.scoped(:includes => :last_comment).all
post = posts.find { |p| p.id == 1 }
assert_equal Post.find(1).last_comment, post.last_comment
end
def test_loading_with_one_association_with_non_preload
- posts = Post.find(:all, :include => :last_comment, :order => 'comments.id DESC')
+ posts = Post.scoped(:includes => :last_comment, :order => 'comments.id DESC').all
post = posts.find { |p| p.id == 1 }
assert_equal Post.find(1).last_comment, post.last_comment
end
def test_loading_conditions_with_or
- posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'")
+ posts = authors(:david).posts.references(:comments).scoped(
+ :includes => :comments,
+ :where => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'"
+ ).all
assert_nil posts.detect { |p| p.author_id != authors(:david).id },
"expected to find only david's posts"
end
def test_with_ordering
- list = Post.find(:all, :include => :comments, :order => "posts.id DESC")
+ list = Post.scoped(:includes => :comments, :order => "posts.id DESC").all
[:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other,
:sti_habtm, :sti_post_and_comments, :sti_comments, :authorless, :thinking, :welcome
].each_with_index do |post, index|
@@ -81,14 +84,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_loading_with_multiple_associations
- posts = Post.find(:all, :include => [ :comments, :author, :categories ], :order => "posts.id")
+ posts = Post.scoped(:includes => [ :comments, :author, :categories ], :order => "posts.id").all
assert_equal 2, posts.first.comments.size
assert_equal 2, posts.first.categories.size
assert posts.first.comments.include?(comments(:greetings))
end
def test_duplicate_middle_objects
- comments = Comment.find :all, :conditions => 'post_id = 1', :include => [:post => :author]
+ comments = Comment.scoped(:where => 'post_id = 1', :includes => [:post => :author]).all
assert_no_queries do
comments.each {|comment| comment.post.author.name}
end
@@ -96,25 +99,25 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_preloading_has_many_in_multiple_queries_with_more_ids_than_database_can_handle
Post.connection.expects(:in_clause_length).at_least_once.returns(5)
- posts = Post.find(:all, :include=>:comments)
+ posts = Post.scoped(:includes=>:comments).all
assert_equal 11, posts.size
end
def test_preloading_has_many_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle
Post.connection.expects(:in_clause_length).at_least_once.returns(nil)
- posts = Post.find(:all, :include=>:comments)
+ posts = Post.scoped(:includes=>:comments).all
assert_equal 11, posts.size
end
def test_preloading_habtm_in_multiple_queries_with_more_ids_than_database_can_handle
Post.connection.expects(:in_clause_length).at_least_once.returns(5)
- posts = Post.find(:all, :include=>:categories)
+ posts = Post.scoped(:includes=>:categories).all
assert_equal 11, posts.size
end
def test_preloading_habtm_in_one_queries_when_database_has_no_limit_on_ids_it_can_handle
Post.connection.expects(:in_clause_length).at_least_once.returns(nil)
- posts = Post.find(:all, :include=>:categories)
+ posts = Post.scoped(:includes=>:categories).all
assert_equal 11, posts.size
end
@@ -151,8 +154,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
popular_post.readers.create!(:person => people(:michael))
popular_post.readers.create!(:person => people(:david))
- readers = Reader.find(:all, :conditions => ["post_id = ?", popular_post.id],
- :include => {:post => :comments})
+ readers = Reader.scoped(:where => ["post_id = ?", popular_post.id],
+ :includes => {:post => :comments}).all
readers.each do |reader|
assert_equal [comment], reader.post.comments
end
@@ -164,8 +167,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
car_post.categories << categories(:technology)
comment = car_post.comments.create!(:body => "hmm")
- categories = Category.find(:all, :conditions => ["posts.id=?", car_post.id],
- :include => {:posts => :comments})
+ categories = Category.scoped(:where => { 'posts.id' => car_post.id },
+ :includes => {:posts => :comments}).all
categories.each do |category|
assert_equal [comment], category.posts[0].comments
end
@@ -183,10 +186,10 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once
author_id = authors(:david).id
- author = assert_queries(3) { Author.find(author_id, :include => {:posts_with_comments => :comments}) } # find the author, then find the posts, then find the comments
+ author = assert_queries(3) { Author.scoped(:includes => {:posts_with_comments => :comments}).find(author_id) } # find the author, then find the posts, then find the comments
author.posts_with_comments.each do |post_with_comments|
assert_equal post_with_comments.comments.length, post_with_comments.comments.count
- assert_nil post_with_comments.comments.uniq!
+ assert_nil post_with_comments.comments.to_a.uniq!
end
end
@@ -194,7 +197,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
author = authors(:david)
post = author.post_about_thinking_with_last_comment
last_comment = post.last_comment
- author = assert_queries(ActiveRecord::IdentityMap.enabled? ? 2 : 3) { Author.find(author.id, :include => {:post_about_thinking_with_last_comment => :last_comment})} # find the author, then find the posts, then find the comments
+ author = assert_queries(3) { Author.scoped(:includes => {:post_about_thinking_with_last_comment => :last_comment}).find(author.id)} # find the author, then find the posts, then find the comments
assert_no_queries do
assert_equal post, author.post_about_thinking_with_last_comment
assert_equal last_comment, author.post_about_thinking_with_last_comment.last_comment
@@ -205,7 +208,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
post = posts(:welcome)
author = post.author
author_address = author.author_address
- post = assert_queries(ActiveRecord::IdentityMap.enabled? ? 2 : 3) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author, then find the address
+ post = assert_queries(3) { Post.scoped(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author, then find the address
assert_no_queries do
assert_equal author, post.author_with_address
assert_equal author_address, post.author_with_address.author_address
@@ -215,7 +218,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_null_belongs_to_association_with_same_include_includes_only_once
post = posts(:welcome)
post.update_attributes!(:author => nil)
- post = assert_queries(1) { Post.find(post.id, :include => {:author_with_address => :author_address}) } # find the post, then find the author which is null so no query for the author or address
+ post = assert_queries(1) { Post.scoped(:includes => {:author_with_address => :author_address}).find(post.id) } # find the post, then find the author which is null so no query for the author or address
assert_no_queries do
assert_equal nil, post.author_with_address
end
@@ -224,41 +227,85 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_finding_with_includes_on_null_belongs_to_polymorphic_association
sponsor = sponsors(:moustache_club_sponsor_for_groucho)
sponsor.update_attributes!(:sponsorable => nil)
- sponsor = assert_queries(1) { Sponsor.find(sponsor.id, :include => :sponsorable) }
+ sponsor = assert_queries(1) { Sponsor.scoped(:includes => :sponsorable).find(sponsor.id) }
assert_no_queries do
assert_equal nil, sponsor.sponsorable
end
end
def test_loading_from_an_association
- posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id")
+ posts = authors(:david).posts.scoped(:includes => :comments, :order => "posts.id").all
assert_equal 2, posts.first.comments.size
end
def test_loading_from_an_association_that_has_a_hash_of_conditions
assert_nothing_raised do
- Author.find(:all, :include => :hello_posts_with_hash_conditions)
+ Author.scoped(:includes => :hello_posts_with_hash_conditions).all
end
- assert !Author.find(authors(:david).id, :include => :hello_posts_with_hash_conditions).hello_posts.empty?
+ assert !Author.scoped(:includes => :hello_posts_with_hash_conditions).find(authors(:david).id).hello_posts.empty?
end
def test_loading_with_no_associations
- assert_nil Post.find(posts(:authorless).id, :include => :author).author
+ assert_nil Post.scoped(:includes => :author).find(posts(:authorless).id).author
end
def test_nested_loading_with_no_associations
assert_nothing_raised do
- Post.find(posts(:authorless).id, :include => {:author => :author_addresss})
+ Post.scoped(:includes => {:author => :author_addresss}).find(posts(:authorless).id)
end
end
+ def test_nested_loading_through_has_one_association
+ aa = AuthorAddress.scoped(:includes => {:author => :posts}).find(author_addresses(:david_address).id)
+ assert_equal aa.author.posts.count, aa.author.posts.length
+ end
+
+ def test_nested_loading_through_has_one_association_with_order
+ aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'author_addresses.id').find(author_addresses(:david_address).id)
+ assert_equal aa.author.posts.count, aa.author.posts.length
+ end
+
+ def test_nested_loading_through_has_one_association_with_order_on_association
+ aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'authors.id').find(author_addresses(:david_address).id)
+ assert_equal aa.author.posts.count, aa.author.posts.length
+ end
+
+ def test_nested_loading_through_has_one_association_with_order_on_nested_association
+ aa = AuthorAddress.scoped(:includes => {:author => :posts}, :order => 'posts.id').find(author_addresses(:david_address).id)
+ assert_equal aa.author.posts.count, aa.author.posts.length
+ end
+
+ def test_nested_loading_through_has_one_association_with_conditions
+ aa = AuthorAddress.references(:author_addresses).scoped(
+ :includes => {:author => :posts},
+ :where => "author_addresses.id > 0"
+ ).find author_addresses(:david_address).id
+ assert_equal aa.author.posts.count, aa.author.posts.length
+ end
+
+ def test_nested_loading_through_has_one_association_with_conditions_on_association
+ aa = AuthorAddress.references(:authors).scoped(
+ :includes => {:author => :posts},
+ :where => "authors.id > 0"
+ ).find author_addresses(:david_address).id
+ assert_equal aa.author.posts.count, aa.author.posts.length
+ end
+
+ def test_nested_loading_through_has_one_association_with_conditions_on_nested_association
+ aa = AuthorAddress.references(:posts).scoped(
+ :includes => {:author => :posts},
+ :where => "posts.id > 0"
+ ).find author_addresses(:david_address).id
+ assert_equal aa.author.posts.count, aa.author.posts.length
+ end
+
def test_eager_association_loading_with_belongs_to_and_foreign_keys
- pets = Pet.find(:all, :include => :owner)
+ pets = Pet.scoped(:includes => :owner).all
assert_equal 3, pets.length
end
def test_eager_association_loading_with_belongs_to
- comments = Comment.find(:all, :include => :post)
+ comments = Comment.scoped(:includes => :post).all
assert_equal 11, comments.length
titles = comments.map { |c| c.post.title }
assert titles.include?(posts(:welcome).title)
@@ -266,45 +313,47 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_association_loading_with_belongs_to_and_limit
- comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :limit => 5, :order => 'comments.id').all
assert_equal 5, comments.length
assert_equal [1,2,3,5,6], comments.collect { |c| c.id }
end
def test_eager_association_loading_with_belongs_to_and_limit_and_conditions
- comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :where => 'post_id = 4', :limit => 3, :order => 'comments.id').all
assert_equal 3, comments.length
assert_equal [5,6,7], comments.collect { |c| c.id }
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset
- comments = Comment.find(:all, :include => :post, :limit => 3, :offset => 2, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :limit => 3, :offset => 2, :order => 'comments.id').all
assert_equal 3, comments.length
assert_equal [3,5,6], comments.collect { |c| c.id }
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions
- comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :where => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id').all
assert_equal 3, comments.length
assert_equal [6,7,8], comments.collect { |c| c.id }
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array
- comments = Comment.find(:all, :include => :post, :conditions => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :where => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id').all
assert_equal 3, comments.length
assert_equal [6,7,8], comments.collect { |c| c.id }
end
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name
assert_nothing_raised do
- Comment.find(:all, :include => :post, :conditions => ['posts.id = ?',4])
+ ActiveSupport::Deprecation.silence do
+ Comment.scoped(:includes => :post, :where => ['posts.id = ?',4]).all
+ end
end
end
def test_eager_association_loading_with_belongs_to_and_conditions_hash
comments = []
assert_nothing_raised do
- comments = Comment.find(:all, :include => :post, :conditions => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id')
+ comments = Comment.scoped(:includes => :post, :where => {:posts => {:id => 4}}, :limit => 3, :order => 'comments.id').all
end
assert_equal 3, comments.length
assert_equal [5,6,7], comments.collect { |c| c.id }
@@ -316,67 +365,71 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name
quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id')
assert_nothing_raised do
- Comment.find(:all, :include => :post, :conditions => ["#{quoted_posts_id} = ?",4])
+ ActiveSupport::Deprecation.silence do
+ Comment.scoped(:includes => :post, :where => ["#{quoted_posts_id} = ?",4]).all
+ end
end
end
def test_eager_association_loading_with_belongs_to_and_order_string_with_unquoted_table_name
assert_nothing_raised do
- Comment.find(:all, :include => :post, :order => 'posts.id')
+ Comment.scoped(:includes => :post, :order => 'posts.id').all
end
end
def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name
quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id')
assert_nothing_raised do
- Comment.find(:all, :include => :post, :order => quoted_posts_id)
+ ActiveSupport::Deprecation.silence do
+ Comment.scoped(:includes => :post, :order => quoted_posts_id).all
+ end
end
end
def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations
- posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id')
+ posts = Post.scoped(:includes => [:author, :very_special_comment], :limit => 1, :order => 'posts.id').all
assert_equal 1, posts.length
assert_equal [1], posts.collect { |p| p.id }
end
def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations
- posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id')
+ posts = Post.scoped(:includes => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id').all
assert_equal 1, posts.length
assert_equal [2], posts.collect { |p| p.id }
end
def test_eager_association_loading_with_belongs_to_inferred_foreign_key_from_association_name
- author_favorite = AuthorFavorite.find(:first, :include => :favorite_author)
+ author_favorite = AuthorFavorite.scoped(:includes => :favorite_author).first
assert_equal authors(:mary), assert_no_queries { author_favorite.favorite_author }
end
def test_eager_load_belongs_to_quotes_table_and_column_names
- job = Job.find jobs(:unicyclist).id, :include => :ideal_reference
+ job = Job.includes(:ideal_reference).find jobs(:unicyclist).id
references(:michael_unicyclist)
assert_no_queries{ assert_equal references(:michael_unicyclist), job.ideal_reference}
end
def test_eager_load_has_one_quotes_table_and_column_names
- michael = Person.find(people(:michael), :include => :favourite_reference)
+ michael = Person.scoped(:includes => :favourite_reference).find(people(:michael))
references(:michael_unicyclist)
assert_no_queries{ assert_equal references(:michael_unicyclist), michael.favourite_reference}
end
def test_eager_load_has_many_quotes_table_and_column_names
- michael = Person.find(people(:michael), :include => :references)
+ michael = Person.scoped(:includes => :references).find(people(:michael))
references(:michael_magician,:michael_unicyclist)
assert_no_queries{ assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) }
end
def test_eager_load_has_many_through_quotes_table_and_column_names
- michael = Person.find(people(:michael), :include => :jobs)
+ michael = Person.scoped(:includes => :jobs).find(people(:michael))
jobs(:magician, :unicyclist)
assert_no_queries{ assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) }
end
def test_eager_load_has_many_with_string_keys
subscriptions = subscriptions(:webster_awdr, :webster_rfr)
- subscriber =Subscriber.find(subscribers(:second).id, :include => :subscriptions)
+ subscriber =Subscriber.scoped(:includes => :subscriptions).find(subscribers(:second).id)
assert_equal subscriptions, subscriber.subscriptions.sort_by(&:id)
end
@@ -394,25 +447,25 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_load_has_many_through_with_string_keys
books = books(:awdr, :rfr)
- subscriber = Subscriber.find(subscribers(:second).id, :include => :books)
+ subscriber = Subscriber.scoped(:includes => :books).find(subscribers(:second).id)
assert_equal books, subscriber.books.sort_by(&:id)
end
def test_eager_load_belongs_to_with_string_keys
subscriber = subscribers(:second)
- subscription = Subscription.find(subscriptions(:webster_awdr).id, :include => :subscriber)
+ subscription = Subscription.scoped(:includes => :subscriber).find(subscriptions(:webster_awdr).id)
assert_equal subscriber, subscription.subscriber
end
def test_eager_association_loading_with_explicit_join
- posts = Post.find(:all, :include => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id')
+ posts = Post.scoped(:includes => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id').all
assert_equal 1, posts.length
end
def test_eager_with_has_many_through
- posts_with_comments = people(:michael).posts.find(:all, :include => :comments, :order => 'posts.id')
- posts_with_author = people(:michael).posts.find(:all, :include => :author, :order => 'posts.id')
- posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ], :order => 'posts.id')
+ posts_with_comments = people(:michael).posts.scoped(:includes => :comments, :order => 'posts.id').all
+ posts_with_author = people(:michael).posts.scoped(:includes => :author, :order => 'posts.id').all
+ posts_with_comments_and_author = people(:michael).posts.scoped(:includes => [ :comments, :author ], :order => 'posts.id').all
assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size }
assert_equal authors(:david), assert_no_queries { posts_with_author.first.author }
assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
@@ -423,32 +476,32 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.create!(:author => author, :title => "TITLE", :body => "BODY")
author.author_favorites.create(:favorite_author_id => 1)
author.author_favorites.create(:favorite_author_id => 2)
- posts_with_author_favorites = author.posts.find(:all, :include => :author_favorites)
+ posts_with_author_favorites = author.posts.scoped(:includes => :author_favorites).all
assert_no_queries { posts_with_author_favorites.first.author_favorites.first.author_id }
end
def test_eager_with_has_many_through_an_sti_join_model
- author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id')
+ author = Author.scoped(:includes => :special_post_comments, :order => 'authors.id').first
assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
end
def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both
- author = Author.find(:first, :include => :special_nonexistant_post_comments, :order => 'authors.id')
+ author = Author.scoped(:includes => :special_nonexistant_post_comments, :order => 'authors.id').first
assert_equal [], author.special_nonexistant_post_comments
end
def test_eager_with_has_many_through_join_model_with_conditions
- assert_equal Author.find(:first, :include => :hello_post_comments,
- :order => 'authors.id').hello_post_comments.sort_by(&:id),
- Author.find(:first, :order => 'authors.id').hello_post_comments.sort_by(&:id)
+ assert_equal Author.scoped(:includes => :hello_post_comments,
+ :order => 'authors.id').first.hello_post_comments.sort_by(&:id),
+ Author.scoped(:order => 'authors.id').first.hello_post_comments.sort_by(&:id)
end
def test_eager_with_has_many_through_join_model_with_conditions_on_top_level
- assert_equal comments(:more_greetings), Author.find(authors(:david).id, :include => :comments_with_order_and_conditions).comments_with_order_and_conditions.first
+ assert_equal comments(:more_greetings), Author.scoped(:includes => :comments_with_order_and_conditions).find(authors(:david).id).comments_with_order_and_conditions.first
end
def test_eager_with_has_many_through_join_model_with_include
- author_comments = Author.find(authors(:david).id, :include => :comments_with_include).comments_with_include.to_a
+ author_comments = Author.scoped(:includes => :comments_with_include).find(authors(:david).id).comments_with_include.to_a
assert_no_queries do
author_comments.first.post.title
end
@@ -456,7 +509,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_through_with_conditions_join_model_with_include
post_tags = Post.find(posts(:welcome).id).misc_tags
- eager_post_tags = Post.find(1, :include => :misc_tags).misc_tags
+ eager_post_tags = Post.scoped(:includes => :misc_tags).find(1).misc_tags
assert_equal post_tags, eager_post_tags
end
@@ -467,16 +520,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_many_and_limit
- posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2)
+ posts = Post.scoped(:order => 'posts.id asc', :includes => [ :author, :comments ], :limit => 2).all
assert_equal 2, posts.size
assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size }
end
def test_eager_with_has_many_and_limit_and_conditions
if current_adapter?(:OpenBaseAdapter)
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id")
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id").all
else
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.body = 'hello'", :order => "posts.id")
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "posts.body = 'hello'", :order => "posts.id").all
end
assert_equal 2, posts.size
assert_equal [4,5], posts.collect { |p| p.id }
@@ -484,57 +537,62 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_many_and_limit_and_conditions_array
if current_adapter?(:OpenBaseAdapter)
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id")
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id").all
else
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id")
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "posts.body = ?", 'hello' ], :order => "posts.id").all
end
assert_equal 2, posts.size
assert_equal [4,5], posts.collect { |p| p.id }
end
def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
+ posts = ActiveSupport::Deprecation.silence do
+ Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => [ "authors.name = ?", 'David' ]).all
+ end
assert_equal 2, posts.size
- count = Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
+ count = ActiveSupport::Deprecation.silence do
+ Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
+ end
assert_equal count, posts.size
end
def test_eager_with_has_many_and_limit_and_high_offset
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).all
assert_equal 0, posts.size
end
def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_array_conditions
assert_queries(1) do
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
- :conditions => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ])
+ posts = Post.references(:authors, :comments).
+ scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10,
+ :where => [ "authors.name = ? and comments.body = ?", 'David', 'go crazy' ]).all
assert_equal 0, posts.size
end
end
def test_eager_with_has_many_and_limit_and_high_offset_and_multiple_hash_conditions
assert_queries(1) do
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10,
- :conditions => { 'authors.name' => 'David', 'comments.body' => 'go crazy' })
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10,
+ :where => { 'authors.name' => 'David', 'comments.body' => 'go crazy' }).all
assert_equal 0, posts.size
end
end
def test_count_eager_with_has_many_and_limit_and_high_offset
- posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :offset => 10, :where => { 'authors.name' => 'David' }).count(:all)
assert_equal 0, posts
end
def test_eager_with_has_many_and_limit_with_no_results
- posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.title = 'magic forest'")
+ posts = Post.scoped(:includes => [ :author, :comments ], :limit => 2, :where => "posts.title = 'magic forest'").all
assert_equal 0, posts.size
end
def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional
author = authors(:david)
author_posts_without_comments = author.posts.select { |post| post.comments.blank? }
- assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null')
+ assert_equal author_posts_without_comments.size, author.posts.includes(:comments).where('comments.id is null').references(:comments).count
end
def test_eager_count_performed_on_a_has_many_through_association_with_multi_table_conditional
@@ -544,7 +602,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_and_belongs_to_many_and_limit
- posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3)
+ posts = Post.scoped(:includes => :categories, :order => "posts.id", :limit => 3).all
assert_equal 3, posts.size
assert_equal 2, posts[0].categories.size
assert_equal 1, posts[1].categories.size
@@ -553,9 +611,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert posts[1].categories.include?(categories(:general))
end
- # This is only really relevant when the identity map is off. Since the preloader for habtm
- # gets raw row hashes from the database and then instantiates them, this test ensures that
- # it only instantiates one actual object per record from the database.
+ # Since the preloader for habtm gets raw row hashes from the database and then
+ # instantiates them, this test ensures that it only instantiates one actual
+ # object per record from the database.
def test_has_and_belongs_to_many_should_not_instantiate_same_records_multiple_times
welcome = posts(:welcome)
categories = Category.includes(:posts)
@@ -570,68 +628,47 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers
- posts = authors(:david).posts.find(:all,
- :include => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
- :limit => 2
- )
+ posts =
+ authors(:david).posts
+ .includes(:comments)
+ .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'")
+ .references(:comments)
+ .limit(2)
+ .to_a
assert_equal 2, posts.size
- count = Post.count(
- :include => [ :comments, :author ],
- :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
- :limit => 2
- )
+ count =
+ Post.includes(:comments, :author)
+ .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')")
+ .references(:authors, :comments)
+ .limit(2)
+ .count
assert_equal count, posts.size
end
def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers
posts = nil
- Post.send(:with_scope, :find => {
- :include => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'"
- }) do
- posts = authors(:david).posts.find(:all, :limit => 2)
- assert_equal 2, posts.size
- end
+ Post.includes(:comments)
+ .where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'")
+ .references(:comments)
+ .scoping do
- Post.send(:with_scope, :find => {
- :include => [ :comments, :author ],
- :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')"
- }) do
- count = Post.count(:limit => 2)
- assert_equal count, posts.size
+ posts = authors(:david).posts.limit(2).to_a
+ assert_equal 2, posts.size
end
- end
- def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers
- Post.send(:with_scope, :find => { :conditions => "1=1" }) do
- posts = authors(:david).posts.find(:all,
- :include => :comments,
- :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
- :limit => 2
- )
- assert_equal 2, posts.size
+ Post.includes(:comments, :author)
+ .where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')")
+ .references(:authors, :comments)
+ .scoping do
- count = Post.count(
- :include => [ :comments, :author ],
- :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
- :limit => 2
- )
+ count = Post.limit(2).count
assert_equal count, posts.size
end
end
- def test_eager_with_scoped_order_using_association_limiting_without_explicit_scope
- posts_with_explicit_order = Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :order => 'posts.id DESC', :limit => 2)
- posts_with_scoped_order = Post.send(:with_scope, :find => {:order => 'posts.id DESC'}) do
- Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :limit => 2)
- end
- assert_equal posts_with_explicit_order, posts_with_scoped_order
- end
-
def test_eager_association_loading_with_habtm
- posts = Post.find(:all, :include => :categories, :order => "posts.id")
+ posts = Post.scoped(:includes => :categories, :order => "posts.id").all
assert_equal 2, posts[0].categories.size
assert_equal 1, posts[1].categories.size
assert_equal 0, posts[2].categories.size
@@ -640,23 +677,23 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_eager_with_inheritance
- SpecialPost.find(:all, :include => [ :comments ])
+ SpecialPost.scoped(:includes => [ :comments ]).all
end
def test_eager_has_one_with_association_inheritance
- post = Post.find(4, :include => [ :very_special_comment ])
+ post = Post.scoped(:includes => [ :very_special_comment ]).find(4)
assert_equal "VerySpecialComment", post.very_special_comment.class.to_s
end
def test_eager_has_many_with_association_inheritance
- post = Post.find(4, :include => [ :special_comments ])
+ post = Post.scoped(:includes => [ :special_comments ]).find(4)
post.special_comments.each do |special_comment|
assert special_comment.is_a?(SpecialComment)
end
end
def test_eager_habtm_with_association_inheritance
- post = Post.find(6, :include => [ :special_categories ])
+ post = Post.scoped(:includes => [ :special_categories ]).find(6)
assert_equal 1, post.special_categories.size
post.special_categories.each do |special_category|
assert_equal "SpecialCategory", special_category.class.to_s
@@ -665,8 +702,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_has_one_dependent_does_not_destroy_dependent
assert_not_nil companies(:first_firm).account
- f = Firm.find(:first, :include => :account,
- :conditions => ["companies.name = ?", "37signals"])
+ f = Firm.scoped(:includes => :account,
+ :where => ["companies.name = ?", "37signals"]).first
assert_not_nil f.account
assert_equal companies(:first_firm, :reload).account, f.account
end
@@ -680,16 +717,16 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_with_invalid_association_reference
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
- Post.find(6, :include=> :monkeys )
+ Post.scoped(:includes=> :monkeys ).find(6)
}
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
- Post.find(6, :include=>[ :monkeys ])
+ Post.scoped(:includes=>[ :monkeys ]).find(6)
}
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
- Post.find(6, :include=>[ 'monkeys' ])
+ Post.scoped(:includes=>[ 'monkeys' ]).find(6)
}
assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
- Post.find(6, :include=>[ :monkeys, :elephants ])
+ Post.scoped(:includes=>[ :monkeys, :elephants ]).find(6)
}
end
@@ -734,21 +771,52 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def find_all_ordered(className, include=nil)
- className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include)
+ className.scoped(:order=>"#{className.table_name}.#{className.primary_key}", :includes=>include).all
end
def test_limited_eager_with_order
- assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title)', :limit => 2, :offset => 1)
- assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1)
+ assert_equal(
+ posts(:thinking, :sti_comments),
+ Post.scoped(
+ :includes => [:author, :comments], :where => { 'authors.name' => 'David' },
+ :order => 'UPPER(posts.title)', :limit => 2, :offset => 1
+ ).all
+ )
+ assert_equal(
+ posts(:sti_post_and_comments, :sti_comments),
+ Post.scoped(
+ :includes => [:author, :comments], :where => { 'authors.name' => 'David' },
+ :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1
+ ).all
+ )
end
def test_limited_eager_with_multiple_order_columns
- assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1)
- assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1)
+ assert_equal(
+ posts(:thinking, :sti_comments),
+ Post.scoped(
+ :includes => [:author, :comments], :where => { 'authors.name' => 'David' },
+ :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1
+ ).all
+ )
+ assert_equal(
+ posts(:sti_post_and_comments, :sti_comments),
+ Post.scoped(
+ :includes => [:author, :comments], :where => { 'authors.name' => 'David' },
+ :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1
+ ).all
+ )
end
def test_limited_eager_with_numeric_in_association
- assert_equal people(:david, :susan), Person.find(:all, :include => [:readers, :primary_contact, :number1_fan], :conditions => "number1_fans_people.first_name like 'M%'", :order => 'people.id', :limit => 2, :offset => 0)
+ assert_equal(
+ people(:david, :susan),
+ Person.references(:number1_fans_people).scoped(
+ :includes => [:readers, :primary_contact, :number1_fan],
+ :where => "number1_fans_people.first_name like 'M%'",
+ :order => 'people.id', :limit => 2, :offset => 0
+ ).all
+ )
end
def test_preload_with_interpolation
@@ -760,9 +828,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_polymorphic_type_condition
- post = Post.find(posts(:thinking).id, :include => :taggings)
+ post = Post.scoped(:includes => :taggings).find(posts(:thinking).id)
assert post.taggings.include?(taggings(:thinking_general))
- post = SpecialPost.find(posts(:thinking).id, :include => :taggings)
+ post = SpecialPost.scoped(:includes => :taggings).find(posts(:thinking).id)
assert post.taggings.include?(taggings(:thinking_general))
end
@@ -813,13 +881,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
def test_eager_with_valid_association_as_string_not_symbol
- assert_nothing_raised { Post.find(:all, :include => 'comments') }
+ assert_nothing_raised { Post.scoped(:includes => 'comments').all }
end
def test_eager_with_floating_point_numbers
assert_queries(2) do
# Before changes, the floating point numbers will be interpreted as table names and will cause this to run in one query
- Comment.find :all, :conditions => "123.456 = 123.456", :include => :post
+ Comment.scoped(:where => "123.456 = 123.456", :includes => :post).all
end
end
@@ -863,31 +931,31 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_count_with_include
if current_adapter?(:SybaseAdapter)
- assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15")
+ assert_equal 3, authors(:david).posts_with_comments.where("len(comments.body) > 15").references(:comments).count
elsif current_adapter?(:OpenBaseAdapter)
- assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15")
+ assert_equal 3, authors(:david).posts_with_comments.where("length(FETCHBLOB(comments.body)) > 15").references(:comments).count
else
- assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15")
+ assert_equal 3, authors(:david).posts_with_comments.where("length(comments.body) > 15").references(:comments).count
end
end
def test_load_with_sti_sharing_association
assert_queries(2) do #should not do 1 query per subclass
- Comment.find :all, :include => :post
+ Comment.includes(:post).all
end
end
def test_conditions_on_join_table_with_include_and_limit
- assert_equal 3, Developer.find(:all, :include => 'projects', :conditions => 'developers_projects.access_level = 1', :limit => 5).size
+ assert_equal 3, Developer.scoped(:includes => 'projects', :where => { 'developers_projects.access_level' => 1 }, :limit => 5).all.size
end
def test_order_on_join_table_with_include_and_limit
- assert_equal 5, Developer.find(:all, :include => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).size
+ assert_equal 5, Developer.scoped(:includes => 'projects', :order => 'developers_projects.joined_on DESC', :limit => 5).all.size
end
def test_eager_loading_with_order_on_joined_table_preloads
posts = assert_queries(2) do
- Post.find(:all, :joins => :comments, :include => :author, :order => 'comments.id DESC')
+ Post.scoped(:joins => :comments, :includes => :author, :order => 'comments.id DESC').all
end
assert_equal posts(:eager_other), posts[1]
assert_equal authors(:mary), assert_no_queries { posts[1].author}
@@ -895,24 +963,24 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_loading_with_conditions_on_joined_table_preloads
posts = assert_queries(2) do
- Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
- posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
- Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ posts = assert_queries(2) do
+ Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => [:comments], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
- posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
- Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id')
+ posts = assert_queries(2) do
+ Post.scoped(:includes => :author, :joins => {:taggings => :tag}, :where => "tags.name = 'General'", :order => 'posts.id').all
end
assert_equal posts(:welcome, :thinking), posts
- posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
- Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id')
+ posts = assert_queries(2) do
+ Post.scoped(:includes => :author, :joins => {:taggings => {:tag => :taggings}}, :where => "taggings_tags.super_tag_id=2", :order => 'posts.id').all
end
assert_equal posts(:welcome, :thinking), posts
@@ -920,13 +988,13 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_loading_with_conditions_on_string_joined_table_preloads
posts = assert_queries(2) do
- Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :where => "comments.body like 'Thank you%'", :order => 'posts.id').all
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
- posts = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
- Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
+ posts = assert_queries(2) do
+ Post.scoped(:select => 'distinct posts.*', :includes => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :where => "comments.body like 'Thank you%'", :order => 'posts.id').all
end
assert_equal [posts(:welcome)], posts
assert_equal authors(:david), assert_no_queries { posts[0].author}
@@ -935,7 +1003,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_loading_with_select_on_joined_table_preloads
posts = assert_queries(2) do
- Post.find(:all, :select => 'posts.*, authors.name as author_name', :include => :comments, :joins => :author, :order => 'posts.id')
+ Post.scoped(:select => 'posts.*, authors.name as author_name', :includes => :comments, :joins => :author, :order => 'posts.id').all
end
assert_equal 'David', posts[0].author_name
assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments}
@@ -945,14 +1013,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
Author.columns
authors = assert_queries(2) do
- Author.find(:all, :include => :author_address, :joins => :comments, :conditions => "posts.title like 'Welcome%'")
+ Author.scoped(:includes => :author_address, :joins => :comments, :where => "posts.title like 'Welcome%'").all
end
assert_equal authors(:david), authors[0]
assert_equal author_addresses(:david_address), authors[0].author_address
end
def test_preload_belongs_to_uses_exclusive_scope
- people = Person.males.find(:all, :include => :primary_contact)
+ people = Person.males.scoped(:includes => :primary_contact).all
assert_not_equal people.length, 0
people.each do |person|
assert_no_queries {assert_not_nil person.primary_contact}
@@ -961,15 +1029,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_preload_has_many_uses_exclusive_scope
- people = Person.males.find :all, :include => :agents
+ people = Person.males.includes(:agents).all
people.each do |person|
assert_equal Person.find(person.id).agents, person.agents
end
end
def test_preload_has_many_using_primary_key
- expected = Firm.find(:first).clients_using_primary_key.to_a
- firm = Firm.find :first, :include => :clients_using_primary_key
+ expected = Firm.first.clients_using_primary_key.to_a
+ firm = Firm.includes(:clients_using_primary_key).first
assert_no_queries do
assert_equal expected, firm.clients_using_primary_key
end
@@ -979,9 +1047,9 @@ class EagerAssociationTest < ActiveRecord::TestCase
expected = Firm.find(1).clients_using_primary_key.sort_by(&:name)
# Oracle adapter truncates alias to 30 characters
if current_adapter?(:OracleAdapter)
- firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name'
+ firm = Firm.scoped(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name').find(1)
else
- firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name'
+ firm = Firm.scoped(:includes => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name').find(1)
end
assert_no_queries do
assert_equal expected, firm.clients_using_primary_key
@@ -990,7 +1058,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_preload_has_one_using_primary_key
expected = accounts(:signals37)
- firm = Firm.find :first, :include => :account_using_primary_key, :order => 'companies.id'
+ firm = Firm.scoped(:includes => :account_using_primary_key, :order => 'companies.id').first
assert_no_queries do
assert_equal expected, firm.account_using_primary_key
end
@@ -998,7 +1066,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_include_has_one_using_primary_key
expected = accounts(:signals37)
- firm = Firm.find(:all, :include => :account_using_primary_key, :order => 'accounts.id').detect {|f| f.id == 1}
+ firm = Firm.scoped(:includes => :account_using_primary_key, :order => 'accounts.id').all.detect {|f| f.id == 1}
assert_no_queries do
assert_equal expected, firm.account_using_primary_key
end
@@ -1014,7 +1082,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_preloading_empty_belongs_to_polymorphic
t = Tagging.create!(:taggable_type => 'Post', :taggable_id => Post.maximum(:id) + 1, :tag => tags(:general))
- tagging = assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Tagging.preload(:taggable).find(t.id) }
+ tagging = assert_queries(2) { Tagging.preload(:taggable).find(t.id) }
assert_no_queries { assert_nil tagging.taggable }
end
@@ -1060,4 +1128,28 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first
end
end
+
+ def test_deep_including_through_habtm
+ posts = Post.scoped(:includes => {:categories => :categorizations}, :order => "posts.id").all
+ assert_no_queries { assert_equal 2, posts[0].categories[0].categorizations.length }
+ assert_no_queries { assert_equal 1, posts[0].categories[1].categorizations.length }
+ assert_no_queries { assert_equal 2, posts[1].categories[0].categorizations.length }
+ end
+
+ test "scoping with a circular preload" do
+ assert_equal Comment.find(1), Comment.preload(:post => :comments).scoping { Comment.find(1) }
+ end
+
+ test "circular preload does not modify unscoped" do
+ expected = FirstPost.unscoped.find(2)
+ FirstPost.preload(:comments => :first_post).find(1)
+ assert_equal expected, FirstPost.unscoped.find(2)
+ end
+
+ test "preload ignores the scoping" do
+ assert_equal(
+ Comment.find(1).post,
+ Post.where('1 = 0').scoping { Comment.preload(:post).find(1).post }
+ )
+ end
end
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index 8dc1423375..d7c489c2b5 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -36,11 +36,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
end
def test_marshalling_extensions
- if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7"
- return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \
- "to be a Ruby bug.")
- end
-
david = developers(:david)
assert_equal projects(:action_controller), david.projects.find_most_recent
@@ -51,11 +46,6 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
end
def test_marshalling_named_extensions
- if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7"
- return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \
- "to be a Ruby bug.")
- end
-
david = developers(:david)
assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
@@ -71,6 +61,12 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension', extension_name(MyApplication::Business::Developer)
end
+ def test_proxy_association_after_scoped
+ post = posts(:welcome)
+ assert_equal post.association(:comments), post.comments.the_association
+ assert_equal post.association(:comments), post.comments.scoped.the_association
+ end
+
private
def extension_name(model)
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 34d90cc395..ed1caa2ef5 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -23,7 +23,7 @@ require 'models/treaty'
require 'active_support/core_ext/string/conversions'
class ProjectWithAfterCreateHook < ActiveRecord::Base
- set_table_name 'projects'
+ self.table_name = 'projects'
has_and_belongs_to_many :developers,
:class_name => "DeveloperForProjectWithAfterCreateHook",
:join_table => "developers_projects",
@@ -39,7 +39,7 @@ class ProjectWithAfterCreateHook < ActiveRecord::Base
end
class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base
- set_table_name 'developers'
+ self.table_name = 'developers'
has_and_belongs_to_many :projects,
:class_name => "ProjectWithAfterCreateHook",
:join_table => "developers_projects",
@@ -48,7 +48,7 @@ class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base
end
class ProjectWithSymbolsForKeys < ActiveRecord::Base
- set_table_name 'projects'
+ self.table_name = 'projects'
has_and_belongs_to_many :developers,
:class_name => "DeveloperWithSymbolsForKeys",
:join_table => :developers_projects,
@@ -57,7 +57,7 @@ class ProjectWithSymbolsForKeys < ActiveRecord::Base
end
class DeveloperWithSymbolsForKeys < ActiveRecord::Base
- set_table_name 'developers'
+ self.table_name = 'developers'
has_and_belongs_to_many :projects,
:class_name => "ProjectWithSymbolsForKeys",
:join_table => :developers_projects,
@@ -66,7 +66,7 @@ class DeveloperWithSymbolsForKeys < ActiveRecord::Base
end
class DeveloperWithCounterSQL < ActiveRecord::Base
- set_table_name 'developers'
+ self.table_name = 'developers'
has_and_belongs_to_many :projects,
:class_name => "DeveloperWithCounterSQL",
:join_table => "developers_projects",
@@ -77,7 +77,7 @@ end
class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
- :parrots, :pirates, :treasures, :price_estimates, :tags, :taggings
+ :parrots, :pirates, :parrots_pirates, :treasures, :price_estimates, :tags, :taggings
def setup_data_for_habtm_case
ActiveRecord::Base.connection.execute('delete from countries_treaties')
@@ -123,11 +123,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert active_record.developers.include?(david)
end
- def test_triple_equality
- assert !(Array === Developer.find(1).projects)
- assert Developer.find(1).projects === Array
- end
-
def test_adding_single
jamis = Developer.find(2)
jamis.projects.reload # causing the collection to load
@@ -338,6 +333,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 3, project.developers.size
end
+ def test_uniq_when_association_already_loaded
+ project = projects(:active_record)
+ project.developers << [ developers(:jamis), developers(:david), developers(:jamis), developers(:david) ]
+ assert_equal 3, Project.includes(:developers).find(project.id).developers.size
+ end
+
def test_deleting
david = Developer.find(1)
active_record = Project.find(1)
@@ -355,7 +356,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_array
david = Developer.find(1)
david.projects.reload
- david.projects.delete(Project.find(:all))
+ david.projects.delete(Project.all)
assert_equal 0, david.projects.size
assert_equal 0, david.projects(true).size
end
@@ -375,10 +376,16 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
active_record.developers.reload
assert_equal 3, active_record.developers_by_sql.size
- active_record.developers_by_sql.delete(Developer.find(:all))
+ active_record.developers_by_sql.delete(Developer.all)
assert_equal 0, active_record.developers_by_sql(true).size
end
+ def test_deleting_all_with_sql
+ project = Project.find(1)
+ project.developers_by_sql.delete_all
+ assert_equal 0, project.developers_by_sql.size
+ end
+
def test_deleting_all
david = Developer.find(1)
david.projects.reload
@@ -445,6 +452,26 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert david.projects(true).empty?
end
+ def test_destroy_associations_destroys_multiple_associations
+ george = parrots(:george)
+ assert !george.pirates.empty?
+ assert !george.treasures.empty?
+
+ assert_no_difference "Pirate.count" do
+ assert_no_difference "Treasure.count" do
+ george.destroy_associations
+ end
+ end
+
+ join_records = Parrot.connection.select_all("SELECT * FROM parrots_pirates WHERE parrot_id = #{george.id}")
+ assert join_records.empty?
+ assert george.pirates(true).empty?
+
+ join_records = Parrot.connection.select_all("SELECT * FROM parrots_treasures WHERE parrot_id = #{george.id}")
+ assert join_records.empty?
+ assert george.treasures(true).empty?
+ end
+
def test_deprecated_push_with_attributes_was_removed
jamis = developers(:jamis)
assert_raise(NoMethodError) do
@@ -477,7 +504,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_include_uses_array_include_after_loaded
project = projects(:active_record)
- project.developers.class # force load target
+ project.developers.load_target
developer = project.developers.first
@@ -528,43 +555,23 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_find_with_merged_options
assert_equal 1, projects(:active_record).limited_developers.size
- assert_equal 1, projects(:active_record).limited_developers.find(:all).size
- assert_equal 3, projects(:active_record).limited_developers.find(:all, :limit => nil).size
+ assert_equal 1, projects(:active_record).limited_developers.all.size
+ assert_equal 3, projects(:active_record).limited_developers.limit(nil).all.size
end
def test_dynamic_find_should_respect_association_order
# Developers are ordered 'name DESC, id DESC'
high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
- assert_equal high_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'")
+ assert_equal high_id_jamis, projects(:active_record).developers.scoped(:where => "name = 'Jamis'").first
assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis')
end
- def test_dynamic_find_all_should_respect_association_order
- # Developers are ordered 'name DESC, id DESC'
- low_id_jamis = developers(:jamis)
- middle_id_jamis = developers(:poor_jamis)
- high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
-
- assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find(:all, :conditions => "name = 'Jamis'")
- assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis')
- end
-
def test_find_should_append_to_association_order
ordered_developers = projects(:active_record).developers.order('projects.id')
assert_equal ['developers.name desc, developers.id desc', 'projects.id'], ordered_developers.order_values
end
- def test_dynamic_find_all_should_respect_association_limit
- assert_equal 1, projects(:active_record).limited_developers.find(:all, :conditions => "name = 'Jamis'").length
- assert_equal 1, projects(:active_record).limited_developers.find_all_by_name('Jamis').length
- end
-
- def test_dynamic_find_all_order_should_override_association_limit
- assert_equal 2, projects(:active_record).limited_developers.find(:all, :conditions => "name = 'Jamis'", :limit => 9_000).length
- assert_equal 2, projects(:active_record).limited_developers.find_all_by_name('Jamis', :limit => 9_000).length
- end
-
def test_dynamic_find_all_should_respect_readonly_access
projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?}
projects(:active_record).readonly_developers.each { |d| d.readonly? }
@@ -612,7 +619,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_consider_type
- developer = Developer.find(:first)
+ developer = Developer.first
special_project = SpecialProject.create("name" => "Special Project")
other_project = developer.projects.first
@@ -647,7 +654,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
categories(:technology).select_testing_posts(true).each do |o|
assert_respond_to o, :correctness_marker
end
- assert_respond_to categories(:technology).select_testing_posts.find(:first), :correctness_marker
+ assert_respond_to categories(:technology).select_testing_posts.first, :correctness_marker
end
def test_habtm_selects_all_columns_by_default
@@ -659,7 +666,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_join_table_alias
- assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL').size
+ assert_equal(
+ 3,
+ Developer.references(:developers_projects_join).scoped(
+ :includes => {:projects => :developers},
+ :where => 'developers_projects_join.joined_on IS NOT NULL'
+ ).to_a.size
+ )
end
def test_join_with_group
@@ -669,12 +682,18 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
Project.columns.each { |c| group << "projects.#{c.name}" }
- assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL', :group => group.join(",")).size
+ assert_equal(
+ 3,
+ Developer.references(:developers_projects_join).scoped(
+ :includes => {:projects => :developers}, :where => 'developers_projects_join.joined_on IS NOT NULL',
+ :group => group.join(",")
+ ).to_a.size
+ )
end
def test_find_grouped
- all_posts_from_category1 = Post.find(:all, :conditions => "category_id = 1", :joins => :categories)
- grouped_posts_of_category1 = Post.find(:all, :conditions => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories)
+ all_posts_from_category1 = Post.scoped(:where => "category_id = 1", :joins => :categories).all
+ grouped_posts_of_category1 = Post.scoped(:where => "category_id = 1", :group => "author_id", :select => 'count(posts.id) as posts_count', :joins => :categories).all
assert_equal 5, all_posts_from_category1.size
assert_equal 2, grouped_posts_of_category1.size
end
@@ -748,8 +767,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 1, project.developers.size
assert_equal 1, developer.projects.size
- assert_equal developer, project.developers.find(:first)
- assert_equal project, developer.projects.find(:first)
+ assert_equal developer, project.developers.first
+ assert_equal project, developer.projects.first
end
def test_self_referential_habtm_without_foreign_key_set_should_raise_exception
@@ -766,13 +785,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog')
end
- def test_counting_on_habtm_association_and_not_array
- david = Developer.find(1)
- # Extra parameter just to make sure we aren't falling back to
- # Array#count in Ruby >=1.8.7, which would raise an ArgumentError
- assert_nothing_raised { david.projects.count(:all, :conditions => '1=1') }
- end
-
def test_count
david = Developer.find(1)
assert_equal 2, david.projects.count
@@ -795,7 +807,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_association_proxy_transaction_method_starts_transaction_in_association_class
Post.expects(:transaction)
- Category.find(:first).posts.transaction do
+ Category.first.posts.transaction do
# nothing
end
end
@@ -805,12 +817,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
# clear cache possibly created by other tests
david.projects.reset_column_information
- # One query for columns, one for primary key
- assert_queries(2) { david.projects.columns; david.projects.columns }
+ assert_queries(1) { david.projects.columns; david.projects.columns }
## and again to verify that reset_column_information clears the cache correctly
david.projects.reset_column_information
- assert_queries(2) { david.projects.columns; david.projects.columns }
+ assert_queries(1) { david.projects.columns; david.projects.columns }
end
def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index cddd2a6f8c..8b384c2513 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -8,6 +8,7 @@ require 'models/reply'
require 'models/category'
require 'models/post'
require 'models/author'
+require 'models/essay'
require 'models/comment'
require 'models/person'
require 'models/reader'
@@ -41,12 +42,37 @@ class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase
end
end
+class HasManyAssociationsTestForCountDistinctWithFinderSql < ActiveRecord::TestCase
+ class Invoice < ActiveRecord::Base
+ has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT DISTINCT line_items.amount from line_items"
+ end
+
+ def test_should_count_distinct_results
+ invoice = Invoice.new
+ invoice.custom_line_items << LineItem.new(:amount => 0)
+ invoice.custom_line_items << LineItem.new(:amount => 0)
+ invoice.save!
+
+ assert_equal 1, invoice.custom_line_items.count
+ end
+end
+
+class HasManyAssociationsTestForReorderWithJoinDependency < ActiveRecord::TestCase
+ fixtures :authors, :posts, :comments
+
+ def test_should_generate_valid_sql
+ author = authors(:david)
+ # this can fail on adapters which require ORDER BY expressions to be included in the SELECT expression
+ # if the reorder clauses are not correctly handled
+ assert author.posts_with_comments_sorted_by_comment_id.where('comments.id > 0').reorder('posts.comments_count DESC', 'posts.taggings_count DESC').last
+ end
+end
class HasManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :categories, :companies, :developers, :projects,
:developers_projects, :topics, :authors, :comments,
- :people, :posts, :readers, :taggings, :cars
+ :people, :posts, :readers, :taggings, :cars, :essays
def setup
Client.destroyed_client_ids.clear
@@ -104,6 +130,28 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal car.id, bulb.car_id
end
+ def test_association_protect_foreign_key
+ invoice = Invoice.create
+
+ line_item = invoice.line_items.new
+ assert_equal invoice.id, line_item.invoice_id
+
+ line_item = invoice.line_items.new :invoice_id => invoice.id + 1
+ assert_equal invoice.id, line_item.invoice_id
+
+ line_item = invoice.line_items.build
+ assert_equal invoice.id, line_item.invoice_id
+
+ line_item = invoice.line_items.build :invoice_id => invoice.id + 1
+ assert_equal invoice.id, line_item.invoice_id
+
+ line_item = invoice.line_items.create
+ assert_equal invoice.id, line_item.invoice_id
+
+ line_item = invoice.line_items.create :invoice_id => invoice.id + 1
+ assert_equal invoice.id, line_item.invoice_id
+ end
+
def test_association_conditions_bypass_attribute_protection
car = Car.create(:name => 'honda')
@@ -173,48 +221,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal person, person.readers.first.person
end
- def test_find_or_create_by_resets_cached_counters
- person = Person.create! :first_name => 'tenderlove'
- post = Post.first
-
- assert_equal [], person.readers
- assert_nil person.readers.find_by_post_id(post.id)
-
- person.readers.find_or_create_by_post_id(post.id)
-
- assert_equal 1, person.readers.count
- assert_equal 1, person.readers.length
- assert_equal post, person.readers.first.post
- assert_equal person, person.readers.first.person
- end
-
def force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.each {|f| }
end
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
def test_counting_with_counter_sql
- assert_equal 2, Firm.find(:first, :order => "id").clients.count
+ assert_equal 2, Firm.scoped(:order => "id").first.clients.count
end
def test_counting
- assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count
- end
-
- def test_counting_with_empty_hash_conditions
- assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {})
- end
-
- def test_counting_with_single_conditions
- assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => ['name=?', "Microsoft"])
+ assert_equal 2, Firm.scoped(:order => "id").first.plain_clients.count
end
def test_counting_with_single_hash
- assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {:name => "Microsoft"})
+ assert_equal 1, Firm.scoped(:order => "id").first.plain_clients.where(:name => "Microsoft").count
end
def test_counting_with_column_name_and_hash
- assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:name)
+ assert_equal 2, Firm.scoped(:order => "id").first.plain_clients.count(:name)
end
def test_counting_with_association_limit
@@ -224,7 +249,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_finding
- assert_equal 2, Firm.find(:first, :order => "id").clients.length
+ assert_equal 2, Firm.scoped(:order => "id").first.clients.length
end
def test_finding_array_compatibility
@@ -233,14 +258,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_find_with_blank_conditions
[[], {}, nil, ""].each do |blank|
- assert_equal 2, Firm.find(:first, :order => "id").clients.find(:all, :conditions => blank).size
+ assert_equal 2, Firm.scoped(:order => "id").first.clients.where(blank).all.size
end
end
def test_find_many_with_merged_options
assert_equal 1, companies(:first_firm).limited_clients.size
- assert_equal 1, companies(:first_firm).limited_clients.find(:all).size
- assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size
+ assert_equal 1, companies(:first_firm).limited_clients.all.size
+ assert_equal 2, companies(:first_firm).limited_clients.limit(nil).all.size
end
def test_find_should_append_to_association_order
@@ -248,97 +273,65 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal ['id DESC', 'companies.id'], ordered_clients.order_values
end
- def test_dynamic_find_last_without_specified_order
- assert_equal companies(:second_client), companies(:first_firm).unsorted_clients.find_last_by_type('Client')
- end
-
def test_dynamic_find_should_respect_association_order
- assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find(:first, :conditions => "type = 'Client'")
+ assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").first
assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client')
end
- def test_dynamic_find_all_should_respect_association_order
- assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find(:all, :conditions => "type = 'Client'")
- assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client')
- end
-
- def test_dynamic_find_all_should_respect_association_limit
- assert_equal 1, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'").length
- assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length
- end
-
- def test_dynamic_find_all_limit_should_override_association_limit
- assert_equal 2, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'", :limit => 9_000).length
- assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length
- end
-
- def test_dynamic_find_all_should_respect_readonly_access
- companies(:first_firm).readonly_clients.find(:all).each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
- companies(:first_firm).readonly_clients.find(:all).each { |c| assert c.readonly? }
- end
-
- def test_dynamic_find_or_create_from_two_attributes_using_an_association
- author = authors(:david)
- number_of_posts = Post.count
- another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
- assert_equal number_of_posts + 1, Post.count
- assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
- assert another.persisted?
- end
-
def test_cant_save_has_many_readonly_association
authors(:david).readonly_comments.each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
authors(:david).readonly_comments.each { |c| assert c.readonly? }
end
- def test_triple_equality
- # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- assert !(Array === Firm.find(:first, :order => "id").clients)
- assert Firm.find(:first, :order => "id").clients === Array
- end
-
def test_finding_default_orders
- assert_equal "Summit", Firm.find(:first, :order => "id").clients.first.name
+ assert_equal "Summit", Firm.scoped(:order => "id").first.clients.first.name
end
def test_finding_with_different_class_name_and_order
- assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_sorted_desc.first.name
+ assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_sorted_desc.first.name
end
def test_finding_with_foreign_key
- assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_of_firm.first.name
+ assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_of_firm.first.name
end
def test_finding_with_condition
- assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms.first.name
+ assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_like_ms.first.name
end
def test_finding_with_condition_hash
- assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms_with_hash_conditions.first.name
+ assert_equal "Microsoft", Firm.scoped(:order => "id").first.clients_like_ms_with_hash_conditions.first.name
end
def test_finding_using_primary_key
- assert_equal "Summit", Firm.find(:first, :order => "id").clients_using_primary_key.first.name
+ assert_equal "Summit", Firm.scoped(:order => "id").first.clients_using_primary_key.first.name
end
def test_finding_using_sql
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
first_client = firm.clients_using_sql.first
assert_not_nil first_client
assert_equal "Microsoft", first_client.name
assert_equal 1, firm.clients_using_sql.size
- assert_equal 1, Firm.find(:first, :order => "id").clients_using_sql.size
+ assert_equal 1, Firm.scoped(:order => "id").first.clients_using_sql.size
+ end
+
+ def test_finding_using_sql_take_into_account_only_uniq_ids
+ firm = Firm.scoped(:order => "id").first
+ client = firm.clients_using_sql.first
+ assert_equal client, firm.clients_using_sql.find(client.id, client.id)
+ assert_equal client, firm.clients_using_sql.find(client.id, client.id.to_s)
end
def test_counting_using_sql
- assert_equal 1, Firm.find(:first, :order => "id").clients_using_counter_sql.size
- assert Firm.find(:first, :order => "id").clients_using_counter_sql.any?
- assert_equal 0, Firm.find(:first, :order => "id").clients_using_zero_counter_sql.size
- assert !Firm.find(:first, :order => "id").clients_using_zero_counter_sql.any?
+ assert_equal 1, Firm.scoped(:order => "id").first.clients_using_counter_sql.size
+ assert Firm.scoped(:order => "id").first.clients_using_counter_sql.any?
+ assert_equal 0, Firm.scoped(:order => "id").first.clients_using_zero_counter_sql.size
+ assert !Firm.scoped(:order => "id").first.clients_using_zero_counter_sql.any?
end
def test_counting_non_existant_items_using_sql
- assert_equal 0, Firm.find(:first, :order => "id").no_clients_using_counter_sql.size
+ assert_equal 0, Firm.scoped(:order => "id").first.no_clients_using_counter_sql.size
end
def test_counting_using_finder_sql
@@ -353,7 +346,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_ids
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find }
@@ -373,7 +366,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_string_ids_when_using_finder_sql
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
client = firm.clients_using_finder_sql.find("2")
assert_kind_of Client, client
@@ -389,9 +382,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_all
- firm = Firm.find(:first, :order => "id")
- assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length
- assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length
+ firm = Firm.scoped(:order => "id").first
+ assert_equal 2, firm.clients.scoped(:where => "#{QUOTED_TYPE} = 'Client'").all.length
+ assert_equal 1, firm.clients.scoped(:where => "name = 'Summit'").all.length
end
def test_find_each
@@ -410,7 +403,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = companies(:first_firm)
assert_queries(2) do
- firm.clients.find_each(:batch_size => 1, :conditions => {:name => "Microsoft"}) do |c|
+ firm.clients.where(name: 'Microsoft').find_each(batch_size: 1) do |c|
assert_equal firm.id, c.firm_id
assert_equal "Microsoft", c.name
end
@@ -435,29 +428,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_find_all_sanitized
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- firm = Firm.find(:first, :order => "id")
- summit = firm.clients.find(:all, :conditions => "name = 'Summit'")
- assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"])
- assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }])
+ firm = Firm.scoped(:order => "id").first
+ summit = firm.clients.scoped(:where => "name = 'Summit'").all
+ assert_equal summit, firm.clients.scoped(:where => ["name = ?", "Summit"]).all
+ assert_equal summit, firm.clients.scoped(:where => ["name = :name", { :name => "Summit" }]).all
end
def test_find_first
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
client2 = Client.find(2)
- assert_equal firm.clients.first, firm.clients.find(:first, :order => "id")
- assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'", :order => "id")
+ assert_equal firm.clients.first, firm.clients.scoped(:order => "id").first
+ assert_equal client2, firm.clients.scoped(:where => "#{QUOTED_TYPE} = 'Client'", :order => "id").first
end
def test_find_first_sanitized
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
client2 = Client.find(2)
- assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id")
- assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id")
+ assert_equal client2, firm.clients.scoped(:where => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id").first
+ assert_equal client2, firm.clients.scoped(:where => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id").first
end
def test_find_all_with_include_and_conditions
assert_nothing_raised do
- Developer.find(:all, :joins => :audit_logs, :conditions => {'audit_logs.message' => nil, :name => 'Smith'})
+ Developer.scoped(:joins => :audit_logs, :where => {'audit_logs.message' => nil, :name => 'Smith'}).all
end
end
@@ -467,8 +460,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_grouped
- all_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1")
- grouped_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count')
+ all_clients_of_firm1 = Client.scoped(:where => "firm_id = 1").all
+ grouped_clients_of_firm1 = Client.scoped(:where => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count').all
assert_equal 2, all_clients_of_firm1.size
assert_equal 1, grouped_clients_of_firm1.size
end
@@ -526,7 +519,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create_with_bang_on_has_many_raises_when_record_not_saved
assert_raise(ActiveRecord::RecordInvalid) do
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
firm.plain_clients.create!
end
end
@@ -691,45 +684,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !companies(:first_firm).clients_of_firm.loaded?
end
- def test_find_or_initialize
- the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client")
- assert_equal companies(:first_firm).id, the_client.firm_id
- assert_equal "Yet another client", the_client.name
- assert !the_client.persisted?
- end
-
- def test_find_or_create_updates_size
- number_of_clients = companies(:first_firm).clients.size
- the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
- assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
- assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client")
- assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
- end
-
- def test_find_or_initialize_updates_collection_size
- number_of_clients = companies(:first_firm).clients_of_firm.size
- companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
- assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size
- end
-
- def test_find_or_create_with_hash
- post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
- assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
- assert post.persisted?
- end
-
- def test_find_or_create_with_one_attribute_followed_by_hash
- post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
- assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
- assert post.persisted?
- end
-
- def test_find_or_create_should_work_with_block
- post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
- assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
- assert post.persisted?
- end
-
def test_deleting
force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
@@ -746,7 +700,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_deleting_updates_counter_cache
- topic = Topic.first(:order => "id ASC")
+ topic = Topic.order("id ASC").first
assert_equal topic.replies.to_a.size, topic.replies_count
topic.replies.delete(topic.replies.first)
@@ -838,12 +792,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
client_id = firm.clients_of_firm.first.id
assert_equal 1, firm.clients_of_firm.size
- cleared = firm.clients_of_firm.clear
+ firm.clients_of_firm.clear
assert_equal 0, firm.clients_of_firm.size
assert_equal 0, firm.clients_of_firm(true).size
assert_equal [], Client.destroyed_client_ids[firm.id]
- assert_equal firm.clients_of_firm.object_id, cleared.object_id
# Should not be destroyed since the association is not dependent.
assert_nothing_raised do
@@ -909,11 +862,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.create(:client_of => firm.id, :name => "BigShot Inc.")
Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
# only one of two clients is included in the association due to the :conditions key
- assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 2, Client.where(client_of: firm.id).size
assert_equal 1, firm.dependent_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
- assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, Client.where(client_of: firm.id).size
end
def test_dependent_association_respects_optional_sanitized_conditions_on_delete
@@ -921,11 +874,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.create(:client_of => firm.id, :name => "BigShot Inc.")
Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
# only one of two clients is included in the association due to the :conditions key
- assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 2, Client.where(client_of: firm.id).size
assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
- assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, Client.where(client_of: firm.id).size
end
def test_dependent_association_respects_optional_hash_conditions_on_delete
@@ -933,22 +886,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.create(:client_of => firm.id, :name => "BigShot Inc.")
Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
# only one of two clients is included in the association due to the :conditions key
- assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 2, Client.where(client_of: firm.id).size
assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
firm.destroy
# only the correctly associated client should have been deleted
- assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, Client.where(client_of: firm.id).size
end
def test_delete_all_association_with_primary_key_deletes_correct_records
- firm = Firm.find(:first)
+ firm = Firm.first
# break the vanilla firm_id foreign key
assert_equal 2, firm.clients.count
firm.clients.first.update_column(:firm_id, nil)
assert_equal 1, firm.clients(true).count
assert_equal 1, firm.clients_using_primary_key_with_delete_all.count
old_record = firm.clients_using_primary_key_with_delete_all.first
- firm = Firm.find(:first)
+ firm = Firm.first
firm.destroy
assert_nil Client.find_by_id(old_record.id)
end
@@ -1056,7 +1009,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = companies(:first_firm)
assert_equal 2, firm.clients.size
firm.destroy
- assert Client.find(:all, :conditions => "firm_id=#{firm.id}").empty?
+ assert Client.scoped(:where => "firm_id=#{firm.id}").all.empty?
end
def test_dependence_for_associations_with_hash_condition
@@ -1066,7 +1019,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_destroy_dependent_when_deleted_from_association
# sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
assert_equal 2, firm.clients.size
client = firm.clients.first
@@ -1094,7 +1047,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm.destroy rescue "do nothing"
- assert_equal 2, Client.find(:all, :conditions => "firm_id=#{firm.id}").size
+ assert_equal 2, Client.scoped(:where => "firm_id=#{firm.id}").all.size
end
def test_dependence_on_account
@@ -1114,16 +1067,42 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_nil companies(:leetsoft).reload.client_of
assert_nil companies(:jadedpixel).reload.client_of
-
assert_equal num_accounts, Account.count
end
def test_restrict
- firm = RestrictedFirm.new(:name => 'restrict')
- firm.save!
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
firm.companies.create(:name => 'child')
+
assert !firm.companies.empty?
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.companies.exists?(:name => 'child')
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
+
+ def test_restrict_when_dependent_restrict_raises_config_set_to_false
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = false
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
+ firm.companies.create(:name => 'child')
+
+ assert !firm.companies.empty?
+
+ firm.destroy
+
+ assert !firm.errors.empty?
+
+ assert_equal "Cannot delete record because dependent companies exist", firm.errors[:base].first
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.companies.exists?(:name => 'child')
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
end
def test_included_in_collection
@@ -1131,16 +1110,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_adding_array_and_collection
- assert_nothing_raised { Firm.find(:first).clients + Firm.find(:all).last.clients }
- end
-
- def test_find_all_without_conditions
- firm = companies(:first_firm)
- assert_equal 2, firm.clients.find(:all).length
+ assert_nothing_raised { Firm.first.clients + Firm.all.last.clients }
end
def test_replace_with_less
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
firm.clients = [companies(:first_client)]
assert firm.save, "Could not save firm"
firm.reload
@@ -1154,7 +1128,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_replace_with_new
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
firm.save
firm.reload
@@ -1227,6 +1201,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert company.clients_using_sql.loaded?
end
+ def test_get_ids_for_ordered_association
+ assert_equal [companies(:second_client).id, companies(:first_client).id], companies(:first_firm).clients_ordered_by_name_ids
+ end
+
def test_assign_ids_ignoring_blanks
firm = Firm.create!(:name => 'Apple')
firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, '']
@@ -1250,29 +1228,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_dynamic_find_should_respect_association_order_for_through
- assert_equal Comment.find(10), authors(:david).comments_desc.find(:first, :conditions => "comments.type = 'SpecialComment'")
+ assert_equal Comment.find(10), authors(:david).comments_desc.scoped(:where => "comments.type = 'SpecialComment'").first
assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type('SpecialComment')
end
- def test_dynamic_find_all_should_respect_association_order_for_through
- assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find(:all, :conditions => "comments.type = 'SpecialComment'")
- assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment')
- end
-
- def test_dynamic_find_all_should_respect_association_limit_for_through
- assert_equal 1, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'").length
- assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length
- end
-
- def test_dynamic_find_all_order_should_override_association_limit_for_through
- assert_equal 4, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'", :limit => 9_000).length
- assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length
- end
-
- def test_find_all_include_over_the_same_table_for_through
- assert_equal 2, people(:michael).posts.find(:all, :include => :people).length
- end
-
def test_has_many_through_respects_hash_conditions
assert_equal authors(:david).hello_posts, authors(:david).hello_posts_with_hash_conditions
assert_equal authors(:david).hello_post_comments, authors(:david).hello_post_comments_with_hash_conditions
@@ -1280,7 +1239,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_include_uses_array_include_after_loaded
firm = companies(:first_firm)
- firm.clients.class # force load target
+ firm.clients.load_target
client = firm.clients.first
@@ -1330,7 +1289,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query
firm = companies(:first_firm)
- firm.clients.class # force load target
+ firm.clients.load_target
assert firm.clients.loaded?
assert_no_queries do
@@ -1376,14 +1335,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
end
- def test_calling_first_or_last_with_find_options_on_loaded_association_should_fetch_with_query
- firm = companies(:first_firm)
- firm.clients.class # force load target
+ def test_custom_primary_key_on_new_record_should_fetch_with_query
+ author = Author.new(:name => "David")
+ assert !author.essays.loaded?
- assert_queries 2 do
- assert firm.clients.loaded?
- firm.clients.first(:order => 'name')
- firm.clients.last(:order => 'name')
+ assert_queries 1 do
+ assert_equal 1, author.essays.size
+ end
+
+ assert_equal author.essays, Essay.where(writer_id: "David")
+
+ end
+
+ def test_has_many_custom_primary_key
+ david = authors(:david)
+ assert_equal david.essays, Essay.where(writer_id: "David")
+ end
+
+ def test_blank_custom_primary_key_on_new_record_should_not_run_queries
+ author = Author.new
+ assert !author.essays.loaded?
+
+ assert_queries 0 do
+ assert_equal 0, author.essays.size
end
end
@@ -1444,11 +1418,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = Namespaced::Firm.create({ :name => 'Some Company' })
firm.clients.create({ :name => 'Some Client' })
- stats = Namespaced::Firm.find(firm.id, {
+ stats = Namespaced::Firm.scoped(
:select => "#{Namespaced::Firm.table_name}.id, COUNT(#{Namespaced::Client.table_name}.id) AS num_clients",
:joins => :clients,
:group => "#{Namespaced::Firm.table_name}.id"
- })
+ ).find firm.id
assert_equal 1, stats.num_clients.to_i
ensure
@@ -1457,7 +1431,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_association_proxy_transaction_method_starts_transaction_in_association_class
Comment.expects(:transaction)
- Post.find(:first).comments.transaction do
+ Post.first.comments.transaction do
# nothing
end
end
@@ -1474,7 +1448,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_creating_using_primary_key
- firm = Firm.find(:first, :order => "id")
+ firm = Firm.scoped(:order => "id").first
client = firm.clients_using_primary_key.create!(:name => 'test')
assert_equal firm.name, client.firm_name
end
@@ -1597,4 +1571,59 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal [bulb2], car.bulbs
assert_equal [bulb2], car.reload.bulbs
end
+
+ def test_replace_returns_target
+ car = Car.create(:name => 'honda')
+ bulb1 = car.bulbs.create
+ bulb2 = car.bulbs.create
+ bulb3 = Bulb.create
+
+ assert_equal [bulb1, bulb2], car.bulbs
+ result = car.bulbs.replace([bulb3, bulb1])
+ assert_equal [bulb1, bulb3], car.bulbs
+ assert_equal [bulb1, bulb3], result
+ end
+
+ def test_building_has_many_association_with_restrict_dependency
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_deprecated { klass.has_many :companies, :dependent => :restrict }
+ assert_not_deprecated { klass.has_many :companies }
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
+
+ def test_collection_association_with_private_kernel_method
+ firm = companies(:first_firm)
+ assert_equal [accounts(:signals37)], firm.accounts.open
+ end
+
+ test "first_or_initialize adds the record to the association" do
+ firm = Firm.create! name: 'omg'
+ client = firm.clients_of_firm.first_or_initialize
+ assert_equal [client], firm.clients_of_firm
+ end
+
+ test "first_or_create adds the record to the association" do
+ firm = Firm.create! name: 'omg'
+ firm.clients_of_firm.load_target
+ client = firm.clients_of_firm.first_or_create name: 'lol'
+ assert_equal [client], firm.clients_of_firm
+ assert_equal [client], firm.reload.clients_of_firm
+ end
+
+ test "delete_all, when not loaded, doesn't load the records" do
+ post = posts(:welcome)
+
+ assert post.taggings_with_delete_all.count > 0
+ assert !post.taggings_with_delete_all.loaded?
+
+ # 2 queries: one DELETE and another to update the counter cache
+ assert_queries(2) do
+ post.taggings_with_delete_all.delete_all
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index b703c96ec1..1c06007d86 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -44,17 +44,33 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_associate_existing
- posts(:thinking); people(:david) # Warm cache
+ post = posts(:thinking)
+ person = people(:david)
assert_queries(1) do
- posts(:thinking).people << people(:david)
+ post.people << person
end
assert_queries(1) do
- assert posts(:thinking).people.include?(people(:david))
+ assert post.people.include?(person)
end
- assert posts(:thinking).reload.people(true).include?(people(:david))
+ assert post.reload.people(true).include?(person)
+ end
+
+ def test_associate_existing_with_strict_mass_assignment_sanitizer
+ SecureReader.mass_assignment_sanitizer = :strict
+
+ SecureReader.new
+
+ post = posts(:thinking)
+ person = people(:david)
+
+ assert_queries(1) do
+ post.secure_people << person
+ end
+ ensure
+ SecureReader.mass_assignment_sanitizer = :logger
end
def test_associate_existing_record_twice_should_add_to_target_twice
@@ -67,6 +83,31 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
end
+ def test_associate_existing_record_twice_should_add_records_twice
+ post = posts(:thinking)
+ person = people(:david)
+
+ assert_difference 'post.people.count', 2 do
+ post.people << person
+ post.people << person
+ end
+ end
+
+ def test_add_two_instance_and_then_deleting
+ post = posts(:thinking)
+ person = people(:david)
+
+ post.people << person
+ post.people << person
+
+ counts = ['post.people.count', 'post.people.to_a.count', 'post.readers.count', 'post.readers.to_a.count']
+ assert_difference counts, -2 do
+ post.people.delete(person)
+ end
+
+ assert !post.people.reload.include?(person)
+ end
+
def test_associating_new
assert_queries(1) { posts(:thinking) }
new_person = nil # so block binding catches it
@@ -492,7 +533,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
end
def test_count_with_include_should_alias_join_table
- assert_equal 2, people(:michael).posts.count(:include => :readers)
+ assert_equal 2, people(:michael).posts.includes(:readers).count
end
def test_inner_join_with_quoted_table_name
@@ -503,6 +544,12 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [posts(:welcome).id, posts(:authorless).id].sort, people(:michael).post_ids.sort
end
+ def test_get_ids_for_has_many_through_with_conditions_should_not_preload
+ Tagging.create!(:taggable_type => 'Post', :taggable_id => posts(:welcome).id, :tag => tags(:misc))
+ ActiveRecord::Associations::Preloader.expects(:new).never
+ posts(:welcome).misc_tag_ids
+ end
+
def test_get_ids_for_loaded_associations
person = people(:michael)
person.posts(true)
@@ -521,7 +568,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_association_proxy_transaction_method_starts_transaction_in_association_class
Tag.expects(:transaction)
- Post.find(:first).tags.transaction do
+ Post.first.tags.transaction do
# nothing
end
end
@@ -604,7 +651,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_collection_singular_ids_setter
company = companies(:rails_core)
- dev = Developer.find(:first)
+ dev = Developer.first
company.developer_ids = [dev.id]
assert_equal [dev], company.developers
@@ -624,7 +671,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
company = companies(:rails_core)
- ids = [Developer.find(:first).id, -9999]
+ ids = [Developer.first.id, -9999]
assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids}
end
@@ -657,6 +704,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert author.comments.include?(comment)
end
+ def test_through_association_readonly_should_be_false
+ assert !people(:michael).posts.first.readonly?
+ assert !people(:michael).posts.all.first.readonly?
+ end
+
+ def test_can_update_through_association
+ assert_nothing_raised do
+ people(:michael).posts.first.update_attributes!(:title => "Can write")
+ end
+ end
+
def test_has_many_through_polymorphic_with_primary_key_option
assert_equal [categories(:general)], authors(:david).essay_categories
@@ -707,7 +765,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_select_chosen_fields_only
author = authors(:david)
- assert_equal ['body'], author.comments.select('comments.body').first.attributes.keys
+ assert_equal ['body', 'id'].sort, author.comments.select('comments.body').first.attributes.keys.sort
end
def test_get_has_many_through_belongs_to_ids_with_conditions
@@ -816,7 +874,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_preloading_empty_through_association_via_joins
person = Person.create!(:first_name => "Gaga")
- person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').includes(:posts).to_a.first
+ person = Person.where(:id => person.id).where('readers.id = 1 or 1=1').references(:readers).includes(:posts).to_a.first
assert person.posts.loaded?, 'person.posts should be loaded'
assert_equal [], person.posts
@@ -825,4 +883,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_explicitly_joining_join_table
assert_equal owners(:blackbeard).toys, owners(:blackbeard).toys.with_pet
end
+
+ def test_has_many_through_with_polymorphic_source
+ post = tags(:general).tagged_posts.create! :title => "foo", :body => "bar"
+ assert_equal [tags(:general)], post.reload.tags
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 26931e3e85..88ec65706c 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -25,13 +25,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_queries(1) { assert_nil firm.account }
assert_queries(0) { assert_nil firm.account }
- firms = Firm.find(:all, :include => :account)
+ firms = Firm.scoped(:includes => :account).all
assert_queries(0) { firms.each(&:account) }
end
def test_with_select
assert_equal Firm.find(1).account_with_select.attributes.size, 2
- assert_equal Firm.find(1, :include => :account_with_select).account_with_select.attributes.size, 2
+ assert_equal Firm.scoped(:includes => :account_with_select).find(1).account_with_select.attributes.size, 2
end
def test_finding_using_primary_key
@@ -157,11 +157,62 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
end
def test_dependence_with_restrict
- firm = RestrictedFirm.new(:name => 'restrict')
- firm.save!
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
firm.create_account(:credit_limit => 10)
+
assert_not_nil firm.account
+
assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy }
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.account.present?
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
+
+ def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = false
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
+ firm.create_account(:credit_limit => 10)
+
+ assert_not_nil firm.account
+
+ firm.destroy
+
+ assert !firm.errors.empty?
+ assert_equal "Cannot delete record because a dependent account exists", firm.errors[:base].first
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.account.present?
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
+
+ def test_dependence_with_restrict_with_dependent_restrict_raises_config_set_to_false_and_attribute_name
+ old_backend = I18n.backend
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations 'en', :activerecord => {:attributes => {:restricted_firm => {:account => "account model"}}}
+
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = false
+
+ firm = RestrictedFirm.create!(:name => 'restrict')
+ firm.create_account(:credit_limit => 10)
+
+ assert_not_nil firm.account
+
+ firm.destroy
+
+ assert !firm.errors.empty?
+ assert_equal "Cannot delete record because a dependent account model exists", firm.errors[:base].first
+ assert RestrictedFirm.exists?(:name => 'restrict')
+ assert firm.account.present?
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ I18n.backend = old_backend
end
def test_successful_build_association
@@ -243,13 +294,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_dependence_with_missing_association_and_nullify
Account.destroy_all
- firm = DependentFirm.find(:first)
+ firm = DependentFirm.first
assert_nil firm.account
firm.destroy
end
def test_finding_with_interpolated_condition
- firm = Firm.find(:first)
+ firm = Firm.first
superior = firm.clients.create(:name => 'SuperiorCo')
superior.rating = 10
superior.save
@@ -295,14 +346,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised do
Firm.find(@firm.id).save!
- Firm.find(@firm.id, :include => :account).save!
+ Firm.scoped(:includes => :account).find(@firm.id).save!
end
@firm.account.destroy
assert_nothing_raised do
Firm.find(@firm.id).save!
- Firm.find(@firm.id, :include => :account).save!
+ Firm.scoped(:includes => :account).find(@firm.id).save!
end
end
@@ -397,6 +448,22 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal car.id, bulb.car_id
end
+ def test_association_protect_foreign_key
+ pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
+
+ ship = pirate.build_ship
+ assert_equal pirate.id, ship.pirate_id
+
+ ship = pirate.build_ship :pirate_id => pirate.id + 1
+ assert_equal pirate.id, ship.pirate_id
+
+ ship = pirate.create_ship
+ assert_equal pirate.id, ship.pirate_id
+
+ ship = pirate.create_ship :pirate_id => pirate.id + 1
+ assert_equal pirate.id, ship.pirate_id
+ end
+
def test_association_conditions_bypass_attribute_protection
car = Car.create(:name => 'honda')
@@ -456,4 +523,16 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal car.id, bulb.attributes_after_initialize['car_id']
end
+
+ def test_building_has_one_association_with_dependent_restrict
+ option_before = ActiveRecord::Base.dependent_restrict_raises
+ ActiveRecord::Base.dependent_restrict_raises = true
+
+ klass = Class.new(ActiveRecord::Base)
+
+ assert_deprecated { klass.has_one :account, :dependent => :restrict }
+ assert_not_deprecated { klass.has_one :account }
+ ensure
+ ActiveRecord::Base.dependent_restrict_raises = option_before
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 2503349c08..94b9639e57 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -73,7 +73,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_eager_loading
members = assert_queries(3) do #base table, through table, clubs table
- Member.find(:all, :include => :club, :conditions => ["name = ?", "Groucho Marx"])
+ Member.scoped(:includes => :club, :where => ["name = ?", "Groucho Marx"]).all
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].club}
@@ -81,7 +81,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_eager_loading_through_polymorphic
members = assert_queries(3) do #base table, through table, clubs table
- Member.find(:all, :include => :sponsor_club, :conditions => ["name = ?", "Groucho Marx"])
+ Member.scoped(:includes => :sponsor_club, :where => ["name = ?", "Groucho Marx"]).all
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].sponsor_club}
@@ -89,14 +89,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_with_conditions_eager_loading
# conditions on the through table
- assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :favourite_club).favourite_club
+ assert_equal clubs(:moustache_club), Member.scoped(:includes => :favourite_club).find(@member.id).favourite_club
memberships(:membership_of_favourite_club).update_column(:favourite, false)
- assert_equal nil, Member.find(@member.id, :include => :favourite_club).reload.favourite_club
+ assert_equal nil, Member.scoped(:includes => :favourite_club).find(@member.id).reload.favourite_club
# conditions on the source table
- assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :hairy_club).hairy_club
+ assert_equal clubs(:moustache_club), Member.scoped(:includes => :hairy_club).find(@member.id).hairy_club
clubs(:moustache_club).update_column(:name, "Association of Clean-Shaven Persons")
- assert_equal nil, Member.find(@member.id, :include => :hairy_club).reload.hairy_club
+ assert_equal nil, Member.scoped(:includes => :hairy_club).find(@member.id).reload.hairy_club
end
def test_has_one_through_polymorphic_with_source_type
@@ -104,14 +104,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
end
def test_eager_has_one_through_polymorphic_with_source_type
- clubs = Club.find(:all, :include => :sponsored_member, :conditions => ["name = ?","Moustache and Eyebrow Fancier Club"])
+ clubs = Club.scoped(:includes => :sponsored_member, :where => ["name = ?","Moustache and Eyebrow Fancier Club"]).all
# Only the eyebrow fanciers club has a sponsored_member
assert_not_nil assert_no_queries {clubs[0].sponsored_member}
end
def test_has_one_through_nonpreload_eagerloading
members = assert_queries(1) do
- Member.find(:all, :include => :club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
+ Member.scoped(:includes => :club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').all #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].club}
@@ -119,7 +119,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eager_loading_through_polymorphic
members = assert_queries(1) do
- Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name') #force fallback
+ Member.scoped(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name').all #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries {members[0].sponsor_club}
@@ -128,7 +128,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_nonpreload_eager_loading_through_polymorphic_with_more_than_one_through_record
Sponsor.new(:sponsor_club => clubs(:crazy_club), :sponsorable => members(:groucho)).save!
members = assert_queries(1) do
- Member.find(:all, :include => :sponsor_club, :conditions => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC') #force fallback
+ Member.scoped(:includes => :sponsor_club, :where => ["members.name = ?", "Groucho Marx"], :order => 'clubs.name DESC').all #force fallback
end
assert_equal 1, members.size
assert_not_nil assert_no_queries { members[0].sponsor_club }
@@ -197,7 +197,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
@member.member_detail = @member_detail
@member.organization = @organization
@member_details = assert_queries(3) do
- MemberDetail.find(:all, :include => :member_type)
+ MemberDetail.scoped(:includes => :member_type).all
end
@new_detail = @member_details[0]
assert @new_detail.send(:association, :member_type).loaded?
@@ -210,14 +210,14 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert_nothing_raised do
Club.find(@club.id).save!
- Club.find(@club.id, :include => :sponsored_member).save!
+ Club.scoped(:includes => :sponsored_member).find(@club.id).save!
end
@club.sponsor.destroy
assert_nothing_raised do
Club.find(@club.id).save!
- Club.find(@club.id, :include => :sponsored_member).save!
+ Club.scoped(:includes => :sponsored_member).find(@club.id).save!
end
end
diff --git a/activerecord/test/cases/associations/identity_map_test.rb b/activerecord/test/cases/associations/identity_map_test.rb
deleted file mode 100644
index 9b8635774c..0000000000
--- a/activerecord/test/cases/associations/identity_map_test.rb
+++ /dev/null
@@ -1,137 +0,0 @@
-require "cases/helper"
-require 'models/author'
-require 'models/post'
-
-if ActiveRecord::IdentityMap.enabled?
-class InverseHasManyIdentityMapTest < ActiveRecord::TestCase
- fixtures :authors, :posts
-
- def test_parent_instance_should_be_shared_with_every_child_on_find
- m = Author.first
- is = m.posts
- is.each do |i|
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance"
- end
- end
-
- def test_parent_instance_should_be_shared_with_eager_loaded_children
- m = Author.find(:first, :include => :posts)
- is = m.posts
- is.each do |i|
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance"
- end
-
- m = Author.find(:first, :include => :posts, :order => 'posts.id')
- is = m.posts
- is.each do |i|
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to child-owned instance"
- end
- end
-
- def test_parent_instance_should_be_shared_with_newly_built_child
- m = Author.first
- i = m.posts.build(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
- assert_not_nil i.author
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to just-built-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_newly_block_style_built_child
- m = Author.first
- i = m.posts.build {|ii| ii.title = 'Industrial Revolution Re-enactment'; ii.body = 'Lorem ipsum'}
- assert_not_nil i.title, "Child attributes supplied to build via blocks should be populated"
- assert_not_nil i.author
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to just-built-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_newly_created_child
- m = Author.first
- i = m.posts.create(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
- assert_not_nil i.author
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child
- m = Author.first
- i = m.posts.create!(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
- assert_not_nil i.author
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_newly_block_style_created_child
- m = Author.first
- i = m.posts.create {|ii| ii.title = 'Industrial Revolution Re-enactment'; ii.body = 'Lorem ipsum'}
- assert_not_nil i.title, "Child attributes supplied to create via blocks should be populated"
- assert_not_nil i.author
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_poked_in_child
- m = Author.first
- i = Post.create(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
- m.posts << i
- assert_not_nil i.author
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to newly-created-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_replaced_via_accessor_children
- m = Author.first
- i = Post.new(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
- m.posts = [i]
- assert_same m, i.author
- assert_not_nil i.author
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to replaced-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_replaced_via_method_children
- m = Author.first
- i = Post.new(:title => 'Industrial Revolution Re-enactment', :body => 'Lorem ipsum')
- m.posts = [i]
- assert_not_nil i.author
- assert_equal m.name, i.author.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to parent instance"
- i.author.name = 'Mungo'
- assert_equal m.name, i.author.name, "Name of man should be the same after changes to replaced-child-owned instance"
- end
-end
-end
diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb
index e5e9ca6131..1d61d5c474 100644
--- a/activerecord/test/cases/associations/inner_join_association_test.rb
+++ b/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -67,22 +67,22 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase
def test_find_with_implicit_inner_joins_does_not_set_associations
authors = Author.joins(:posts).select('authors.*')
assert !authors.empty?, "expected authors to be non-empty"
- assert authors.all? {|a| !a.send(:instance_variable_names).include?("@posts")}, "expected no authors to have the @posts association loaded"
+ assert authors.all? { |a| !a.instance_variable_defined?(:@posts) }, "expected no authors to have the @posts association loaded"
end
def test_count_honors_implicit_inner_joins
real_count = Author.scoped.to_a.sum{|a| a.posts.count }
- assert_equal real_count, Author.count(:joins => :posts), "plain inner join count should match the number of referenced posts records"
+ assert_equal real_count, Author.joins(:posts).count, "plain inner join count should match the number of referenced posts records"
end
def test_calculate_honors_implicit_inner_joins
real_count = Author.scoped.to_a.sum{|a| a.posts.count }
- assert_equal real_count, Author.calculate(:count, 'authors.id', :joins => :posts), "plain inner join count should match the number of referenced posts records"
+ assert_equal real_count, Author.joins(:posts).calculate(:count, 'authors.id'), "plain inner join count should match the number of referenced posts records"
end
def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions
real_count = Author.scoped.to_a.select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length
- authors_with_welcoming_post_titles = Author.calculate(:count, 'authors.id', :joins => :posts, :distinct => true, :conditions => "posts.title like 'Welcome%'")
+ authors_with_welcoming_post_titles = Author.scoped(:joins => :posts, :where => "posts.title like 'Welcome%'").calculate(:count, 'authors.id', :distinct => true)
assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'"
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 76282213d8..f35ffb2994 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -96,7 +96,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
def test_parent_instance_should_be_shared_with_eager_loaded_child_on_find
- m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face)
+ m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :face).first
f = m.face
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
m.name = 'Bongo'
@@ -104,7 +104,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
f.man.name = 'Mungo'
assert_equal m.name, f.man.name, "Name of man should be the same after changes to child-owned instance"
- m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :face, :order => 'faces.id')
+ m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :face, :order => 'faces.id').first
f = m.face
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
m.name = 'Bongo'
@@ -114,7 +114,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_built_child
- m = Man.find(:first)
+ m = Man.first
f = m.build_face(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
@@ -125,7 +125,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_created_child
- m = Man.find(:first)
+ m = Man.first
f = m.create_face(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
@@ -136,7 +136,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method
- m = Man.find(:first)
+ m = Man.first
f = m.create_face!(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
@@ -147,7 +147,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_replaced_via_accessor_child
- m = Man.find(:first)
+ m = Man.first
f = Face.new(:description => 'haunted')
m.face = f
assert_not_nil f.man
@@ -159,7 +159,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
- assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).dirty_face }
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.dirty_face }
end
end
@@ -179,7 +179,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_eager_loaded_children
- m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests)
+ m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :interests).first
is = m.interests
is.each do |i|
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -189,7 +189,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to child-owned instance"
end
- m = Man.find(:first, :conditions => {:name => 'Gordon'}, :include => :interests, :order => 'interests.id')
+ m = Man.scoped(:where => {:name => 'Gordon'}, :includes => :interests, :order => 'interests.id').first
is = m.interests
is.each do |i|
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -201,7 +201,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_block_style_built_child
- m = Man.find(:first)
+ m = Man.first
i = m.interests.build {|ii| ii.topic = 'Industrial Revolution Re-enactment'}
assert_not_nil i.topic, "Child attributes supplied to build via blocks should be populated"
assert_not_nil i.man
@@ -213,7 +213,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child
- m = Man.find(:first)
+ m = Man.first
i = m.interests.create!(:topic => 'Industrial Revolution Re-enactment')
assert_not_nil i.man
assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
@@ -224,7 +224,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_block_style_created_child
- m = Man.find(:first)
+ m = Man.first
i = m.interests.create {|ii| ii.topic = 'Industrial Revolution Re-enactment'}
assert_not_nil i.topic, "Child attributes supplied to create via blocks should be populated"
assert_not_nil i.man
@@ -248,7 +248,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_replaced_via_accessor_children
- m = Man.find(:first)
+ m = Man.first
i = Interest.new(:topic => 'Industrial Revolution Re-enactment')
m.interests = [i]
assert_not_nil i.man
@@ -260,7 +260,7 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
- assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).secret_interests }
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests }
end
end
@@ -278,7 +278,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
end
def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find
- f = Face.find(:first, :include => :man, :conditions => {:description => 'trusting'})
+ f = Face.scoped(:includes => :man, :where => {:description => 'trusting'}).first
m = f.man
assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -286,7 +286,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
m.face.description = 'pleasing'
assert_equal f.description, m.face.description, "Description of face should be the same after changes to parent-owned instance"
- f = Face.find(:first, :include => :man, :order => 'men.id', :conditions => {:description => 'trusting'})
+ f = Face.scoped(:includes => :man, :order => 'men.id', :where => {:description => 'trusting'}).first
m = f.man
assert_equal f.description, m.face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -331,7 +331,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
end
def test_child_instance_should_be_shared_with_replaced_via_accessor_parent
- f = Face.find(:first)
+ f = Face.first
m = Man.new(:name => 'Charles')
f.man = m
assert_not_nil m.face
@@ -343,7 +343,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
end
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
- assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_man }
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_man }
end
end
@@ -351,7 +351,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
fixtures :men, :faces, :interests
def test_child_instance_should_be_shared_with_parent_on_find
- f = Face.find(:first, :conditions => {:description => 'confused'})
+ f = Face.scoped(:where => {:description => 'confused'}).first
m = f.polymorphic_man
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -361,7 +361,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
end
def test_eager_loaded_child_instance_should_be_shared_with_parent_on_find
- f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man)
+ f = Face.scoped(:where => {:description => 'confused'}, :includes => :man).first
m = f.polymorphic_man
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -369,7 +369,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
m.polymorphic_face.description = 'pleasing'
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same after changes to parent-owned instance"
- f = Face.find(:first, :conditions => {:description => 'confused'}, :include => :man, :order => 'men.id')
+ f = Face.scoped(:where => {:description => 'confused'}, :includes => :man, :order => 'men.id').first
m = f.polymorphic_man
assert_equal f.description, m.polymorphic_face.description, "Description of face should be the same before changes to child instance"
f.description = 'gormless'
@@ -421,19 +421,19 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
def test_trying_to_access_inverses_that_dont_exist_shouldnt_raise_an_error
# Ideally this would, if only for symmetry's sake with other association types
- assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_polymorphic_man }
+ assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man }
end
def test_trying_to_set_polymorphic_inverses_that_dont_exist_at_all_should_raise_an_error
# fails because no class has the correct inverse_of for horrible_polymorphic_man
- assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).horrible_polymorphic_man = Man.first }
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.horrible_polymorphic_man = Man.first }
end
def test_trying_to_set_polymorphic_inverses_that_dont_exist_on_the_instance_being_set_should_raise_an_error
# passes because Man does have the correct inverse_of
- assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).polymorphic_man = Man.first }
+ assert_nothing_raised(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Man.first }
# fails because Interest does have the correct inverse_of
- assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.find(:first).polymorphic_man = Interest.first }
+ assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Face.first.polymorphic_man = Interest.first }
end
end
@@ -444,7 +444,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
def test_that_we_can_load_associations_that_have_the_same_reciprocal_name_from_different_models
assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
- i = Interest.find(:first)
+ i = Interest.first
i.zine
i.man
end
@@ -452,7 +452,7 @@ class InverseMultipleHasManyInversesForSameModel < ActiveRecord::TestCase
def test_that_we_can_create_associations_that_have_the_same_reciprocal_name_from_different_models
assert_nothing_raised(ActiveRecord::AssociationTypeMismatch) do
- i = Interest.find(:first)
+ i = Interest.first
i.build_zine(:title => 'Get Some in Winter! 2008')
i.build_man(:name => 'Gordon')
i.save!
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 4ce8b85098..ecc676f300 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -46,16 +46,12 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert !authors(:mary).unique_categorized_posts.loaded?
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count }
assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count(:title) }
- assert_queries(1) { assert_equal 0, author.unique_categorized_posts.count(:title, :conditions => "title is NULL") }
+ assert_queries(1) { assert_equal 0, author.unique_categorized_posts.where(title: nil).count(:title) }
assert !authors(:mary).unique_categorized_posts.loaded?
end
def test_has_many_uniq_through_find
- assert_equal 1, authors(:mary).unique_categorized_posts.find(:all).size
- end
-
- def test_has_many_uniq_through_dynamic_find
- assert_equal 1, authors(:mary).unique_categorized_posts.find_all_by_title("So I was thinking").size
+ assert_equal 1, authors(:mary).unique_categorized_posts.all.size
end
def test_polymorphic_has_many_going_through_join_model
@@ -71,7 +67,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_polymorphic_has_many_going_through_join_model_with_find
- assert_equal tags(:general), tag = posts(:welcome).tags.find(:first)
+ assert_equal tags(:general), tag = posts(:welcome).tags.first
assert_no_queries do
tag.tagging
end
@@ -85,7 +81,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection_with_find
- assert_equal tags(:general), tag = posts(:welcome).funky_tags.find(:first)
+ assert_equal tags(:general), tag = posts(:welcome).funky_tags.first
assert_no_queries do
tag.tagging
end
@@ -237,8 +233,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_has_many_through
- posts = Post.find(:all, :order => 'posts.id')
- posts_with_authors = Post.find(:all, :include => :authors, :order => 'posts.id')
+ posts = Post.scoped(:order => 'posts.id').all
+ posts_with_authors = Post.scoped(:includes => :authors, :order => 'posts.id').all
assert_equal posts.length, posts_with_authors.length
posts.length.times do |i|
assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length }
@@ -246,7 +242,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_polymorphic_has_one
- post = Post.find_by_id(posts(:welcome).id, :include => :tagging)
+ post = Post.includes(:tagging).find posts(:welcome).id
tagging = taggings(:welcome_general)
assert_no_queries do
assert_equal tagging, post.tagging
@@ -254,7 +250,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_polymorphic_has_one_defined_in_abstract_parent
- item = Item.find_by_id(items(:dvd).id, :include => :tagging)
+ item = Item.includes(:tagging).find items(:dvd).id
tagging = taggings(:godfather)
assert_no_queries do
assert_equal tagging, item.tagging
@@ -262,8 +258,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_polymorphic_has_many_through
- posts = Post.find(:all, :order => 'posts.id')
- posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id')
+ posts = Post.scoped(:order => 'posts.id').all
+ posts_with_tags = Post.scoped(:includes => :tags, :order => 'posts.id').all
assert_equal posts.length, posts_with_tags.length
posts.length.times do |i|
assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length }
@@ -271,8 +267,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_polymorphic_has_many
- posts = Post.find(:all, :order => 'posts.id')
- posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id')
+ posts = Post.scoped(:order => 'posts.id').all
+ posts_with_taggings = Post.scoped(:includes => :taggings, :order => 'posts.id').all
assert_equal posts.length, posts_with_taggings.length
posts.length.times do |i|
assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length }
@@ -280,25 +276,20 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_find_all
- assert_equal [categories(:general)], authors(:david).categories.find(:all)
+ assert_equal [categories(:general)], authors(:david).categories.all
end
def test_has_many_find_first
- assert_equal categories(:general), authors(:david).categories.find(:first)
+ assert_equal categories(:general), authors(:david).categories.first
end
def test_has_many_with_hash_conditions
- assert_equal categories(:general), authors(:david).categories_like_general.find(:first)
+ assert_equal categories(:general), authors(:david).categories_like_general.first
end
def test_has_many_find_conditions
- assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'")
- assert_nil authors(:david).categories.find(:first, :conditions => "categories.name = 'Technology'")
- end
-
- def test_has_many_class_methods_called_by_method_missing
- assert_equal categories(:general), authors(:david).categories.find_all_by_name('General').first
- assert_nil authors(:david).categories.find_by_name('Technology')
+ assert_equal categories(:general), authors(:david).categories.scoped(:where => "categories.name = 'General'").first
+ assert_nil authors(:david).categories.scoped(:where => "categories.name = 'Technology'").first
end
def test_has_many_array_methods_called_by_method_missing
@@ -327,14 +318,6 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
assert_equal [authors(:david), authors(:bob)], posts(:thinking).authors_using_custom_pk.order('authors.id')
end
- def test_both_scoped_and_explicit_joins_should_be_respected
- assert_nothing_raised do
- Post.send(:with_scope, :find => {:joins => "left outer join comments on comments.id = posts.id"}) do
- Post.find :all, :select => "comments.id, authors.id", :joins => "left outer join authors on authors.id = posts.author_id"
- end
- end
- end
-
def test_belongs_to_polymorphic_with_counter_cache
assert_equal 1, posts(:welcome)[:taggings_count]
tagging = posts(:welcome).taggings.create(:tag => tags(:general))
@@ -362,7 +345,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
assert_raise ActiveRecord::EagerLoadPolymorphicError do
- tags(:general).taggings.find(:all, :include => :taggable, :conditions => 'bogus_table.column = 1')
+ tags(:general).taggings.includes(:taggable).where('bogus_table.column = 1').references(:bogus_table).to_a
end
end
@@ -372,7 +355,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_eager_has_many_polymorphic_with_source_type
- tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts)
+ tag_with_include = Tag.scoped(:includes => :tagged_posts).find(tags(:general).id)
desired = posts(:welcome, :thinking)
assert_no_queries do
# added sort by ID as otherwise test using JRuby was failing as array elements were in different order
@@ -382,20 +365,20 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_has_many_find_all
- assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').first
+ assert_equal comments(:greetings), authors(:david).comments.scoped(:order => 'comments.id').all.first
end
def test_has_many_through_has_many_find_all_with_custom_class
- assert_equal comments(:greetings), authors(:david).funky_comments.find(:all, :order => 'comments.id').first
+ assert_equal comments(:greetings), authors(:david).funky_comments.scoped(:order => 'comments.id').all.first
end
def test_has_many_through_has_many_find_first
- assert_equal comments(:greetings), authors(:david).comments.find(:first, :order => 'comments.id')
+ assert_equal comments(:greetings), authors(:david).comments.scoped(:order => 'comments.id').first
end
def test_has_many_through_has_many_find_conditions
- options = { :conditions => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' }
- assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, options)
+ options = { :where => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' }
+ assert_equal comments(:does_it_hurt), authors(:david).comments.scoped(options).first
end
def test_has_many_through_has_many_find_by_id
@@ -411,7 +394,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_include_has_many_through_polymorphic_has_many
- author = Author.find_by_id(authors(:david).id, :include => :taggings)
+ author = Author.includes(:taggings).find authors(:david).id
expected_taggings = taggings(:welcome_general, :thinking_general)
assert_no_queries do
assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
@@ -419,7 +402,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_eager_load_has_many_through_has_many
- author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id'
+ author = Author.scoped(:where => ['name = ?', 'David'], :includes => :comments, :order => 'comments.id').first
SpecialComment.new; VerySpecialComment.new
assert_no_queries do
assert_equal [1,2,3,5,6,7,8,9,10,12], author.comments.collect(&:id)
@@ -427,7 +410,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_eager_load_has_many_through_has_many_with_conditions
- post = Post.find(:first, :include => :invalid_tags)
+ post = Post.scoped(:includes => :invalid_tags).first
assert_no_queries do
post.invalid_tags
end
@@ -435,8 +418,8 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_eager_belongs_to_and_has_one_not_singularized
assert_nothing_raised do
- Author.find(:first, :include => :author_address)
- AuthorAddress.find(:first, :include => :author)
+ Author.scoped(:includes => :author_address).first
+ AuthorAddress.scoped(:includes => :author).first
end
end
@@ -452,7 +435,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_through_uses_conditions_specified_on_the_has_many_association
- author = Author.find(:first)
+ author = Author.first
assert_present author.comments
assert_blank author.nonexistant_comments
end
@@ -622,7 +605,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_polymorphic_has_many
expected = taggings(:welcome_general)
- p = Post.find(posts(:welcome).id, :include => :taggings)
+ p = Post.scoped(:includes => :taggings).find(posts(:welcome).id)
assert_no_queries {assert p.taggings.include?(expected)}
assert posts(:welcome).taggings.include?(taggings(:welcome_general))
end
@@ -630,18 +613,18 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_polymorphic_has_one
expected = posts(:welcome)
- tagging = Tagging.find(taggings(:welcome_general).id, :include => :taggable)
+ tagging = Tagging.scoped(:includes => :taggable).find(taggings(:welcome_general).id)
assert_no_queries { assert_equal expected, tagging.taggable}
end
def test_polymorphic_belongs_to
- p = Post.find(posts(:welcome).id, :include => {:taggings => :taggable})
+ p = Post.scoped(:includes => {:taggings => :taggable}).find(posts(:welcome).id)
assert_no_queries {assert_equal posts(:welcome), p.taggings.first.taggable}
end
def test_preload_polymorphic_has_many_through
- posts = Post.find(:all, :order => 'posts.id')
- posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id')
+ posts = Post.scoped(:order => 'posts.id').all
+ posts_with_tags = Post.scoped(:includes => :tags, :order => 'posts.id').all
assert_equal posts.length, posts_with_tags.length
posts.length.times do |i|
assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length }
@@ -649,7 +632,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_preload_polymorph_many_types
- taggings = Tagging.find :all, :include => :taggable, :conditions => ['taggable_type != ?', 'FakeModel']
+ taggings = Tagging.scoped(:includes => :taggable, :where => ['taggable_type != ?', 'FakeModel']).all
assert_no_queries do
taggings.first.taggable.id
taggings[1].taggable.id
@@ -662,13 +645,13 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_preload_nil_polymorphic_belongs_to
assert_nothing_raised do
- Tagging.find(:all, :include => :taggable, :conditions => ['taggable_type IS NULL'])
+ Tagging.scoped(:includes => :taggable, :where => ['taggable_type IS NULL']).all
end
end
def test_preload_polymorphic_has_many
- posts = Post.find(:all, :order => 'posts.id')
- posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id')
+ posts = Post.scoped(:order => 'posts.id').all
+ posts_with_taggings = Post.scoped(:includes => :taggings, :order => 'posts.id').all
assert_equal posts.length, posts_with_taggings.length
posts.length.times do |i|
assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length }
@@ -676,7 +659,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_belongs_to_shared_parent
- comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 1')
+ comments = Comment.scoped(:includes => :post, :where => 'post_id = 1').all
assert_no_queries do
assert_equal comments.first.post, comments[1].post
end
@@ -684,7 +667,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_include_uses_array_include_after_loaded
david = authors(:david)
- david.categories.class # force load target
+ david.categories.load_target
category = david.categories.first
@@ -733,7 +716,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}"
Post.find(post_id).update_column :type, class_name
klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
- klass.set_table_name 'posts'
+ klass.table_name = 'posts'
klass.send(association, association_name, :as => :taggable, :dependent => dependency)
klass.find(post_id)
end
diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb
index 530f5212a2..03d99d19f6 100644
--- a/activerecord/test/cases/associations/nested_through_associations_test.rb
+++ b/activerecord/test/cases/associations/nested_through_associations_test.rb
@@ -505,7 +505,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
def test_nested_has_many_through_with_conditions_on_through_associations_preload_via_joins
# Pointless condition to force single-query loading
assert_includes_and_joins_equal(
- Author.where('tags.id = tags.id'),
+ Author.where('tags.id = tags.id').references(:tags),
[authors(:bob)], :misc_post_first_blue_tags
)
end
@@ -526,7 +526,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
def test_nested_has_many_through_with_conditions_on_source_associations_preload_via_joins
# Pointless condition to force single-query loading
assert_includes_and_joins_equal(
- Author.where('tags.id = tags.id'),
+ Author.where('tags.id = tags.id').references(:tags),
[authors(:bob)], :misc_post_first_blue_tags_2
)
end
@@ -545,6 +545,15 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase
assert_equal [organizations(:nsa)], organizations
end
+ def test_nested_has_many_through_should_not_be_autosaved
+ c = Categorization.new
+ c.author = authors(:david)
+ c.post_taggings.to_a
+ assert !c.post_taggings.empty?
+ c.save
+ assert !c.post_taggings.empty?
+ end
+
private
def assert_includes_and_joins_equal(query, expected, association)
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index ffe2993e0f..1d0550afaf 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'models/computer'
require 'models/developer'
require 'models/project'
require 'models/company'
@@ -28,7 +29,7 @@ class AssociationsTest < ActiveRecord::TestCase
molecule.electrons.create(:name => 'electron_1')
molecule.electrons.create(:name => 'electron_2')
- liquids = Liquid.includes(:molecules => :electrons).where('molecules.id is not null')
+ liquids = Liquid.includes(:molecules => :electrons).references(:molecules).where('molecules.id is not null')
assert_equal 1, liquids[0].molecules.length
end
@@ -73,8 +74,8 @@ class AssociationsTest < ActiveRecord::TestCase
def test_include_with_order_works
- assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)}
- assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)}
+ assert_nothing_raised {Account.scoped(:order => 'id', :includes => :firm).first}
+ assert_nothing_raised {Account.scoped(:order => :id, :includes => :firm).first}
end
def test_bad_collection_keys
@@ -128,6 +129,11 @@ class AssociationsTest < ActiveRecord::TestCase
end
end
+ def test_association_with_references
+ firm = companies(:first_firm)
+ assert_equal ['foo'], firm.association_with_references.scoped.references_values
+ end
+
end
class AssociationProxyTest < ActiveRecord::TestCase
@@ -208,6 +214,10 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = developers(:david)
assert_equal david.association(:projects), david.projects.proxy_association
end
+
+ def test_scoped_allows_conditions
+ assert developers(:david).projects.scoped(where: 'foo').where_values.include?('foo')
+ end
end
class OverridingAssociationsTest < ActiveRecord::TestCase
@@ -273,3 +283,18 @@ class OverridingAssociationsTest < ActiveRecord::TestCase
)
end
end
+
+class GeneratedMethodsTest < ActiveRecord::TestCase
+ fixtures :developers, :computers, :posts, :comments
+ def test_association_methods_override_attribute_methods_of_same_name
+ assert_equal(developers(:david), computers(:workstation).developer)
+ # this next line will fail if the attribute methods module is generated lazily
+ # after the association methods module is generated
+ assert_equal(developers(:david), computers(:workstation).developer)
+ assert_equal(developers(:david).id, computers(:workstation)[:developer])
+ end
+
+ def test_model_method_overrides_association_method
+ assert_equal(comments(:greetings).body, posts(:welcome).first_comment)
+ end
+end
diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb
index e03ed33591..98c38535a6 100644
--- a/activerecord/test/cases/attribute_methods/read_test.rb
+++ b/activerecord/test/cases/attribute_methods/read_test.rb
@@ -1,21 +1,28 @@
require "cases/helper"
require 'active_support/core_ext/object/inclusion'
+require 'thread'
module ActiveRecord
module AttributeMethods
class ReadTest < ActiveRecord::TestCase
class FakeColumn < Struct.new(:name)
- def type_cast_code(var)
- var
- end
-
def type; :integer; end
end
def setup
@klass = Class.new do
+ def self.superclass; Base; end
+ def self.active_record_super; Base; end
+ def self.base_class; self; end
+
include ActiveRecord::AttributeMethods
- include ActiveRecord::AttributeMethods::Read
+
+ def self.define_attribute_methods
+ # Created in the inherited/included hook for "proper" ARs
+ @attribute_methods_mutex ||= Mutex.new
+
+ super
+ end
def self.column_names
%w{ one two three }
@@ -33,9 +40,6 @@ module ActiveRecord
[name, FakeColumn.new(name)]
}]
end
-
- def self.serialized_attributes; {}; end
- def self.base_class; self; end
end
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index b1b41fed0d..1093fedea1 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -30,9 +30,36 @@ class AttributeMethodsTest < ActiveRecord::TestCase
t = Topic.new
t.title = "hello there!"
t.written_on = Time.now
+ t.author_name = ""
assert t.attribute_present?("title")
assert t.attribute_present?("written_on")
assert !t.attribute_present?("content")
+ assert !t.attribute_present?("author_name")
+
+ end
+
+ def test_attribute_present_with_booleans
+ b1 = Boolean.new
+ b1.value = false
+ assert b1.attribute_present?(:value)
+
+ b2 = Boolean.new
+ b2.value = true
+ assert b2.attribute_present?(:value)
+
+ b3 = Boolean.new
+ assert !b3.attribute_present?(:value)
+
+ b4 = Boolean.new
+ b4.value = false
+ b4.save!
+ assert Boolean.find(b4.id).attribute_present?(:value)
+ end
+
+ def test_caching_nil_primary_key
+ klass = Class.new(Minimalistic)
+ klass.expects(:reset_primary_key).returns(nil).once
+ 2.times { klass.primary_key }
end
def test_attribute_keys_on_new_instance
@@ -78,7 +105,6 @@ 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
@@ -95,9 +121,15 @@ class AttributeMethodsTest < ActiveRecord::TestCase
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
+
+ def test_id_before_type_cast_with_custom_primary_key
+ keyboard = Keyboard.create
+ keyboard.key_number = '10'
+ assert_equal '10', keyboard.id_before_type_cast
+ assert_equal nil, keyboard.read_attribute_before_type_cast('id')
+ assert_equal '10', keyboard.read_attribute_before_type_cast('key_number')
end
# Syck calls respond_to? before actually calling initialize
@@ -212,7 +244,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
# DB2 is not case-sensitive
return true if current_adapter?(:DB2Adapter)
- assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
+ assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.first.attributes
end
def test_hashes_not_mangled
@@ -237,8 +269,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic.send(:write_attribute, :title, "Still another topic")
assert_equal "Still another topic", topic.title
- topic.send(:write_attribute, "title", "Still another topic: part 2")
+ topic[:title] = "Still another topic: part 2"
assert_equal "Still another topic: part 2", topic.title
+
+ topic.send(:write_attribute, "title", "Still another topic: part 3")
+ assert_equal "Still another topic: part 3", topic.title
+
+ topic["title"] = "Still another topic: part 4"
+ assert_equal "Still another topic: part 4", topic.title
end
def test_read_attribute
@@ -291,6 +329,39 @@ class AttributeMethodsTest < ActiveRecord::TestCase
# puts ""
end
+ def test_overridden_write_attribute
+ topic = Topic.new
+ def topic.write_attribute(attr_name, value)
+ super(attr_name, value.downcase)
+ end
+
+ topic.send(:write_attribute, :title, "Yet another topic")
+ assert_equal "yet another topic", topic.title
+
+ topic[:title] = "Yet another topic: part 2"
+ assert_equal "yet another topic: part 2", topic.title
+
+ topic.send(:write_attribute, "title", "Yet another topic: part 3")
+ assert_equal "yet another topic: part 3", topic.title
+
+ topic["title"] = "Yet another topic: part 4"
+ assert_equal "yet another topic: part 4", topic.title
+ end
+
+ def test_overridden_read_attribute
+ topic = Topic.new
+ topic.title = "Stop changing the topic"
+ def topic.read_attribute(attr_name)
+ super(attr_name).upcase
+ end
+
+ assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, "title")
+ assert_equal "STOP CHANGING THE TOPIC", topic["title"]
+
+ assert_equal "STOP CHANGING THE TOPIC", topic.send(:read_attribute, :title)
+ assert_equal "STOP CHANGING THE TOPIC", topic[:title]
+ end
+
def test_read_overridden_attribute
topic = Topic.new(:title => 'a')
def topic.title() 'b' end
@@ -410,23 +481,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
def test_typecast_attribute_from_select_to_false
- topic = Topic.create(:title => 'Budget')
+ Topic.create(:title => 'Budget')
# Oracle does not support boolean expressions in SELECT
if current_adapter?(:OracleAdapter)
- topic = Topic.find(:first, :select => "topics.*, 0 as is_test")
+ topic = Topic.scoped(:select => "topics.*, 0 as is_test").first
else
- topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test")
+ topic = Topic.scoped(:select => "topics.*, 1=2 as is_test").first
end
assert !topic.is_test?
end
def test_typecast_attribute_from_select_to_true
- topic = Topic.create(:title => 'Budget')
+ Topic.create(:title => 'Budget')
# Oracle does not support boolean expressions in SELECT
if current_adapter?(:OracleAdapter)
- topic = Topic.find(:first, :select => "topics.*, 1 as is_test")
+ topic = Topic.scoped(:select => "topics.*, 1 as is_test").first
else
- topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test")
+ topic = Topic.scoped(:select => "topics.*, 2=2 as is_test").first
end
assert topic.is_test?
end
@@ -489,6 +560,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_write_time_to_date_attributes
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new
+ record.last_read = Time.utc(2010, 1, 1, 10)
+ assert_equal Date.civil(2010, 1, 1), record.last_read
+ end
+ end
+
def test_time_attributes_are_retrieved_in_current_time_zone
in_time_zone "Pacific Time (US & Canada)" do
utc_time = Time.utc(2008, 1, 1)
@@ -524,6 +603,17 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_setting_time_zone_aware_read_attribute
+ utc_time = Time.utc(2008, 1, 1)
+ cst_time = utc_time.in_time_zone("Central Time (US & Canada)")
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.create(:written_on => cst_time).reload
+ assert_equal utc_time, record[:written_on]
+ assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record[:written_on].time_zone
+ assert_equal Time.utc(2007, 12, 31, 16), record[:written_on].time
+ end
+ end
+
def test_setting_time_zone_aware_attribute_with_string
utc_time = Time.utc(2008, 1, 1)
(-11..13).each do |timezone_offset|
@@ -538,11 +628,22 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_time_zone_aware_attribute_saved
+ in_time_zone 1 do
+ record = @target.create(:written_on => '2012-02-20 10:00')
+
+ record.written_on = '2012-02-20 09:00'
+ record.save
+ assert_equal Time.zone.local(2012, 02, 20, 9), record.reload.written_on
+ end
+ end
+
def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
record.written_on = ' '
assert_nil record.written_on
+ assert_nil record[:written_on]
end
end
@@ -657,7 +758,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
topic = Topic.new(:id => 5)
topic.id = 5
- topic.method(:id).owner.send(:remove_method, :id)
+ topic.method(:id).owner.send(:undef_method, :id)
assert_deprecated do
assert_equal 5, topic.id
@@ -666,17 +767,39 @@ class AttributeMethodsTest < ActiveRecord::TestCase
Topic.undefine_attribute_methods
end
+ def test_read_attribute_with_nil_should_not_asplode
+ assert_equal nil, Topic.new.read_attribute(nil)
+ end
+
+ # If B < A, and A defines an accessor for 'foo', we don't want to override
+ # that by defining a 'foo' method in the generated methods module for B.
+ # (That module will be inserted between the two, e.g. [B, <GeneratedAttributes>, A].)
+ def test_inherited_custom_accessors
+ klass = Class.new(ActiveRecord::Base) do
+ self.table_name = "topics"
+ self.abstract_class = true
+ def title; "omg"; end
+ def title=(val); self.author_name = val; end
+ end
+ subklass = Class.new(klass)
+ [klass, subklass].each(&:define_attribute_methods)
+
+ topic = subklass.find(1)
+ assert_equal "omg", topic.title
+
+ topic.title = "lol"
+ assert_equal "lol", topic.author_name
+ end
+
private
def cached_columns
- @cached_columns ||= (time_related_columns_on_topic + serialized_columns_on_topic).map(&:name)
+ Topic.columns.find_all { |column|
+ !Topic.serialized_attributes.include? column.name
+ }.map(&:name)
end
def time_related_columns_on_topic
- Topic.columns.select { |c| c.type.in?([:time, :date, :datetime, :timestamp]) }
- end
-
- def serialized_columns_on_topic
- Topic.columns.select { |c| Topic.serialized_attributes.include?(c.name) }
+ Topic.columns.select { |c| [:time, :date, :datetime, :timestamp].include?(c.type) }
end
def in_time_zone(zone)
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 4ad2cdfc7e..8ef3bfef15 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -87,7 +87,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
end
def test_save_fails_for_invalid_has_one
- firm = Firm.find(:first)
+ firm = Firm.first
assert firm.valid?
firm.build_account
@@ -99,7 +99,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
end
def test_save_succeeds_for_invalid_has_one_with_validate_false
- firm = Firm.find(:first)
+ firm = Firm.first
assert firm.valid?
firm.build_unvalidated_account
@@ -155,20 +155,20 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
end
def test_not_resaved_when_unchanged
- firm = Firm.find(:first, :include => :account)
+ firm = Firm.scoped(:includes => :account).first
firm.name += '-changed'
assert_queries(1) { firm.save! }
- firm = Firm.find(:first)
- firm.account = Account.find(:first)
+ firm = Firm.first
+ firm.account = Account.first
assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! }
- firm = Firm.find(:first).dup
- firm.account = Account.find(:first)
+ firm = Firm.first.dup
+ firm.account = Account.first
assert_queries(2) { firm.save! }
- firm = Firm.find(:first).dup
- firm.account = Account.find(:first).dup
+ firm = Firm.first.dup
+ firm.account = Account.first.dup
assert_queries(2) { firm.save! }
end
@@ -228,7 +228,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
end
def test_assignment_before_parent_saved
- client = Client.find(:first)
+ client = Client.first
apple = Firm.new("name" => "Apple")
client.firm = apple
assert_equal apple, client.firm
@@ -342,11 +342,22 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
end
def test_build_and_then_save_parent_should_not_reload_target
- client = Client.find(:first)
+ client = Client.first
apple = client.build_firm(:name => "Apple")
client.save!
assert_no_queries { assert_equal apple, client.firm }
end
+
+ def test_validation_does_not_validate_stale_association_target
+ valid_developer = Developer.create!(:name => "Dude", :salary => 50_000)
+ invalid_developer = Developer.new()
+
+ auditlog = AuditLog.new(:message => "foo")
+ auditlog.developer = invalid_developer
+ auditlog.developer_id = valid_developer.id
+
+ assert auditlog.valid?
+ end
end
class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
@@ -373,7 +384,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
end
def test_invalid_adding_with_validate_false
- firm = Firm.find(:first)
+ firm = Firm.first
client = Client.new
firm.unvalidated_clients_of_firm << client
@@ -386,7 +397,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa
def test_valid_adding_with_validate_false
no_of_clients = Client.count
- firm = Firm.find(:first)
+ firm = Firm.first
client = Client.new("name" => "Apple")
assert firm.valid?
@@ -575,6 +586,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
self.use_transactional_fixtures = false unless supports_savepoints?
def setup
+ super
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
end
@@ -615,7 +627,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_a_child_marked_for_destruction_should_not_be_destroyed_twice
@pirate.ship.mark_for_destruction
assert @pirate.save
- @pirate.ship.expects(:destroy).never
+ class << @pirate.ship
+ def destroy; raise "Should not be called" end
+ end
assert @pirate.save
end
@@ -660,7 +674,9 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
def test_a_parent_marked_for_destruction_should_not_be_destroyed_twice
@ship.pirate.mark_for_destruction
assert @ship.save
- @ship.pirate.expects(:destroy).never
+ class << @ship.pirate
+ def destroy; raise "Should not be called" end
+ end
assert @ship.save
end
@@ -755,6 +771,16 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
assert_equal before, @pirate.reload.birds
end
+ def test_when_new_record_a_child_marked_for_destruction_should_not_affect_other_records_from_saving
+ @pirate = @ship.build_pirate(:catchphrase => "Arr' now I shall keep me eye on you matey!") # new record
+
+ 3.times { |i| @pirate.birds.build(:name => "birds_#{i}") }
+ @pirate.birds[1].mark_for_destruction
+ @pirate.save!
+
+ assert_equal 2, @pirate.birds.reload.length
+ end
+
# Add and remove callbacks tests for association collections.
%w{ method proc }.each do |callback_type|
define_method("test_should_run_add_callback_#{callback_type}s_for_has_many") do
@@ -846,7 +872,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
2.times { |i| @pirate.parrots.create!(:name => "parrots_#{i}") }
before = @pirate.parrots.map { |c| c.mark_for_destruction ; c }
- class << @pirate.parrots
+ class << @pirate.association(:parrots)
def destroy(*args)
super
raise 'Oh noes!'
@@ -897,6 +923,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
self.use_transactional_fixtures = false unless supports_savepoints?
def setup
+ super
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@ship = @pirate.create_ship(:name => 'Nights Dirty Lightning')
end
@@ -965,10 +992,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)]
# Oracle saves empty string as NULL
if current_adapter?(:OracleAdapter)
- expected = ActiveRecord::IdentityMap.enabled? ?
- [nil, nil, '', ''] :
- [nil, nil, nil, nil]
- assert_equal expected, values
+ assert_equal [nil, nil, nil, nil], values
else
assert_equal ['', '', '', ''], values
end
@@ -1020,6 +1044,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
self.use_transactional_fixtures = false unless supports_savepoints?
def setup
+ super
@ship = Ship.create(:name => 'Nights Dirty Lightning')
@pirate = @ship.create_pirate(:catchphrase => "Don' botharrr talkin' like one, savvy?")
end
@@ -1063,8 +1088,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
@ship.save(:validate => false)
# Oracle saves empty string as NULL
if current_adapter?(:OracleAdapter)
- expected = ActiveRecord::IdentityMap.enabled? ? [nil, ''] : [nil, nil]
- assert_equal expected, [@ship.reload.name, @ship.pirate.catchphrase]
+ assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase]
else
assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase]
end
@@ -1253,7 +1277,7 @@ module AutosaveAssociationOnACollectionAssociationTests
def test_should_not_load_the_associated_models_if_they_were_not_loaded_yet
assert_queries(1) { @pirate.catchphrase = 'Arr'; @pirate.save! }
- @pirate.send(@association_name).class # hack to load the target
+ @pirate.send(@association_name).load_target
assert_queries(3) do
@pirate.catchphrase = 'Yarr'
@@ -1268,6 +1292,7 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
self.use_transactional_fixtures = false unless supports_savepoints?
def setup
+ super
@association_name = :birds
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@@ -1282,6 +1307,7 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::T
self.use_transactional_fixtures = false unless supports_savepoints?
def setup
+ super
@association_name = :parrots
@habtm = true
@@ -1297,6 +1323,7 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te
self.use_transactional_fixtures = false unless supports_savepoints?
def setup
+ super
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@pirate.birds.create(:name => 'cookoo')
end
@@ -1313,6 +1340,7 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes
self.use_transactional_fixtures = false unless supports_savepoints?
def setup
+ super
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@pirate.create_ship(:name => 'titanic')
super
@@ -1335,6 +1363,7 @@ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::
self.use_transactional_fixtures = false unless supports_savepoints?
def setup
+ super
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
end
@@ -1355,6 +1384,7 @@ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::Test
self.use_transactional_fixtures = false unless supports_savepoints?
def setup
+ super
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
end
@@ -1377,6 +1407,7 @@ class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCas
self.use_transactional_fixtures = false unless supports_savepoints?
def setup
+ super
@pirate = Pirate.new
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 12c1cfb30e..619fb881fa 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -23,10 +23,18 @@ require 'models/edge'
require 'models/joke'
require 'models/bulb'
require 'models/bird'
+require 'models/teapot'
require 'rexml/document'
require 'active_support/core_ext/exception'
require 'bcrypt'
+class FirstAbstractClass < ActiveRecord::Base
+ self.abstract_class = true
+end
+class SecondAbstractClass < FirstAbstractClass
+ self.abstract_class = true
+end
+class Photo < SecondAbstractClass; end
class Category < ActiveRecord::Base; end
class Categorization < ActiveRecord::Base; end
class Smarts < ActiveRecord::Base; end
@@ -54,7 +62,11 @@ end
class Weird < ActiveRecord::Base; end
-class Boolean < ActiveRecord::Base; end
+class Boolean < ActiveRecord::Base
+ def has_fun
+ super
+ end
+end
class LintTest < ActiveRecord::TestCase
include ActiveModel::Lint::Tests
@@ -69,6 +81,16 @@ end
class BasicsTest < ActiveRecord::TestCase
fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts
+ def test_generated_methods_modules
+ modules = Computer.ancestors
+ assert modules.include?(Computer::GeneratedFeatureMethods)
+ assert_equal(Computer::GeneratedFeatureMethods, Computer.generated_feature_methods)
+ assert(modules.index(Computer.generated_attribute_methods) > modules.index(Computer.generated_feature_methods),
+ "generated_attribute_methods must be higher in inheritance hierarchy than generated_feature_methods")
+ assert_not_equal Computer.generated_feature_methods, Post.generated_feature_methods
+ assert(modules.index(Computer.generated_attribute_methods) < modules.index(ActiveRecord::Base.ancestors[1]))
+ end
+
def test_column_names_are_escaped
conn = ActiveRecord::Base.connection
classname = conn.class.name[/[^:]*$/]
@@ -103,20 +125,13 @@ class BasicsTest < ActiveRecord::TestCase
unless current_adapter?(:PostgreSQLAdapter,:OracleAdapter,:SQLServerAdapter)
def test_limit_with_comma
- assert_nothing_raised do
- Topic.limit("1,2").all
- end
+ assert Topic.limit("1,2").all
end
end
def test_limit_without_comma
- assert_nothing_raised do
- assert_equal 1, Topic.limit("1").all.length
- end
-
- assert_nothing_raised do
- assert_equal 1, Topic.limit(1).all.length
- end
+ assert_equal 1, Topic.limit("1").all.length
+ assert_equal 1, Topic.limit(1).all.length
end
def test_invalid_limit
@@ -145,7 +160,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_select_symbol
topic_ids = Topic.select(:id).map(&:id).sort
- assert_equal Topic.all.map(&:id).sort, topic_ids
+ assert_equal Topic.pluck(:id).sort, topic_ids
end
def test_table_exists
@@ -169,6 +184,31 @@ class BasicsTest < ActiveRecord::TestCase
end
end
+ def test_previously_changed
+ topic = Topic.first
+ topic.title = '<3<3<3'
+ assert_equal({}, topic.previous_changes)
+
+ topic.save!
+ expected = ["The First Topic", "<3<3<3"]
+ assert_equal(expected, topic.previous_changes['title'])
+ end
+
+ def test_previously_changed_dup
+ topic = Topic.first
+ topic.title = '<3<3<3'
+ topic.save!
+
+ t2 = topic.dup
+
+ assert_equal(topic.previous_changes, t2.previous_changes)
+
+ topic.title = "lolwut"
+ topic.save!
+
+ assert_not_equal(topic.previous_changes, t2.previous_changes)
+ end
+
def test_preserving_time_objects
assert_kind_of(
Time, Topic.find(1).bonus_time,
@@ -181,7 +221,7 @@ class BasicsTest < ActiveRecord::TestCase
)
# For adapters which support microsecond resolution.
- if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLiteAdapter)
+ if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLite3Adapter)
assert_equal 11, Topic.find(1).written_on.sec
assert_equal 223300, Topic.find(1).written_on.usec
assert_equal 9900, Topic.find(2).written_on.usec
@@ -303,13 +343,13 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_load
- topics = Topic.find(:all, :order => 'id')
+ topics = Topic.scoped(:order => 'id').all
assert_equal(4, topics.size)
assert_equal(topics(:first).title, topics.first.title)
end
def test_load_with_condition
- topics = Topic.find(:all, :conditions => "author_name = 'Mary'")
+ topics = Topic.scoped(:where => "author_name = 'Mary'").all
assert_equal(1, topics.size)
assert_equal(topics(:second).title, topics.first.title)
@@ -431,7 +471,7 @@ class BasicsTest < ActiveRecord::TestCase
if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_update_all_with_order_and_limit
- assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC')
+ assert_equal 1, Topic.limit(1).order('id DESC').update_all(:content => 'bulk updated!')
end
end
@@ -464,7 +504,7 @@ class BasicsTest < ActiveRecord::TestCase
end
# Oracle, and Sybase do not have a TIME datatype.
- unless current_adapter?(:OracleAdapter, :SybaseAdapter)
+ unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter)
def test_utc_as_time_zone
Topic.default_timezone = :utc
attributes = { "bonus_time" => "5:42:00AM" }
@@ -561,10 +601,12 @@ class BasicsTest < ActiveRecord::TestCase
weird = Weird.create('a$b' => 'value')
weird.reload
assert_equal 'value', weird.send('a$b')
+ assert_equal 'value', weird.read_attribute('a$b')
weird.update_column('a$b', 'value2')
weird.reload
assert_equal 'value2', weird.send('a$b')
+ assert_equal 'value2', weird.read_attribute('a$b')
end
def test_multiparameter_attributes_on_date
@@ -644,7 +686,7 @@ class BasicsTest < ActiveRecord::TestCase
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
end
def test_multiparameter_attributes_on_time_with_no_date
@@ -704,6 +746,9 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ ActiveRecord::Base.default_timezone = :local
+ Time.zone = nil
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12",
"written_on(5i)" => "12", "written_on(6i)" => "02"
@@ -810,7 +855,7 @@ class BasicsTest < ActiveRecord::TestCase
end
# Oracle, and Sybase do not have a TIME datatype.
- unless current_adapter?(:OracleAdapter, :SybaseAdapter)
+ unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter)
def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion
ActiveRecord::Base.time_zone_aware_attributes = true
ActiveRecord::Base.default_timezone = :utc
@@ -831,6 +876,9 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_multiparameter_attributes_on_time_with_empty_seconds
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ ActiveRecord::Base.default_timezone = :local
+ Time.zone = nil
attributes = {
"written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
"written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
@@ -886,14 +934,14 @@ class BasicsTest < ActiveRecord::TestCase
def test_attributes_on_dummy_time
# Oracle, and Sybase do not have a TIME datatype.
- return true if current_adapter?(:OracleAdapter, :SybaseAdapter)
+ return true if current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter)
attributes = {
"bonus_time" => "5:42:00AM"
}
topic = Topic.find(1)
topic.attributes = attributes
- assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
+ assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
end
def test_boolean
@@ -912,6 +960,16 @@ class BasicsTest < ActiveRecord::TestCase
assert b_true.value?
end
+ def test_boolean_without_questionmark
+ b_true = Boolean.create({ "value" => true })
+ true_id = b_true.id
+
+ subclass = Class.new(Boolean).find true_id
+ superclass = Boolean.find true_id
+
+ assert_equal superclass.read_attribute(:has_fun), subclass.read_attribute(:has_fun)
+ end
+
def test_boolean_cast_from_string
b_blank = Boolean.create({ "value" => "" })
blank_id = b_blank.id
@@ -947,10 +1005,9 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "b", duped_topic.title
# test if the attribute values have been duped
- topic.title = {"a" => "b"}
duped_topic = topic.dup
- duped_topic.title["a"] = "c"
- assert_equal "b", topic.title["a"]
+ duped_topic.title.replace "c"
+ assert_equal "a", topic.title
# test if attributes set as part of after_initialize are duped correctly
assert_equal topic.author_email_address, duped_topic.author_email_address
@@ -961,8 +1018,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal duped_topic.id, topic.id
duped_topic.reload
- # FIXME: I think this is poor behavior, and will fix it with #5686
- assert_equal({'a' => 'c'}.to_yaml, duped_topic.title)
+ assert_equal("c", duped_topic.title)
end
def test_dup_with_aggregate_of_same_name_as_attribute
@@ -1061,7 +1117,10 @@ class BasicsTest < ActiveRecord::TestCase
# TODO: extend defaults tests to other databases!
if current_adapter?(:PostgreSQLAdapter)
def test_default
+ tz = Default.default_timezone
+ Default.default_timezone = :local
default = Default.new
+ Default.default_timezone = tz
# fixed dates / times
assert_equal Date.new(2004, 1, 1), default.fixed_date
@@ -1093,7 +1152,7 @@ class BasicsTest < ActiveRecord::TestCase
assert g.save
# Reload and check that we have all the geometric attributes.
- h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) }
+ h = Geometric.find(g.id)
assert_equal '(5,6.1)', h.a_point
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
@@ -1104,7 +1163,8 @@ class BasicsTest < ActiveRecord::TestCase
# use a geometric function to test for an open path
objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
- assert_equal objs[0].isopen, 't'
+
+ assert_equal true, objs[0].isopen
# test alternate formats when defining the geometric types
@@ -1121,7 +1181,7 @@ class BasicsTest < ActiveRecord::TestCase
assert g.save
# Reload and check that we have all the geometric attributes.
- h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) }
+ h = Geometric.find(g.id)
assert_equal '(5,6.1)', h.a_point
assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
@@ -1132,7 +1192,8 @@ class BasicsTest < ActiveRecord::TestCase
# use a geometric function to test for an closed path
objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
- assert_equal objs[0].isclosed, 't'
+
+ assert_equal true, objs[0].isclosed
end
end
@@ -1185,19 +1246,6 @@ class BasicsTest < ActiveRecord::TestCase
assert(auto.id > 0)
end
- def quote_column_name(name)
- "<#{name}>"
- end
-
- def test_quote_keys
- ar = AutoId.new
- source = {"foo" => "bar", "baz" => "quux"}
- actual = ar.send(:quote_columns, self, source)
- inverted = actual.invert
- assert_equal("<foo>", inverted["bar"])
- assert_equal("<baz>", inverted["quux"])
- end
-
def test_sql_injection_via_find
assert_raise(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do
Topic.find("123456 OR id > 0")
@@ -1215,10 +1263,10 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_quoting_arrays
- replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ])
+ replies = Reply.scoped(:where => [ "id IN (?)", topics(:first).replies.collect(&:id) ]).all
assert_equal topics(:first).replies.size, replies.size
- replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ])
+ replies = Reply.scoped(:where => [ "id IN (?)", [] ]).all
assert_equal 0, replies.size
end
@@ -1235,6 +1283,42 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal(myobj, topic.content)
end
+ def test_serialized_attribute_in_base_class
+ Topic.serialize("content", Hash)
+
+ hash = { 'content1' => 'value1', 'content2' => 'value2' }
+ important_topic = ImportantTopic.create("content" => hash)
+ assert_equal(hash, important_topic.content)
+
+ important_topic.reload
+ assert_equal(hash, important_topic.content)
+ end
+
+ # This test was added to fix GH #4004. Obviously the value returned
+ # is not really the value 'before type cast' so we should maybe think
+ # about changing that in the future.
+ def test_serialized_attribute_before_type_cast_returns_unserialized_value
+ klass = Class.new(ActiveRecord::Base)
+ klass.table_name = "topics"
+ klass.serialize :content, Hash
+
+ t = klass.new(:content => { :foo => :bar })
+ assert_equal({ :foo => :bar }, t.content_before_type_cast)
+ t.save!
+ t.reload
+ assert_equal({ :foo => :bar }, t.content_before_type_cast)
+ end
+
+ def test_serialized_attribute_declared_in_subclass
+ hash = { 'important1' => 'value1', 'important2' => 'value2' }
+ important_topic = ImportantTopic.create("important" => hash)
+ assert_equal(hash, important_topic.important)
+
+ important_topic.reload
+ assert_equal(hash, important_topic.important)
+ assert_equal(hash, important_topic.read_attribute(:important))
+ end
+
def test_serialized_time_attribute
myobj = Time.local(2008,1,1,1,0)
topic = Topic.create("content" => myobj).reload
@@ -1247,11 +1331,24 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal(myobj, topic.content)
end
- def test_nil_serialized_attribute_with_class_constraint
+ def test_nil_serialized_attribute_without_class_constraint
topic = Topic.new
assert_nil topic.content
end
+ def test_nil_not_serialized_without_class_constraint
+ assert Topic.new(:content => nil).save
+ assert_equal 1, Topic.where(:content => nil).count
+ end
+
+ def test_nil_not_serialized_with_class_constraint
+ Topic.serialize :content, Hash
+ assert Topic.new(:content => nil).save
+ assert_equal 1, Topic.where(:content => nil).count
+ ensure
+ Topic.serialize(:content)
+ end
+
def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
myobj = MyObject.new('value1', 'value2')
topic = Topic.new(:content => myobj)
@@ -1359,22 +1456,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal author_name, Topic.find(topic.id).author_name
end
- if RUBY_VERSION < '1.9'
- def test_quote_chars
- with_kcode('UTF8') do
- str = 'The Narrator'
- topic = Topic.create(:author_name => str)
- assert_equal str, topic.author_name
-
- assert_kind_of ActiveSupport::Multibyte.proxy_class, str.mb_chars
- topic = Topic.find_by_author_name(str.mb_chars)
-
- assert_kind_of Topic, topic
- assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars"
- end
- end
- end
-
def test_toggle_attribute
assert !topics(:first).approved?
topics(:first).toggle!(:approved)
@@ -1401,107 +1482,100 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal dev, dev.reload
end
- def test_define_attr_method_with_value
- k = Class.new( ActiveRecord::Base )
- k.send(:define_attr_method, :table_name, "foo")
- assert_equal "foo", k.table_name
- end
+ def test_switching_between_table_name
+ assert_difference("GoodJoke.count") do
+ Joke.table_name = "cold_jokes"
+ Joke.create
- def test_define_attr_method_with_block
- k = Class.new( ActiveRecord::Base ) do
- class << self
- attr_accessor :foo_key
- end
+ Joke.table_name = "funny_jokes"
+ Joke.create
end
- k.foo_key = "id"
- k.send(:define_attr_method, :foo_key) { "sys_" + original_foo_key }
- assert_equal "sys_id", k.foo_key
end
- def test_set_table_name_with_value
- k = Class.new( ActiveRecord::Base )
- k.table_name = "foo"
- assert_equal "foo", k.table_name
- k.set_table_name "bar"
- assert_equal "bar", k.table_name
+ def test_clear_cash_when_setting_table_name
+ Joke.table_name = "cold_jokes"
+ before_columns = Joke.columns
+ before_seq = Joke.sequence_name
+
+ Joke.table_name = "funny_jokes"
+ after_columns = Joke.columns
+ after_seq = Joke.sequence_name
+
+ assert_not_equal before_columns, after_columns
+ assert_not_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil?
end
- def test_switching_between_table_name
- assert_difference("GoodJoke.count") do
- Joke.set_table_name "cold_jokes"
- Joke.create
+ def test_dont_clear_sequence_name_when_setting_explicitly
+ Joke.sequence_name = "black_jokes_seq"
+ Joke.table_name = "cold_jokes"
+ before_seq = Joke.sequence_name
- Joke.set_table_name "funny_jokes"
- Joke.create
- end
+ Joke.table_name = "funny_jokes"
+ after_seq = Joke.sequence_name
+
+ assert_equal before_seq, after_seq unless before_seq.nil? && after_seq.nil?
+ end
+
+ def test_dont_clear_inheritnce_column_when_setting_explicitly
+ Joke.inheritance_column = "my_type"
+ before_inherit = Joke.inheritance_column
+
+ Joke.reset_column_information
+ after_inherit = Joke.inheritance_column
+
+ assert_equal before_inherit, after_inherit unless before_inherit.blank? && after_inherit.blank?
+ end
+
+ def test_set_table_name_symbol_converted_to_string
+ Joke.table_name = :cold_jokes
+ assert_equal 'cold_jokes', Joke.table_name
end
def test_quoted_table_name_after_set_table_name
klass = Class.new(ActiveRecord::Base)
- klass.set_table_name "foo"
+ klass.table_name = "foo"
assert_equal "foo", klass.table_name
assert_equal klass.connection.quote_table_name("foo"), klass.quoted_table_name
- klass.set_table_name "bar"
+ klass.table_name = "bar"
assert_equal "bar", klass.table_name
assert_equal klass.connection.quote_table_name("bar"), klass.quoted_table_name
end
- def test_set_table_name_with_block
- k = Class.new( ActiveRecord::Base )
- k.set_table_name { "ks" }
- assert_equal "ks", k.table_name
- end
-
- def test_set_primary_key_with_value
+ def test_set_table_name_with_inheritance
k = Class.new( ActiveRecord::Base )
- k.primary_key = "foo"
- assert_equal "foo", k.primary_key
- k.set_primary_key "bar"
- assert_equal "bar", k.primary_key
+ def k.name; "Foo"; end
+ def k.table_name; super + "ks"; end
+ assert_equal "foosks", k.table_name
end
- def test_set_primary_key_with_block
- k = Class.new( ActiveRecord::Base )
- k.primary_key = 'id'
- k.set_primary_key { "sys_" + original_primary_key }
- assert_equal "sys_id", k.primary_key
- end
-
- def test_set_inheritance_column_with_value
- k = Class.new( ActiveRecord::Base )
- k.inheritance_column = "foo"
- assert_equal "foo", k.inheritance_column
- k.set_inheritance_column "bar"
- assert_equal "bar", k.inheritance_column
- end
-
- def test_set_inheritance_column_with_block
- k = Class.new( ActiveRecord::Base )
- k.set_inheritance_column { original_inheritance_column + "_id" }
- assert_equal "type_id", k.inheritance_column
+ def test_sequence_name_with_abstract_class
+ ak = Class.new(ActiveRecord::Base)
+ ak.abstract_class = true
+ k = Class.new(ak)
+ k.table_name = "projects"
+ orig_name = k.sequence_name
+ return skip "sequences not supported by db" unless orig_name
+ assert_equal k.reset_sequence_name, orig_name
end
def test_count_with_join
res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
- res2 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
+ res2 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count
assert_equal res, res2
res3 = nil
assert_nothing_raised do
- res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'",
- :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
+ res3 = Post.where("posts.#{QUOTED_TYPE} = 'Post'").joins("LEFT JOIN comments ON posts.id=comments.post_id").count
end
assert_equal res, res3
res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
res5 = nil
assert_nothing_raised do
- res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
- :joins => "p, comments co",
- :select => "p.id")
+ res5 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count
end
assert_equal res4, res5
@@ -1509,145 +1583,64 @@ class BasicsTest < ActiveRecord::TestCase
res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
res7 = nil
assert_nothing_raised do
- res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
- :joins => "p, comments co",
- :select => "p.id",
- :distinct => true)
+ res7 = Post.where("p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id").joins("p, comments co").select("p.id").count(distinct: true)
end
assert_equal res6, res7
end
- def test_scoped_find_conditions
- scoped_developers = Developer.send(:with_scope, :find => { :conditions => 'salary > 90000' }) do
- Developer.find(:all, :conditions => 'id < 5')
- end
- assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
- assert_equal 3, scoped_developers.size
- end
-
def test_no_limit_offset
assert_nothing_raised do
- Developer.find(:all, :offset => 2)
- end
- end
-
- def test_scoped_find_limit_offset
- scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :offset => 2 }) do
- Developer.find(:all, :order => 'id')
- end
- assert !scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 3, scoped_developers.size
-
- # Test without scoped find conditions to ensure we get the whole thing
- developers = Developer.find(:all, :order => 'id')
- assert_equal Developer.count, developers.size
- end
-
- def test_scoped_find_order
- # Test order in scope
- scoped_developers = Developer.send(:with_scope, :find => { :limit => 1, :order => 'salary DESC' }) do
- Developer.find(:all)
- end
- assert_equal 'Jamis', scoped_developers.first.name
- assert scoped_developers.include?(developers(:jamis))
- # Test scope without order and order in find
- scoped_developers = Developer.send(:with_scope, :find => { :limit => 1 }) do
- Developer.find(:all, :order => 'salary DESC')
- end
- # Test scope order + find order, order has priority
- scoped_developers = Developer.send(:with_scope, :find => { :limit => 3, :order => 'id DESC' }) do
- Developer.find(:all, :order => 'salary ASC')
- end
- assert scoped_developers.include?(developers(:poor_jamis))
- assert ! scoped_developers.include?(developers(:david))
- assert ! scoped_developers.include?(developers(:jamis))
- assert_equal 3, scoped_developers.size
-
- # Test without scoped find conditions to ensure we get the right thing
- assert ! scoped_developers.include?(Developer.find(1))
- assert scoped_developers.include?(Developer.find(11))
- end
-
- def test_scoped_find_limit_offset_including_has_many_association
- topics = Topic.send(:with_scope, :find => {:limit => 1, :offset => 1, :include => :replies}) do
- Topic.find(:all, :order => "topics.id")
- end
- assert_equal 1, topics.size
- assert_equal 2, topics.first.id
- end
-
- def test_scoped_find_order_including_has_many_association
- developers = Developer.send(:with_scope, :find => { :order => 'developers.salary DESC', :include => :projects }) do
- Developer.find(:all)
- end
- assert developers.size >= 2
- for i in 1...developers.size
- assert developers[i-1].salary >= developers[i].salary
+ Developer.scoped(:offset => 2).all
end
end
- def test_scoped_find_with_group_and_having
- developers = Developer.send(:with_scope, :find => { :group => 'developers.salary', :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary" }) do
- Developer.find(:all)
- end
- assert_equal 3, developers.size
- end
-
def test_find_last
- last = Developer.find :last
- assert_equal last, Developer.find(:first, :order => 'id desc')
+ last = Developer.last
+ assert_equal last, Developer.scoped(:order => 'id desc').first
end
def test_last
- assert_equal Developer.find(:first, :order => 'id desc'), Developer.last
+ assert_equal Developer.scoped(:order => 'id desc').first, Developer.last
end
def test_all
developers = Developer.all
assert_kind_of Array, developers
- assert_equal Developer.find(:all), developers
+ assert_equal Developer.all, developers
end
def test_all_with_conditions
- assert_equal Developer.find(:all, :order => 'id desc'), Developer.order('id desc').all
+ assert_equal Developer.scoped(:order => 'id desc').all, Developer.order('id desc').all
end
def test_find_ordered_last
- last = Developer.find :last, :order => 'developers.salary ASC'
- assert_equal last, Developer.find(:all, :order => 'developers.salary ASC').last
+ last = Developer.scoped(:order => 'developers.salary ASC').last
+ assert_equal last, Developer.scoped(:order => 'developers.salary ASC').all.last
end
def test_find_reverse_ordered_last
- last = Developer.find :last, :order => 'developers.salary DESC'
- assert_equal last, Developer.find(:all, :order => 'developers.salary DESC').last
+ last = Developer.scoped(:order => 'developers.salary DESC').last
+ assert_equal last, Developer.scoped(:order => 'developers.salary DESC').all.last
end
def test_find_multiple_ordered_last
- last = Developer.find :last, :order => 'developers.name, developers.salary DESC'
- assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last
+ last = Developer.scoped(:order => 'developers.name, developers.salary DESC').last
+ assert_equal last, Developer.scoped(:order => 'developers.name, developers.salary DESC').all.last
end
def test_find_keeps_multiple_order_values
- combined = Developer.find(:all, :order => 'developers.name, developers.salary')
- assert_equal combined, Developer.find(:all, :order => ['developers.name', 'developers.salary'])
+ combined = Developer.scoped(:order => 'developers.name, developers.salary').all
+ assert_equal combined, Developer.scoped(:order => ['developers.name', 'developers.salary']).all
end
def test_find_keeps_multiple_group_values
- combined = Developer.find(:all, :group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at')
- assert_equal combined, Developer.find(:all, :group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at'])
+ combined = Developer.scoped(:group => 'developers.name, developers.salary, developers.id, developers.created_at, developers.updated_at').all
+ assert_equal combined, Developer.scoped(:group => ['developers.name', 'developers.salary', 'developers.id', 'developers.created_at', 'developers.updated_at']).all
end
def test_find_symbol_ordered_last
- last = Developer.find :last, :order => :salary
- assert_equal last, Developer.find(:all, :order => :salary).last
- end
-
- def test_find_scoped_ordered_last
- last_developer = Developer.send(:with_scope, :find => { :order => 'developers.salary ASC' }) do
- Developer.find(:last)
- end
- assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last
+ last = Developer.scoped(:order => :salary).last
+ assert_equal last, Developer.scoped(:order => :salary).all.last
end
def test_abstract_class
@@ -1673,10 +1666,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_descends_from_active_record
- # Tries to call Object.abstract_class?
- assert_raise(NoMethodError) do
- ActiveRecord::Base.descends_from_active_record?
- end
+ assert !ActiveRecord::Base.descends_from_active_record?
# Abstract subclass of AR::Base.
assert LoosePerson.descends_from_active_record?
@@ -1699,6 +1689,10 @@ class BasicsTest < ActiveRecord::TestCase
# Concrete subclasses an abstract class which has a type column.
assert !SubStiPost.descends_from_active_record?
+
+ assert Teapot.descends_from_active_record?
+ assert !OtherTeapot.descends_from_active_record?
+ assert CoolTeapot.descends_from_active_record?
end
def test_find_on_abstract_base_class_doesnt_use_type_condition
@@ -1721,7 +1715,7 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_to_param_should_return_string
- assert_kind_of String, Client.find(:first).to_param
+ assert_kind_of String, Client.first.to_param
end
def test_inspect_class
@@ -1732,7 +1726,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_inspect_instance
topic = topics(:first)
- assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect
+ assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", important: nil, approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect
end
def test_inspect_new_instance
@@ -1740,8 +1734,8 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_inspect_limited_select_instance
- assert_equal %(#<Topic id: 1>), Topic.find(:first, :select => 'id', :conditions => 'id = 1').inspect
- assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.find(:first, :select => 'id, title', :conditions => 'id = 1').inspect
+ assert_equal %(#<Topic id: 1>), Topic.scoped(:select => 'id', :where => 'id = 1').first.inspect
+ assert_equal %(#<Topic id: 1, title: "The First Topic">), Topic.scoped(:select => 'id, title', :where => 'id = 1').first.inspect
end
def test_inspect_class_without_table
@@ -1761,10 +1755,18 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal "The First Topic", topics(:first).becomes(Reply).title
end
+ def test_becomes_includes_errors
+ company = Company.new(:name => nil)
+ assert !company.valid?
+ original_errors = company.errors
+ client = company.becomes(Client)
+ assert_equal original_errors, client.errors
+ end
+
def test_silence_sets_log_level_to_error_in_block
original_logger = ActiveRecord::Base.logger
log = StringIO.new
- ActiveRecord::Base.logger = Logger.new(log)
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
ActiveRecord::Base.logger.level = Logger::DEBUG
ActiveRecord::Base.silence do
ActiveRecord::Base.logger.warn "warn"
@@ -1778,7 +1780,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_silence_sets_log_level_back_to_level_before_yield
original_logger = ActiveRecord::Base.logger
log = StringIO.new
- ActiveRecord::Base.logger = Logger.new(log)
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
ActiveRecord::Base.logger.level = Logger::WARN
ActiveRecord::Base.silence do
end
@@ -1790,7 +1792,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_benchmark_with_log_level
original_logger = ActiveRecord::Base.logger
log = StringIO.new
- ActiveRecord::Base.logger = Logger.new(log)
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
ActiveRecord::Base.logger.level = Logger::WARN
ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count }
ActiveRecord::Base.benchmark("Warn Topic Count", :level => :warn) { Topic.count }
@@ -1805,10 +1807,8 @@ class BasicsTest < ActiveRecord::TestCase
def test_benchmark_with_use_silence
original_logger = ActiveRecord::Base.logger
log = StringIO.new
- ActiveRecord::Base.logger = Logger.new(log)
- ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => true) { ActiveRecord::Base.logger.debug "Loud" }
+ ActiveRecord::Base.logger = ActiveSupport::Logger.new(log)
ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" }
- assert_no_match(/Loud/, log.string)
assert_match(/Quiet/, log.string)
ensure
ActiveRecord::Base.logger = original_logger
@@ -1840,9 +1840,9 @@ class BasicsTest < ActiveRecord::TestCase
def test_clear_cache!
# preheat cache
- c1 = Post.columns
+ c1 = Post.connection.schema_cache.columns['posts']
ActiveRecord::Base.clear_cache!
- c2 = Post.columns
+ c2 = Post.connection.schema_cache.columns['posts']
assert_not_equal c1, c2
end
@@ -1859,11 +1859,6 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_marshal_round_trip
- if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7"
- return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \
- "to be a Ruby bug.")
- end
-
expected = posts(:welcome)
marshalled = Marshal.dump(expected)
actual = Marshal.load(marshalled)
@@ -1872,11 +1867,6 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_marshal_new_record_round_trip
- if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7"
- return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \
- "to be a Ruby bug.")
- end
-
marshalled = Marshal.dump(Post.new)
post = Marshal.load(marshalled)
@@ -1884,11 +1874,6 @@ class BasicsTest < ActiveRecord::TestCase
end
def test_marshalling_with_associations
- if ENV['TRAVIS'] && RUBY_VERSION == "1.8.7"
- return skip("Marshalling tests disabled for Ruby 1.8.7 on Travis CI due to what appears " \
- "to be a Ruby bug.")
- end
-
post = Post.new
post.comments.build
@@ -1898,8 +1883,17 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal 1, post.comments.length
end
+ def test_marshalling_new_record_round_trip_with_associations
+ post = Post.new
+ post.comments.build
+
+ post = Marshal.load(Marshal.dump(post))
+
+ assert post.new_record?, "should be a new record"
+ end
+
def test_attribute_names
- assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"],
+ assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id", "description"],
Company.attribute_names
end
@@ -1907,7 +1901,7 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal [], NonExistentTable.attribute_names
end
- def test_attribtue_names_on_abstract_class
+ def test_attribute_names_on_abstract_class
assert_equal [], AbstractCompany.attribute_names
end
@@ -1927,7 +1921,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_cache_key_format_for_existing_record_with_updated_at
dev = Developer.first
- assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:number)}", dev.cache_key
+ assert_equal "developers/#{dev.id}-#{dev.updated_at.utc.to_s(:nsec)}", dev.cache_key
end
def test_cache_key_format_for_existing_record_with_nil_updated_at
@@ -1935,4 +1929,84 @@ class BasicsTest < ActiveRecord::TestCase
dev.update_attribute(:updated_at, nil)
assert_match(/\/#{dev.id}$/, dev.cache_key)
end
+
+ def test_uniq_delegates_to_scoped
+ scope = stub
+ Bird.stubs(:scoped).returns(mock(:uniq => scope))
+ assert_equal scope, Bird.uniq
+ end
+
+ def test_active_record_super
+ assert_equal ActiveRecord::Model, ActiveRecord::Base.active_record_super
+ assert_equal ActiveRecord::Base, Topic.active_record_super
+ assert_equal Topic, ImportantTopic.active_record_super
+ assert_equal ActiveRecord::Model, Teapot.active_record_super
+ assert_equal Teapot, OtherTeapot.active_record_super
+ assert_equal ActiveRecord::Model, CoolTeapot.active_record_super
+ end
+
+ def test_table_name_with_2_abstract_subclasses
+ assert_equal "photos", Photo.table_name
+ end
+
+ def test_column_types_typecast
+ topic = Topic.first
+ refute_equal 't.lo', topic.author_name
+
+ attrs = topic.attributes.dup
+ attrs.delete 'id'
+
+ typecast = Class.new {
+ def type_cast value
+ "t.lo"
+ end
+ }
+
+ types = { 'author_name' => typecast.new }
+ topic = Topic.allocate.init_with 'attributes' => attrs,
+ 'column_types' => types
+
+ assert_equal 't.lo', topic.author_name
+ end
+
+ def test_typecasting_aliases
+ assert_equal 10, Topic.select('10 as tenderlove').first.tenderlove
+ end
+
+ def test_slice
+ company = Company.new(:rating => 1, :name => "37signals", :firm_name => "37signals")
+ hash = company.slice(:name, :rating, "arbitrary_method")
+ assert_equal hash[:name], company.name
+ assert_equal hash['name'], company.name
+ assert_equal hash[:rating], company.rating
+ assert_equal hash['arbitrary_method'], company.arbitrary_method
+ assert_equal hash[:arbitrary_method], company.arbitrary_method
+ assert_nil hash[:firm_name]
+ assert_nil hash['firm_name']
+ end
+
+ def test_default_values_are_deeply_dupped
+ company = Company.new
+ company.description << "foo"
+ assert_equal "", Company.new.description
+ end
+
+ ["find_by", "find_by!"].each do |meth|
+ test "#{meth} delegates to scoped" do
+ record = stub
+
+ scope = mock
+ scope.expects(meth).with(:foo, :bar).returns(record)
+
+ klass = Class.new(ActiveRecord::Base)
+ klass.stubs(:scoped => scope)
+
+ assert_equal record, klass.public_send(meth, :foo, :bar)
+ end
+ end
+
+ test "scoped can take a values hash" do
+ klass = Class.new(ActiveRecord::Base)
+ assert_equal ['foo'], klass.scoped(select: 'foo').select_values
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index 660098b9ad..cdd4b49042 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -27,30 +27,18 @@ class EachTest < ActiveRecord::TestCase
def test_each_should_raise_if_select_is_set_without_id
assert_raise(RuntimeError) do
- Post.find_each(:select => :title, :batch_size => 1) { |post| post }
+ Post.select(:title).find_each(:batch_size => 1) { |post| post }
end
end
def test_each_should_execute_if_id_is_in_select
assert_queries(6) do
- Post.find_each(:select => "id, title, type", :batch_size => 2) do |post|
+ Post.select("id, title, type").find_each(:batch_size => 2) do |post|
assert_kind_of Post, post
end
end
end
- def test_each_should_raise_if_the_order_is_set
- assert_raise(RuntimeError) do
- Post.find_each(:order => "title") { |post| post }
- end
- end
-
- def test_each_should_raise_if_the_limit_is_set
- assert_raise(RuntimeError) do
- Post.find_each(:limit => 1) { |post| post }
- end
- end
-
def test_warn_if_limit_scope_is_set
ActiveRecord::Base.logger.expects(:warn)
Post.limit(1).find_each { |post| post }
diff --git a/activerecord/test/cases/binary_test.rb b/activerecord/test/cases/binary_test.rb
index 06c14cb108..25d2896ab0 100644
--- a/activerecord/test/cases/binary_test.rb
+++ b/activerecord/test/cases/binary_test.rb
@@ -8,11 +8,11 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
require 'models/binary'
class BinaryTest < ActiveRecord::TestCase
- FIXTURES = %w(flowers.jpg example.log)
+ FIXTURES = %w(flowers.jpg example.log test.txt)
def test_mixed_encoding
str = "\x80"
- str.force_encoding('ASCII-8BIT') if str.respond_to?(:force_encoding)
+ str.force_encoding('ASCII-8BIT')
binary = Binary.new :name => 'いただきます!', :data => str
binary.save!
@@ -23,7 +23,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
# 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)
+ name.force_encoding('UTF-8')
end
assert_equal 'いただきます!', name
end
@@ -33,7 +33,7 @@ unless current_adapter?(:SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
FIXTURES.each do |filename|
data = File.read(ASSETS_ROOT + "/#{filename}")
- data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding)
+ data.force_encoding('ASCII-8BIT')
data.freeze
bin = Binary.new(:data => data)
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 3652255c38..03aa9fdb27 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -23,6 +23,8 @@ module ActiveRecord
@listener = LogListener.new
@pk = Topic.columns.find { |c| c.primary }
ActiveSupport::Notifications.subscribe('sql.active_record', @listener)
+
+ skip_if_prepared_statement_caching_is_not_supported
end
def teardown
@@ -30,9 +32,6 @@ module ActiveRecord
end
def test_binds_are_logged
- # FIXME: use skip with minitest
- return unless @connection.supports_statement_cache?
-
sub = @connection.substitute_at(@pk, 0)
binds = [[@pk, 1]]
sql = "select * from topics where id = #{sub}"
@@ -44,9 +43,6 @@ module ActiveRecord
end
def test_find_one_uses_binds
- # FIXME: use skip with minitest
- return unless @connection.supports_statement_cache?
-
Topic.find(1)
binds = [[@pk, 1]]
message = @listener.calls.find { |args| args[4][:binds] == binds }
@@ -54,9 +50,6 @@ module ActiveRecord
end
def test_logs_bind_vars
- # FIXME: use skip with minitest
- return unless @connection.supports_statement_cache?
-
pk = Topic.columns.find { |x| x.primary }
payload = {
@@ -86,5 +79,11 @@ module ActiveRecord
logger.sql event
assert_match([[pk.name, 10]].inspect, logger.debugs.first)
end
+
+ private
+
+ def skip_if_prepared_statement_caching_is_not_supported
+ skip('prepared statement caching is not supported') unless @connection.supports_statement_cache?
+ end
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index c38814713a..041f8ffb7c 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/company'
+require "models/contract"
require 'models/topic'
require 'models/edge'
require 'models/club'
@@ -48,13 +49,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_get_maximum_of_field_with_include
- assert_equal 55, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'")
- end
-
- def test_should_get_maximum_of_field_with_scoped_include
- Account.send :with_scope, :find => { :include => :firm, :conditions => "companies.name != 'Summit'" } do
- assert_equal 55, Account.maximum(:credit_limit)
- end
+ assert_equal 55, Account.where("companies.name != 'Summit'").references(:companies).includes(:firm).maximum(:credit_limit)
end
def test_should_get_minimum_of_field
@@ -62,12 +57,12 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_field
- c = Account.sum(:credit_limit, :group => :firm_id)
+ c = Account.group(:firm_id).sum(:credit_limit)
[1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
end
def test_should_group_by_multiple_fields
- c = Account.count(:all, :group => ['firm_id', :credit_limit])
+ c = Account.group('firm_id', :credit_limit).count(:all)
[ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) }
end
@@ -80,32 +75,32 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field
- c = Account.sum(:credit_limit, :group => :firm_id)
+ c = Account.group(:firm_id).sum(:credit_limit)
assert_equal 50, c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
end
def test_should_order_by_grouped_field
- c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id")
+ c = Account.scoped(:group => :firm_id, :order => "firm_id").sum(:credit_limit)
assert_equal [1, 2, 6, 9], c.keys.compact
end
def test_should_order_by_calculation
- c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id")
+ c = Account.scoped(:group => :firm_id, :order => "sum_credit_limit desc, firm_id").sum(:credit_limit)
assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] }
assert_equal [6, 2, 9, 1], c.keys.compact
end
def test_should_limit_calculation
- c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
- :group => :firm_id, :order => "firm_id", :limit => 2)
+ c = Account.scoped(:where => "firm_id IS NOT NULL",
+ :group => :firm_id, :order => "firm_id", :limit => 2).sum(:credit_limit)
assert_equal [1, 2], c.keys.compact
end
def test_should_limit_calculation_with_offset
- c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
- :group => :firm_id, :order => "firm_id", :limit => 2, :offset => 1)
+ c = Account.scoped(:where => "firm_id IS NOT NULL", :group => :firm_id,
+ :order => "firm_id", :limit => 2, :offset => 1).sum(:credit_limit)
assert_equal [2, 6], c.keys.compact
end
@@ -155,16 +150,8 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_having_condition
- c = Account.sum(:credit_limit, :group => :firm_id,
- :having => 'sum(credit_limit) > 50')
- assert_nil c[1]
- assert_equal 105, c[6]
- assert_equal 60, c[2]
- end
-
- def test_should_group_by_summed_field_having_sanitized_condition
- c = Account.sum(:credit_limit, :group => :firm_id,
- :having => ['sum(credit_limit) > ?', 50])
+ c = Account.scoped(:group => :firm_id,
+ :having => 'sum(credit_limit) > 50').sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
@@ -178,19 +165,19 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_association
- c = Account.sum(:credit_limit, :group => :firm)
+ c = Account.group(:firm).sum(:credit_limit)
assert_equal 50, c[companies(:first_firm)]
assert_equal 105, c[companies(:rails_core)]
assert_equal 60, c[companies(:first_client)]
end
def test_should_sum_field_with_conditions
- assert_equal 105, Account.sum(:credit_limit, :conditions => 'firm_id = 6')
+ assert_equal 105, Account.where('firm_id = 6').sum(:credit_limit)
end
def test_should_return_zero_if_sum_conditions_return_nothing
- assert_equal 0, Account.sum(:credit_limit, :conditions => '1 = 2')
- assert_equal 0, companies(:rails_core).companies.sum(:id, :conditions => '1 = 2')
+ assert_equal 0, Account.where('1 = 2').sum(:credit_limit)
+ assert_equal 0, companies(:rails_core).companies.where('1 = 2').sum(:id)
end
def test_sum_should_return_valid_values_for_decimals
@@ -199,24 +186,24 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_group_by_summed_field_with_conditions
- c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
- :group => :firm_id)
+ c = Account.scoped(:where => 'firm_id > 1',
+ :group => :firm_id).sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
end
def test_should_group_by_summed_field_with_conditions_and_having
- c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
- :group => :firm_id,
- :having => 'sum(credit_limit) > 60')
+ c = Account.scoped(:where => 'firm_id > 1',
+ :group => :firm_id,
+ :having => 'sum(credit_limit) > 60').sum(:credit_limit)
assert_nil c[1]
assert_equal 105, c[6]
assert_nil c[2]
end
def test_should_group_by_fields_with_table_alias
- c = Account.sum(:credit_limit, :group => 'accounts.firm_id')
+ c = Account.group('accounts.firm_id').sum(:credit_limit)
assert_equal 50, c[1]
assert_equal 105, c[6]
assert_equal 60, c[2]
@@ -228,14 +215,14 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_calculate_grouped_with_invalid_field
- c = Account.count(:all, :group => 'accounts.firm_id')
+ c = Account.group('accounts.firm_id').count(:all)
assert_equal 1, c[1]
assert_equal 2, c[6]
assert_equal 1, c[2]
end
def test_should_calculate_grouped_association_with_invalid_field
- c = Account.count(:all, :group => :firm)
+ c = Account.group(:firm).count(:all)
assert_equal 1, c[companies(:first_firm)]
assert_equal 2, c[companies(:rails_core)]
assert_equal 1, c[companies(:first_client)]
@@ -254,7 +241,7 @@ class CalculationsTest < ActiveRecord::TestCase
column.expects(:type_cast).with("ABC").returns("ABC")
Account.expects(:columns).at_least_once.returns([column])
- c = Account.count(:all, :group => :firm)
+ c = Account.group(:firm).count(:all)
first_key = c.keys.first
assert_equal Firm, first_key.class
assert_equal 1, c[first_key]
@@ -262,22 +249,14 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_calculate_grouped_association_with_foreign_key_option
Account.belongs_to :another_firm, :class_name => 'Firm', :foreign_key => 'firm_id'
- c = Account.count(:all, :group => :another_firm)
+ c = Account.group(:another_firm).count(:all)
assert_equal 1, c[companies(:first_firm)]
assert_equal 2, c[companies(:rails_core)]
assert_equal 1, c[companies(:first_client)]
end
- def test_should_not_modify_options_when_using_includes
- options = {:conditions => 'companies.id > 1', :include => :firm}
- options_copy = options.dup
-
- Account.count(:all, options)
- assert_equal options_copy, options
- end
-
def test_should_calculate_grouped_by_function
- c = Company.count(:all, :group => "UPPER(#{QUOTED_TYPE})")
+ c = Company.group("UPPER(#{QUOTED_TYPE})").count(:all)
assert_equal 2, c[nil]
assert_equal 1, c['DEPENDENTFIRM']
assert_equal 4, c['CLIENT']
@@ -285,7 +264,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_calculate_grouped_by_function_with_table_alias
- c = Company.count(:all, :group => "UPPER(companies.#{QUOTED_TYPE})")
+ c = Company.group("UPPER(companies.#{QUOTED_TYPE})").count(:all)
assert_equal 2, c[nil]
assert_equal 1, c['DEPENDENTFIRM']
assert_equal 4, c['CLIENT']
@@ -305,25 +284,24 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_sum_scoped_field_with_conditions
- assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7')
+ assert_equal 8, companies(:rails_core).companies.where('id > 7').sum(:id)
end
def test_should_group_by_scoped_field
- c = companies(:rails_core).companies.sum(:id, :group => :name)
+ c = companies(:rails_core).companies.group(:name).sum(:id)
assert_equal 7, c['Leetsoft']
assert_equal 8, c['Jadedpixel']
end
def test_should_group_by_summed_field_through_association_and_having
- c = companies(:rails_core).companies.sum(:id, :group => :name,
- :having => 'sum(id) > 7')
+ c = companies(:rails_core).companies.group(:name).having('sum(id) > 7').sum(:id)
assert_nil c['Leetsoft']
assert_equal 8, c['Jadedpixel']
end
def test_should_count_selected_field_with_include
- assert_equal 6, Account.count(:distinct => true, :include => :firm)
- assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit)
+ assert_equal 6, Account.includes(:firm).count(:distinct => true)
+ assert_equal 4, Account.includes(:firm).select(:credit_limit).count(:distinct => true)
end
def test_should_not_perform_joined_include_by_default
@@ -347,11 +325,11 @@ class CalculationsTest < ActiveRecord::TestCase
Account.last.update_column('credit_limit', 49)
Account.first.update_column('credit_limit', 51)
- assert_equal 1, Account.scoped(:select => "credit_limit").count(:conditions => ['credit_limit >= 50'])
+ assert_equal 1, Account.scoped(:select => "credit_limit").where('credit_limit >= 50').count
end
def test_should_count_manual_select_with_include
- assert_equal 6, Account.count(:select => "DISTINCT accounts.id", :include => :firm)
+ assert_equal 6, Account.scoped(:select => "DISTINCT accounts.id", :includes => :firm).count
end
def test_count_with_column_parameter
@@ -359,16 +337,16 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_count_with_column_and_options_parameter
- assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50 AND firm_id IS NOT NULL")
+ assert_equal 2, Account.where("credit_limit = 50 AND firm_id IS NOT NULL").count(:firm_id)
end
def test_should_count_field_in_joined_table
- assert_equal 5, Account.count('companies.id', :joins => :firm)
- assert_equal 4, Account.count('companies.id', :joins => :firm, :distinct => true)
+ assert_equal 5, Account.joins(:firm).count('companies.id')
+ assert_equal 4, Account.joins(:firm).count('companies.id', :distinct => true)
end
def test_should_count_field_in_joined_table_with_group_by
- c = Account.count('companies.id', :group => 'accounts.firm_id', :joins => :firm)
+ c = Account.scoped(:group => 'accounts.firm_id', :joins => :firm).count('companies.id')
[1,6,2,9].each { |firm_id| assert c.keys.include?(firm_id) }
end
@@ -391,17 +369,17 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_count_with_from_option
- assert_equal Company.count(:all), Company.count(:all, :from => 'companies')
- assert_equal Account.count(:all, :conditions => "credit_limit = 50"),
- Account.count(:all, :from => 'accounts', :conditions => "credit_limit = 50")
- assert_equal Company.count(:type, :conditions => {:type => "Firm"}),
- Company.count(:type, :conditions => {:type => "Firm"}, :from => 'companies')
+ assert_equal Company.count(:all), Company.from('companies').count(:all)
+ assert_equal Account.where("credit_limit = 50").count(:all),
+ Account.from('accounts').where("credit_limit = 50").count(:all)
+ assert_equal Company.where(:type => "Firm").count(:type),
+ Company.where(:type => "Firm").from('companies').count(:type)
end
def test_sum_with_from_option
- assert_equal Account.sum(:credit_limit), Account.sum(:credit_limit, :from => 'accounts')
- assert_equal Account.sum(:credit_limit, :conditions => "credit_limit > 50"),
- Account.sum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
+ assert_equal Account.sum(:credit_limit), Account.from('accounts').sum(:credit_limit)
+ assert_equal Account.where("credit_limit > 50").sum(:credit_limit),
+ Account.where("credit_limit > 50").from('accounts').sum(:credit_limit)
end
def test_sum_array_compatibility
@@ -409,33 +387,33 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_average_with_from_option
- assert_equal Account.average(:credit_limit), Account.average(:credit_limit, :from => 'accounts')
- assert_equal Account.average(:credit_limit, :conditions => "credit_limit > 50"),
- Account.average(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
+ assert_equal Account.average(:credit_limit), Account.from('accounts').average(:credit_limit)
+ assert_equal Account.where("credit_limit > 50").average(:credit_limit),
+ Account.where("credit_limit > 50").from('accounts').average(:credit_limit)
end
def test_minimum_with_from_option
- assert_equal Account.minimum(:credit_limit), Account.minimum(:credit_limit, :from => 'accounts')
- assert_equal Account.minimum(:credit_limit, :conditions => "credit_limit > 50"),
- Account.minimum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
+ assert_equal Account.minimum(:credit_limit), Account.from('accounts').minimum(:credit_limit)
+ assert_equal Account.where("credit_limit > 50").minimum(:credit_limit),
+ Account.where("credit_limit > 50").from('accounts').minimum(:credit_limit)
end
def test_maximum_with_from_option
- assert_equal Account.maximum(:credit_limit), Account.maximum(:credit_limit, :from => 'accounts')
- assert_equal Account.maximum(:credit_limit, :conditions => "credit_limit > 50"),
- Account.maximum(:credit_limit, :from => 'accounts', :conditions => "credit_limit > 50")
+ assert_equal Account.maximum(:credit_limit), Account.from('accounts').maximum(:credit_limit)
+ assert_equal Account.where("credit_limit > 50").maximum(:credit_limit),
+ Account.where("credit_limit > 50").from('accounts').maximum(:credit_limit)
end
def test_from_option_with_specified_index
if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2'
- assert_equal Edge.count(:all), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)')
- assert_equal Edge.count(:all, :conditions => 'sink_id < 5'),
- Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5')
+ assert_equal Edge.count(:all), Edge.from('edges USE INDEX(unique_edge_index)').count(:all)
+ assert_equal Edge.where('sink_id < 5').count(:all),
+ Edge.from('edges USE INDEX(unique_edge_index)').where('sink_id < 5').count(:all)
end
end
def test_from_option_with_table_different_than_class
- assert_equal Account.count(:all), Company.count(:all, :from => 'accounts')
+ assert_equal Account.count(:all), Company.from('accounts').count(:all)
end
def test_distinct_is_honored_when_used_with_count_operation_after_group
@@ -446,4 +424,64 @@ class CalculationsTest < ActiveRecord::TestCase
distinct_authors_for_approved_count = Topic.group(:approved).count(:author_name, :distinct => true)[true]
assert_equal distinct_authors_for_approved_count, 2
end
+
+ def test_pluck
+ assert_equal [1,2,3,4], Topic.order(:id).pluck(:id)
+ end
+
+ def test_pluck_type_cast
+ topic = topics(:first)
+ relation = Topic.where(:id => topic.id)
+ assert_equal [ topic.approved ], relation.pluck(:approved)
+ assert_equal [ topic.last_read ], relation.pluck(:last_read)
+ assert_equal [ topic.written_on ], relation.pluck(:written_on)
+ end
+
+ def test_pluck_and_uniq
+ assert_equal [50, 53, 55, 60], Account.order(:credit_limit).uniq.pluck(:credit_limit)
+ end
+
+ def test_pluck_in_relation
+ company = Company.first
+ contract = company.contracts.create!
+ assert_equal [contract.id], company.contracts.pluck(:id)
+ end
+
+ def test_pluck_with_serialization
+ t = Topic.create!(:content => { :foo => :bar })
+ assert_equal [{:foo => :bar}], Topic.where(:id => t.id).pluck(:content)
+ end
+
+ def test_pluck_with_qualified_column_name
+ assert_equal [1,2,3,4], Topic.order(:id).pluck("topics.id")
+ end
+
+ def test_pluck_auto_table_name_prefix
+ c = Company.create!(:name => "test", :contracts => [Contract.new])
+ assert_equal [c.id], Company.joins(:contracts).pluck(:id)
+ end
+
+ def test_pluck_not_auto_table_name_prefix_if_column_joined
+ Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
+ assert_equal [7], Company.joins(:contracts).pluck(:developer_id)
+ end
+
+ def test_pluck_with_selection_clause
+ assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT credit_limit').sort
+ assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT accounts.credit_limit').sort
+ assert_equal [50, 53, 55, 60], Account.pluck('DISTINCT(credit_limit)').sort
+
+ # MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless
+ # an alias is provided. Without the alias, the column cannot be found
+ # and properly typecast.
+ assert_equal [50 + 53 + 55 + 60], Account.pluck('SUM(DISTINCT(credit_limit)) as credit_limit')
+ end
+
+ def test_pluck_expects_a_single_selection
+ assert_raise(ArgumentError) { Account.pluck 'id, credit_limit' }
+ end
+
+ def test_plucks_with_ids
+ assert_equal Company.all.map(&:id).sort, Company.ids.sort
+ end
end
diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb
index 7f4d25790b..7690769226 100644
--- a/activerecord/test/cases/callbacks_test.rb
+++ b/activerecord/test/cases/callbacks_test.rb
@@ -1,7 +1,7 @@
require "cases/helper"
class CallbackDeveloper < ActiveRecord::Base
- set_table_name 'developers'
+ self.table_name = 'developers'
class << self
def callback_string(callback_method)
@@ -48,7 +48,7 @@ class CallbackDeveloperWithFalseValidation < CallbackDeveloper
end
class ParentDeveloper < ActiveRecord::Base
- set_table_name 'developers'
+ self.table_name = 'developers'
attr_accessor :after_save_called
before_validation {|record| record.after_save_called = true}
end
@@ -58,7 +58,7 @@ class ChildDeveloper < ParentDeveloper
end
class RecursiveCallbackDeveloper < ActiveRecord::Base
- set_table_name 'developers'
+ self.table_name = 'developers'
before_save :on_before_save
after_save :on_after_save
@@ -79,7 +79,7 @@ class RecursiveCallbackDeveloper < ActiveRecord::Base
end
class ImmutableDeveloper < ActiveRecord::Base
- set_table_name 'developers'
+ self.table_name = 'developers'
validates_inclusion_of :salary, :in => 50000..200000
@@ -98,7 +98,7 @@ class ImmutableDeveloper < ActiveRecord::Base
end
class ImmutableMethodDeveloper < ActiveRecord::Base
- set_table_name 'developers'
+ self.table_name = 'developers'
validates_inclusion_of :salary, :in => 50000..200000
@@ -118,7 +118,7 @@ class ImmutableMethodDeveloper < ActiveRecord::Base
end
class OnCallbacksDeveloper < ActiveRecord::Base
- set_table_name 'developers'
+ self.table_name = 'developers'
before_validation { history << :before_validation }
before_validation(:on => :create){ history << :before_validation_on_create }
@@ -138,7 +138,7 @@ class OnCallbacksDeveloper < ActiveRecord::Base
end
class CallbackCancellationDeveloper < ActiveRecord::Base
- set_table_name 'developers'
+ self.table_name = 'developers'
attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called
attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy
diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb
index c7dcc21809..b874adc081 100644
--- a/activerecord/test/cases/coders/yaml_column_test.rb
+++ b/activerecord/test/cases/coders/yaml_column_test.rb
@@ -9,6 +9,13 @@ module ActiveRecord
assert_equal Object, coder.object_class
end
+ def test_type_mismatch_on_different_classes_on_dump
+ coder = YAMLColumn.new(Array)
+ assert_raises(SerializationTypeMismatch) do
+ coder.dump("a")
+ end
+ end
+
def test_type_mismatch_on_different_classes
coder = YAMLColumn.new(Array)
assert_raises(SerializationTypeMismatch) do
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index 14884e42af..a44b49466f 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -126,17 +126,20 @@ module ActiveRecord
if current_adapter?(:PostgreSQLAdapter)
def test_bigint_column_should_map_to_integer
- bigint_column = PostgreSQLColumn.new('number', nil, "bigint")
+ oid = PostgreSQLAdapter::OID::Identity.new
+ bigint_column = PostgreSQLColumn.new('number', nil, oid, "bigint")
assert_equal :integer, bigint_column.type
end
def test_smallint_column_should_map_to_integer
- smallint_column = PostgreSQLColumn.new('number', nil, "smallint")
+ oid = PostgreSQLAdapter::OID::Identity.new
+ smallint_column = PostgreSQLColumn.new('number', nil, oid, "smallint")
assert_equal :integer, smallint_column.type
end
def test_uuid_column_should_map_to_string
- uuid_column = PostgreSQLColumn.new('unique_id', nil, "uuid")
+ oid = PostgreSQLAdapter::OID::Identity.new
+ uuid_column = PostgreSQLColumn.new('unique_id', nil, oid, "uuid")
assert_equal :string, uuid_column.type
end
end
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
new file mode 100644
index 0000000000..4111a5f808
--- /dev/null
+++ b/activerecord/test/cases/column_test.rb
@@ -0,0 +1,81 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class ColumnTest < ActiveRecord::TestCase
+ def test_type_cast_boolean
+ column = Column.new("field", nil, "boolean")
+ assert column.type_cast(true)
+ assert column.type_cast(1)
+ assert column.type_cast('1')
+ assert column.type_cast('t')
+ assert column.type_cast('T')
+ assert column.type_cast('true')
+ assert column.type_cast('TRUE')
+ assert column.type_cast('on')
+ assert column.type_cast('ON')
+ assert !column.type_cast(false)
+ assert !column.type_cast(0)
+ assert !column.type_cast('0')
+ assert !column.type_cast('f')
+ assert !column.type_cast('F')
+ assert !column.type_cast('false')
+ assert !column.type_cast('FALSE')
+ assert !column.type_cast('off')
+ assert !column.type_cast('OFF')
+ end
+
+ def test_type_cast_integer
+ column = Column.new("field", nil, "integer")
+ assert_equal 1, column.type_cast(1)
+ assert_equal 1, column.type_cast('1')
+ assert_equal 1, column.type_cast('1ignore')
+ assert_equal 0, column.type_cast('bad1')
+ assert_equal 0, column.type_cast('bad')
+ assert_equal 1, column.type_cast(1.7)
+ assert_nil column.type_cast(nil)
+ end
+
+ def test_type_cast_non_integer_to_integer
+ column = Column.new("field", nil, "integer")
+ assert_raises(NoMethodError) do
+ column.type_cast([])
+ end
+ assert_raises(NoMethodError) do
+ column.type_cast(true)
+ end
+ assert_raises(NoMethodError) do
+ column.type_cast(false)
+ end
+ end
+
+ def test_type_cast_time
+ column = Column.new("field", nil, "time")
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ time_string = Time.now.utc.strftime("%T")
+ assert_equal time_string, column.type_cast(time_string).strftime("%T")
+ end
+
+ def test_type_cast_datetime_and_timestamp
+ [Column.new("field", nil, "datetime"), Column.new("field", nil, "timestamp")].each do |column|
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ datetime_string = Time.now.utc.strftime("%FT%T")
+ assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T")
+ end
+ end
+
+ def test_type_cast_date
+ column = Column.new("field", nil, "date")
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ date_string = Time.now.utc.strftime("%F")
+ assert_equal date_string, column.type_cast(date_string).strftime("%F")
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
new file mode 100644
index 0000000000..7dc6e8afcb
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb
@@ -0,0 +1,54 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class AbstractAdapterTest < ActiveRecord::TestCase
+ attr_reader :adapter
+
+ def setup
+ @adapter = AbstractAdapter.new nil, nil
+ end
+
+ def test_in_use?
+ # FIXME: change to refute in Rails 4.0 / mt
+ assert !adapter.in_use?, 'adapter is not in use'
+ assert adapter.lease, 'lease adapter'
+ assert adapter.in_use?, 'adapter is in use'
+ end
+
+ def test_lease_twice
+ assert adapter.lease, 'should lease adapter'
+ assert !adapter.lease, 'should not lease adapter'
+ end
+
+ def test_last_use
+ assert !adapter.last_use
+ adapter.lease
+ assert adapter.last_use
+ end
+
+ def test_expire_mutates_in_use
+ assert adapter.lease, 'lease adapter'
+ assert adapter.in_use?, 'adapter is in use'
+ adapter.expire
+ assert !adapter.in_use?, 'adapter is in use'
+ end
+
+ def test_close
+ pool = ConnectionPool.new(ConnectionSpecification.new({}, nil))
+ pool.connections << adapter
+ adapter.pool = pool
+
+ # Make sure the pool marks the connection in use
+ assert_equal adapter, pool.connection
+ assert adapter.in_use?
+
+ # Close should put the adapter back in the pool
+ adapter.close
+ assert !adapter.in_use?
+
+ assert_equal adapter, pool.connection
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index bd0d161838..dc99ac665c 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -8,6 +8,9 @@ module ActiveRecord
@handler.establish_connection 'america', Base.connection_pool.spec
@klass = Class.new do
def self.name; 'america'; end
+ class << self
+ alias active_record_super superclass
+ end
end
@subklass = Class.new(@klass) do
def self.name; 'north america'; end
@@ -40,8 +43,6 @@ module ActiveRecord
def test_retrieve_connection_pool_uses_superclass_pool_after_subclass_establish_and_remove
@handler.establish_connection 'north america', Base.connection_pool.spec
- assert_not_same @handler.retrieve_connection_pool(@klass),
- @handler.retrieve_connection_pool(@subklass)
@handler.remove_connection @subklass
assert_same @handler.retrieve_connection_pool(@klass),
diff --git a/activerecord/test/cases/connection_adapters/connection_specification_test.rb b/activerecord/test/cases/connection_adapters/connection_specification_test.rb
new file mode 100644
index 0000000000..ea2196cda2
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/connection_specification_test.rb
@@ -0,0 +1,12 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionSpecificationTest < ActiveRecord::TestCase
+ def test_dup_deep_copy_config
+ spec = ConnectionSpecification.new({ :a => :b }, "bar")
+ assert_not_equal(spec.config.object_id, spec.dup.config.object_id)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_adapters/quoting_test.rb b/activerecord/test/cases/connection_adapters/quoting_test.rb
new file mode 100644
index 0000000000..59dcb96ebc
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/quoting_test.rb
@@ -0,0 +1,13 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ module Quoting
+ class QuotingTest < ActiveRecord::TestCase
+ def test_quoting_classes
+ assert_equal "'Object'", AbstractAdapter.new(nil).quote(Object)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
new file mode 100644
index 0000000000..541e983758
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb
@@ -0,0 +1,59 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class SchemaCacheTest < ActiveRecord::TestCase
+ def setup
+ connection = ActiveRecord::Base.connection
+ @cache = SchemaCache.new connection
+ end
+
+ def test_primary_key
+ assert_equal 'id', @cache.primary_keys['posts']
+ end
+
+ def test_primary_key_for_non_existent_table
+ assert_nil @cache.primary_keys['omgponies']
+ end
+
+ def test_caches_columns
+ columns = @cache.columns['posts']
+ assert_equal columns, @cache.columns['posts']
+ end
+
+ def test_caches_columns_hash
+ columns_hash = @cache.columns_hash['posts']
+ assert_equal columns_hash, @cache.columns_hash['posts']
+ end
+
+ def test_clearing
+ @cache.columns['posts']
+ @cache.columns_hash['posts']
+ @cache.tables['posts']
+ @cache.primary_keys['posts']
+
+ @cache.clear!
+
+ assert_equal 0, @cache.columns.size
+ assert_equal 0, @cache.columns_hash.size
+ assert_equal 0, @cache.tables.size
+ assert_equal 0, @cache.primary_keys.size
+ end
+
+ def test_dump_and_load
+ @cache.columns['posts']
+ @cache.columns_hash['posts']
+ @cache.tables['posts']
+ @cache.primary_keys['posts']
+
+ @cache = Marshal.load(Marshal.dump(@cache))
+
+ assert_equal 12, @cache.columns['posts'].size
+ assert_equal 12, @cache.columns_hash['posts'].size
+ assert @cache.tables['posts']
+ assert_equal 'id', @cache.primary_keys['posts']
+ end
+
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index f554ceef35..fe1b40d884 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "rack"
module ActiveRecord
module ConnectionAdapters
@@ -25,38 +26,25 @@ module ActiveRecord
assert ActiveRecord::Base.connection_handler.active_connections?
end
- class FakeBase < ActiveRecord::Base
- def self.establish_connection spec
- String === spec ? super : spec
- end
- end
+ def test_connection_pool_per_pid
+ return skip('must support fork') unless Process.respond_to?(:fork)
- def test_url_host_no_db
- spec = FakeBase.establish_connection 'postgres://foo?encoding=utf8'
- assert_equal({
- :adapter => "postgresql",
- :database => "",
- :host => "foo",
- :encoding => "utf8" }, spec)
- end
+ object_id = ActiveRecord::Base.connection.object_id
- def test_url_host_db
- spec = FakeBase.establish_connection 'postgres://foo/bar?encoding=utf8'
- assert_equal({
- :adapter => "postgresql",
- :database => "bar",
- :host => "foo",
- :encoding => "utf8" }, spec)
- end
+ rd, wr = IO.pipe
+
+ pid = fork {
+ rd.close
+ wr.write Marshal.dump ActiveRecord::Base.connection.object_id
+ wr.close
+ exit!
+ }
+
+ wr.close
- def test_url_port
- spec = FakeBase.establish_connection 'postgres://foo:123?encoding=utf8'
- assert_equal({
- :adapter => "postgresql",
- :database => "",
- :port => 123,
- :host => "foo",
- :encoding => "utf8" }, spec)
+ Process.waitpid pid
+ assert_not_equal object_id, Marshal.load(rd.read)
+ rd.close
end
def test_app_delegation
@@ -114,9 +102,10 @@ module ActiveRecord
test "proxy is polite to it's body and responds to it" do
body = Class.new(String) { def to_path; "/path"; end }.new
- proxy = ConnectionManagement::Proxy.new(body)
- assert proxy.respond_to?(:to_path)
- assert_equal proxy.to_path, "/path"
+ app = lambda { |_| [200, {}, body] }
+ response_body = ConnectionManagement.new(app).call(@env)[2]
+ assert response_body.respond_to?(:to_path)
+ assert_equal response_body.to_path, "/path"
end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 8a0f453127..8dc9f761c2 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -3,7 +3,11 @@ require "cases/helper"
module ActiveRecord
module ConnectionAdapters
class ConnectionPoolTest < ActiveRecord::TestCase
+ attr_reader :pool
+
def setup
+ super
+
# Keep a duplicate pool so we do not bother others
@pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
@@ -18,73 +22,161 @@ module ActiveRecord
end
end
- def test_active_connection?
- assert !@pool.active_connection?
- assert @pool.connection
- assert @pool.active_connection?
- @pool.release_connection
- assert !@pool.active_connection?
+ def teardown
+ super
+ @pool.disconnect!
end
- def test_pool_caches_columns
- columns = @pool.columns['posts']
- assert_equal columns, @pool.columns['posts']
+ def active_connections(pool)
+ pool.connections.find_all(&:in_use?)
end
- def test_pool_caches_columns_hash
- columns_hash = @pool.columns_hash['posts']
- assert_equal columns_hash, @pool.columns_hash['posts']
+ def test_checkout_after_close
+ connection = pool.connection
+ assert connection.in_use?
+
+ connection.close
+ assert !connection.in_use?
+
+ assert pool.connection.in_use?
end
- def test_clearing_column_cache
- @pool.columns['posts']
- @pool.columns_hash['posts']
+ def test_released_connection_moves_between_threads
+ thread_conn = nil
- @pool.clear_cache!
+ Thread.new {
+ pool.with_connection do |conn|
+ thread_conn = conn
+ end
+ }.join
- assert_equal 0, @pool.columns.size
- assert_equal 0, @pool.columns_hash.size
- end
+ assert thread_conn
- def test_primary_key
- assert_equal 'id', @pool.primary_keys['posts']
+ Thread.new {
+ pool.with_connection do |conn|
+ assert_equal thread_conn, conn
+ end
+ }.join
end
- def test_primary_key_for_non_existent_table
- assert_equal 'id', @pool.primary_keys['omgponies']
+ def test_with_connection
+ assert_equal 0, active_connections(pool).size
+
+ main_thread = pool.connection
+ assert_equal 1, active_connections(pool).size
+
+ Thread.new {
+ pool.with_connection do |conn|
+ assert conn
+ assert_equal 2, active_connections(pool).size
+ end
+ assert_equal 1, active_connections(pool).size
+ }.join
+
+ main_thread.close
+ assert_equal 0, active_connections(pool).size
end
- def test_primary_key_is_set_on_columns
- posts_columns = @pool.columns_hash['posts']
- assert posts_columns['id'].primary
+ def test_active_connection_in_use
+ assert !pool.active_connection?
+ main_thread = pool.connection
+
+ assert pool.active_connection?
+
+ main_thread.close
+
+ assert !pool.active_connection?
+ end
- (posts_columns.keys - ['id']).each do |key|
- assert !posts_columns[key].primary
+ def test_full_pool_exception
+ assert_raises(PoolFullError) do
+ (@pool.size + 1).times do
+ @pool.checkout
+ end
end
end
- def test_clear_stale_cached_connections!
- pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
+ def test_full_pool_blocks
+ cs = @pool.size.times.map { @pool.checkout }
+ t = Thread.new { @pool.checkout }
- threads = [
- Thread.new { pool.connection },
- Thread.new { pool.connection }]
+ # make sure our thread is in the timeout section
+ Thread.pass until t.status == "sleep"
- threads.map { |t| t.join }
+ connection = cs.first
+ connection.close
+ assert_equal connection, t.join.value
+ end
- pool.extend Module.new {
- attr_accessor :checkins
- def checkin conn
- @checkins << conn
- conn.object_id
- end
- }
- pool.checkins = []
+ def test_removing_releases_latch
+ cs = @pool.size.times.map { @pool.checkout }
+ t = Thread.new { @pool.checkout }
+
+ # make sure our thread is in the timeout section
+ Thread.pass until t.status == "sleep"
+
+ connection = cs.first
+ @pool.remove connection
+ assert_respond_to t.join.value, :execute
+ end
+
+ def test_reap_and_active
+ @pool.checkout
+ @pool.checkout
+ @pool.checkout
+ @pool.timeout = 0
+
+ connections = @pool.connections.dup
+
+ @pool.reap
+
+ assert_equal connections.length, @pool.connections.length
+ end
+
+ def test_reap_inactive
+ @pool.checkout
+ @pool.checkout
+ @pool.checkout
+ @pool.timeout = 0
+
+ connections = @pool.connections.dup
+ connections.each do |conn|
+ conn.extend(Module.new { def active?; false; end; })
+ end
+
+ @pool.reap
+
+ assert_equal 0, @pool.connections.length
+ ensure
+ connections.each(&:close)
+ end
+
+ def test_remove_connection
+ conn = @pool.checkout
+ assert conn.in_use?
+
+ length = @pool.connections.length
+ @pool.remove conn
+ assert conn.in_use?
+ assert_equal(length - 1, @pool.connections.length)
+ ensure
+ conn.close
+ end
+
+ def test_remove_connection_for_thread
+ conn = @pool.connection
+ @pool.remove conn
+ assert_not_equal(conn, @pool.connection)
+ ensure
+ conn.close if conn
+ end
- cleared_threads = pool.clear_stale_cached_connections!
- assert((cleared_threads - threads.map { |x| x.object_id }).empty?,
- "threads should have been removed")
- assert_equal pool.checkins.length, threads.length
+ def test_active_connection?
+ assert !@pool.active_connection?
+ assert @pool.connection
+ assert @pool.active_connection?
+ @pool.release_connection
+ assert !@pool.active_connection?
end
def test_checkout_behaviour
@@ -96,24 +188,16 @@ module ActiveRecord
threads << Thread.new(i) do |pool_count|
connection = pool.connection
assert_not_nil connection
+ connection.close
end
end
- threads.each {|t| t.join}
+ threads.each(&:join)
Thread.new do
- threads.each do |t|
- thread_ids = pool.instance_variable_get(:@reserved_connections).keys
- assert thread_ids.include?(t.object_id)
- end
-
- pool.connection
- threads.each do |t|
- thread_ids = pool.instance_variable_get(:@reserved_connections).keys
- assert !thread_ids.include?(t.object_id)
- end
- end.join()
-
+ assert pool.connection
+ pool.connection.close
+ end.join
end
def test_automatic_reconnect=
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
new file mode 100644
index 0000000000..e6cb1b9521
--- /dev/null
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -0,0 +1,41 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionSpecification
+ class ResolverTest < ActiveRecord::TestCase
+ def resolve(spec)
+ Resolver.new(spec, {}).spec.config
+ end
+
+ def test_url_host_no_db
+ spec = resolve 'mysql://foo?encoding=utf8'
+ assert_equal({
+ :adapter => "mysql",
+ :database => "",
+ :host => "foo",
+ :encoding => "utf8" }, spec)
+ end
+
+ def test_url_host_db
+ spec = resolve 'mysql://foo/bar?encoding=utf8'
+ assert_equal({
+ :adapter => "mysql",
+ :database => "bar",
+ :host => "foo",
+ :encoding => "utf8" }, spec)
+ end
+
+ def test_url_port
+ spec = resolve 'mysql://foo:123?encoding=utf8'
+ assert_equal({
+ :adapter => "mysql",
+ :database => "",
+ :port => 123,
+ :host => "foo",
+ :encoding => "utf8" }, spec)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 3ed96a3ec8..cd3d19e783 100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
@@ -6,9 +6,11 @@ require 'models/engine'
require 'models/reply'
require 'models/category'
require 'models/categorization'
+require 'models/dog'
+require 'models/dog_lover'
class CounterCacheTest < ActiveRecord::TestCase
- fixtures :topics, :categories, :categorizations, :cars
+ fixtures :topics, :categories, :categorizations, :cars, :dogs, :dog_lovers
class ::SpecialTopic < ::Topic
has_many :special_replies, :foreign_key => 'parent_id'
@@ -61,7 +63,7 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
- test "reset counter should with belongs_to which has class_name" do
+ test "reset counter with belongs_to which has class_name" do
car = cars(:honda)
assert_nothing_raised do
Car.reset_counters(car.id, :engines)
@@ -71,6 +73,20 @@ class CounterCacheTest < ActiveRecord::TestCase
end
end
+ test "reset the right counter if two have the same class_name" do
+ david = dog_lovers(:david)
+
+ DogLover.increment_counter(:bred_dogs_count, david.id)
+ DogLover.increment_counter(:trained_dogs_count, david.id)
+
+ assert_difference 'david.reload.bred_dogs_count', -1 do
+ DogLover.reset_counters(david.id, :bred_dogs)
+ end
+ assert_difference 'david.reload.trained_dogs_count', -1 do
+ DogLover.reset_counters(david.id, :trained_dogs)
+ end
+ end
+
test "update counter with initial null value" do
category = categories(:general)
assert_equal 2, category.categorizations.count
diff --git a/activerecord/test/cases/custom_locking_test.rb b/activerecord/test/cases/custom_locking_test.rb
index d63ecdbcc5..42ef51ef3e 100644
--- a/activerecord/test/cases/custom_locking_test.rb
+++ b/activerecord/test/cases/custom_locking_test.rb
@@ -9,7 +9,7 @@ module ActiveRecord
if current_adapter?(:MysqlAdapter, :Mysql2Adapter)
assert_match 'SHARE MODE', Person.lock('LOCK IN SHARE MODE').to_sql
assert_sql(/LOCK IN SHARE MODE/) do
- Person.find(1, :lock => 'LOCK IN SHARE MODE')
+ Person.scoped(:lock => 'LOCK IN SHARE MODE').find(1)
end
end
end
diff --git a/activerecord/test/cases/deprecated_dynamic_methods_test.rb b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
new file mode 100644
index 0000000000..77a609f49a
--- /dev/null
+++ b/activerecord/test/cases/deprecated_dynamic_methods_test.rb
@@ -0,0 +1,607 @@
+# This file should be deleted when active_record_deprecated_finders is removed as
+# a dependency.
+#
+# It is kept for now as there is some fairly nuanced behaviour in the dynamic
+# finders so it is useful to keep this around to guard against regressions if
+# we need to change the code.
+
+require 'cases/helper'
+require 'models/topic'
+require 'models/reply'
+require 'models/customer'
+require 'models/post'
+require 'models/company'
+require 'models/author'
+require 'models/category'
+require 'models/comment'
+require 'models/person'
+require 'models/reader'
+
+class DeprecatedDynamicMethodsTest < ActiveRecord::TestCase
+ fixtures :topics, :customers, :companies, :accounts, :posts, :categories, :categories_posts, :authors, :people, :comments, :readers
+
+ def setup
+ @deprecation_behavior = ActiveSupport::Deprecation.behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.behavior = @deprecation_behavior
+ end
+
+ def test_find_all_by_one_attribute
+ topics = Topic.find_all_by_content("Have a nice day")
+ assert_equal 2, topics.size
+ assert topics.include?(topics(:first))
+
+ assert_equal [], Topic.find_all_by_title("The First Topic!!")
+ end
+
+ def test_find_all_by_one_attribute_which_is_a_symbol
+ topics = Topic.find_all_by_content("Have a nice day".to_sym)
+ assert_equal 2, topics.size
+ assert topics.include?(topics(:first))
+
+ assert_equal [], Topic.find_all_by_title("The First Topic!!")
+ end
+
+ def test_find_all_by_one_attribute_that_is_an_aggregate
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customers = Customer.find_all_by_balance(balance)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_two_attributes_that_are_both_aggregates
+ balance = customers(:david).balance
+ address = customers(:david).address
+ assert_kind_of Money, balance
+ assert_kind_of Address, address
+ found_customers = Customer.find_all_by_balance_and_address(balance, address)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_two_attributes_with_one_being_an_aggregate
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_one_attribute_with_options
+ topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
+ assert_equal topics(:first), topics.last
+
+ topics = Topic.find_all_by_content("Have a nice day", :order => "id")
+ assert_equal topics(:first), topics.first
+ end
+
+ def test_find_all_by_array_attribute
+ assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size
+ end
+
+ def test_find_all_by_boolean_attribute
+ topics = Topic.find_all_by_approved(false)
+ assert_equal 1, topics.size
+ assert topics.include?(topics(:first))
+
+ topics = Topic.find_all_by_approved(true)
+ assert_equal 3, topics.size
+ assert topics.include?(topics(:second))
+ end
+
+ def test_find_all_by_nil_and_not_nil_attributes
+ topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
+ assert_equal 1, topics.size
+ assert_equal "Mary", topics[0].author_name
+ end
+
+ def test_find_or_create_from_one_attribute
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name("38signals")
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name("38signals")
+ assert sig38.persisted?
+ end
+
+ def test_find_or_create_from_two_attributes
+ number_of_topics = Topic.count
+ another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
+ assert_equal number_of_topics + 1, Topic.count
+ assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
+ assert another.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_bang
+ number_of_companies = Company.count
+ assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name!("") }
+ assert_equal number_of_companies, Company.count
+ sig38 = Company.find_or_create_by_name!("38signals")
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name!("38signals")
+ assert sig38.persisted?
+ end
+
+ def test_find_or_create_from_two_attributes_bang
+ number_of_companies = Company.count
+ assert_raises(ActiveRecord::RecordInvalid) { Company.find_or_create_by_name_and_firm_id!("", 17) }
+ assert_equal number_of_companies, Company.count
+ sig38 = Company.find_or_create_by_name_and_firm_id!("38signals", 17)
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name_and_firm_id!("38signals", 17)
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ end
+
+ def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
+ number_of_customers = Customer.count
+ created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
+ assert created_customer.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_and_hash
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ end
+
+ def test_find_or_create_from_two_attributes_and_hash
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name_and_firm_id({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert sig38.persisted?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ end
+
+ def test_find_or_create_from_one_aggregate_attribute
+ number_of_customers = Customer.count
+ created_customer = Customer.find_or_create_by_balance(Money.new(123))
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
+ assert created_customer.persisted?
+ end
+
+ def test_find_or_create_from_one_aggregate_attribute_and_hash
+ number_of_customers = Customer.count
+ balance = Money.new(123)
+ name = "Elizabeth"
+ created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
+ assert created_customer.persisted?
+ assert_equal balance, created_customer.balance
+ assert_equal name, created_customer.name
+ end
+
+ def test_find_or_initialize_from_one_attribute
+ sig38 = Company.find_or_initialize_by_name("38signals")
+ assert_equal "38signals", sig38.name
+ assert !sig38.persisted?
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute
+ new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
+ assert_equal 123, new_customer.balance.amount
+ assert !new_customer.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
+ c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
+ assert_equal "Fortune 1000", c.name
+ assert_not_equal 1000, c.rating
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected
+ c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000})
+ assert_equal "Fortune 1000", c.name
+ assert_not_equal 1000, c.rating
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
+ c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
+ c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000)
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
+ c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"})
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
+ c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"})
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_should_set_protected_attributes_if_given_as_block
+ c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert !c.persisted?
+ end
+
+ def test_find_or_create_should_set_protected_attributes_if_given_as_block
+ c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_create_should_work_with_block_on_first_call
+ class << Company
+ undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name)
+ end
+ c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert c.persisted?
+ end
+
+ def test_find_or_initialize_from_two_attributes
+ another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John")
+ assert_equal "Another topic", another.title
+ assert_equal "John", another.author_name
+ assert !another.persisted?
+ end
+
+ def test_find_or_initialize_from_two_attributes_but_passing_only_one
+ assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") }
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
+ new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
+ assert_equal 123, new_customer.balance.amount
+ assert_equal "Elizabeth", new_customer.name
+ assert !new_customer.persisted?
+ end
+
+ def test_find_or_initialize_from_one_attribute_and_hash
+ sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ assert !sig38.persisted?
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute_and_hash
+ balance = Money.new(123)
+ name = "Elizabeth"
+ new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
+ assert_equal balance, new_customer.balance
+ assert_equal name, new_customer.name
+ assert !new_customer.persisted?
+ end
+
+ def test_find_last_by_one_attribute
+ assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title)
+ assert_nil Topic.find_last_by_title("A title with no matches")
+ end
+
+ def test_find_last_by_invalid_method_syntax
+ assert_raise(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") }
+ assert_raise(NoMethodError) { Topic.find_last_by_title?("The First Topic") }
+ end
+
+ def test_find_last_by_one_attribute_with_several_options
+ assert_equal accounts(:signals37), Account.order('id DESC').where('id != ?', 3).find_last_by_credit_limit(50)
+ end
+
+ def test_find_last_by_one_missing_attribute
+ assert_raise(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") }
+ end
+
+ def test_find_last_by_two_attributes
+ topic = Topic.last
+ assert_equal topic, Topic.find_last_by_title_and_author_name(topic.title, topic.author_name)
+ assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous")
+ end
+
+ def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(2).limit(2)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded
+ scope = Topic.offset(3)
+ unloaded_last = scope.last
+ loaded_last = scope.all.last
+ assert_equal loaded_last, unloaded_last
+ end
+
+ def test_find_all_by_nil_attribute
+ topics = Topic.find_all_by_last_read nil
+ assert_equal 3, topics.size
+ assert topics.collect(&:last_read).all?(&:nil?)
+ end
+
+ def test_forwarding_to_dynamic_finders
+ welcome = Post.find(1)
+ assert_equal 4, Category.find_all_by_type('SpecialCategory').size
+ assert_equal 0, welcome.categories.find_all_by_type('SpecialCategory').size
+ assert_equal 2, welcome.categories.find_all_by_type('Category').size
+ end
+
+ def test_dynamic_find_all_should_respect_association_order
+ assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.scoped(:where => "type = 'Client'").all
+ assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client')
+ end
+
+ def test_dynamic_find_all_should_respect_association_limit
+ assert_equal 1, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'").all.length
+ assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length
+ end
+
+ def test_dynamic_find_all_limit_should_override_association_limit
+ assert_equal 2, companies(:first_firm).limited_clients.scoped(:where => "type = 'Client'", :limit => 9_000).all.length
+ assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length
+ end
+
+ def test_dynamic_find_last_without_specified_order
+ assert_equal companies(:second_client), companies(:first_firm).unsorted_clients.find_last_by_type('Client')
+ end
+
+ def test_dynamic_find_or_create_from_two_attributes_using_an_association
+ author = authors(:david)
+ number_of_posts = Post.count
+ another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
+ assert_equal number_of_posts + 1, Post.count
+ assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
+ assert another.persisted?
+ end
+
+ def test_dynamic_find_all_should_respect_association_order_for_through
+ assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.scoped(:where => "comments.type = 'SpecialComment'").all
+ assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment')
+ end
+
+ def test_dynamic_find_all_should_respect_association_limit_for_through
+ assert_equal 1, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'").all.length
+ assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length
+ end
+
+ def test_dynamic_find_all_order_should_override_association_limit_for_through
+ assert_equal 4, authors(:david).limited_comments.scoped(:where => "comments.type = 'SpecialComment'", :limit => 9_000).all.length
+ assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length
+ end
+
+ def test_find_all_include_over_the_same_table_for_through
+ assert_equal 2, people(:michael).posts.scoped(:includes => :people).all.length
+ end
+
+ def test_find_or_create_by_resets_cached_counters
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.first
+
+ assert_equal [], person.readers
+ assert_nil person.readers.find_by_post_id(post.id)
+
+ person.readers.find_or_create_by_post_id(post.id)
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+ def test_find_or_initialize
+ the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client")
+ assert_equal companies(:first_firm).id, the_client.firm_id
+ assert_equal "Yet another client", the_client.name
+ assert !the_client.persisted?
+ end
+
+ def test_find_or_create_updates_size
+ number_of_clients = companies(:first_firm).clients.size
+ the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
+ assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
+ assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client")
+ assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
+ end
+
+ def test_find_or_initialize_updates_collection_size
+ number_of_clients = companies(:first_firm).clients_of_firm.size
+ companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
+ assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size
+ end
+
+ def test_find_or_initialize_returns_the_instantiated_object
+ client = companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client")
+ assert_equal client, companies(:first_firm).clients_of_firm[-1]
+ end
+
+ def test_find_or_initialize_only_instantiates_a_single_object
+ number_of_clients = Client.count
+ companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client").save!
+ companies(:first_firm).save!
+ assert_equal number_of_clients+1, Client.count
+ end
+
+ def test_find_or_create_with_hash
+ post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_with_one_attribute_followed_by_hash
+ post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody')
+ assert post.persisted?
+ end
+
+ def test_find_or_create_should_work_with_block
+ post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'}
+ assert post.persisted?
+ end
+
+ def test_forwarding_to_dynamic_finders_2
+ welcome = Post.find(1)
+ assert_equal 4, Comment.find_all_by_type('Comment').size
+ assert_equal 2, welcome.comments.find_all_by_type('Comment').size
+ end
+
+ def test_dynamic_find_all_by_attributes
+ authors = Author.scoped
+
+ davids = authors.find_all_by_name('David')
+ assert_kind_of Array, davids
+ assert_equal [authors(:david)], davids
+ end
+
+ def test_dynamic_find_or_initialize_by_attributes
+ authors = Author.scoped
+
+ lifo = authors.find_or_initialize_by_name('Lifo')
+ assert_equal "Lifo", lifo.name
+ assert !lifo.persisted?
+
+ assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David')
+ end
+
+ def test_dynamic_find_or_create_by_attributes
+ authors = Author.scoped
+
+ lifo = authors.find_or_create_by_name('Lifo')
+ assert_equal "Lifo", lifo.name
+ assert lifo.persisted?
+
+ assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David')
+ end
+
+ def test_dynamic_find_or_create_by_attributes_bang
+ authors = Author.scoped
+
+ assert_raises(ActiveRecord::RecordInvalid) { authors.find_or_create_by_name!('') }
+
+ lifo = authors.find_or_create_by_name!('Lifo')
+ assert_equal "Lifo", lifo.name
+ assert lifo.persisted?
+
+ assert_equal authors(:david), authors.find_or_create_by_name!(:name => 'David')
+ end
+
+ def test_finder_block
+ t = Topic.first
+ found = nil
+ Topic.find_by_id(t.id) { |f| found = f }
+ assert_equal t, found
+ end
+
+ def test_finder_block_nothing_found
+ bad_id = Topic.maximum(:id) + 1
+ assert_nil Topic.find_by_id(bad_id) { |f| raise }
+ end
+
+ def test_find_returns_block_value
+ t = Topic.first
+ x = Topic.find_by_id(t.id) { |f| "hi mom!" }
+ assert_equal "hi mom!", x
+ end
+
+ def test_dynamic_finder_with_invalid_params
+ assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
+ end
+
+ def test_find_by_one_attribute_with_order_option
+ assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
+ assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
+ end
+
+ def test_dynamic_find_by_attributes_should_yield_found_object
+ david = authors(:david)
+ yielded_value = nil
+ Author.find_by_name(david.name) do |author|
+ yielded_value = author
+ end
+ assert_equal david, yielded_value
+ end
+end
+
+class DynamicScopeTest < ActiveRecord::TestCase
+ fixtures :posts
+
+ def setup
+ @test_klass = Class.new(Post) do
+ def self.name; "Post"; end
+ end
+ @deprecation_behavior = ActiveSupport::Deprecation.behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.behavior = @deprecation_behavior
+ end
+
+ def test_dynamic_scope
+ assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1)
+ assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.scoped(:where => { :author_id => 1, :title => "Welcome to the weblog"}).first
+ end
+
+ def test_dynamic_scope_should_create_methods_after_hitting_method_missing
+ assert_blank @test_klass.methods.grep(/scoped_by_type/)
+ @test_klass.scoped_by_type(nil)
+ assert_present @test_klass.methods.grep(/scoped_by_type/)
+ end
+
+ def test_dynamic_scope_with_less_number_of_arguments
+ assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) }
+ end
+end
+
+class DynamicScopeMatchTest < ActiveRecord::TestCase
+ def test_scoped_by_no_match
+ assert_nil ActiveRecord::DynamicMatchers::Method.match(nil, "not_scoped_at_all")
+ end
+
+ def test_scoped_by
+ match = ActiveRecord::DynamicMatchers::Method.match(nil, "scoped_by_age_and_sex_and_location")
+ assert_not_nil match
+ assert_equal %w(age sex location), match.attribute_names
+ end
+end
diff --git a/activerecord/test/cases/deprecated_finder_test.rb b/activerecord/test/cases/deprecated_finder_test.rb
deleted file mode 100644
index 2afc91b769..0000000000
--- a/activerecord/test/cases/deprecated_finder_test.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require "cases/helper"
-require 'models/entrant'
-
-class DeprecatedFinderTest < ActiveRecord::TestCase
- fixtures :entrants
-
- def test_deprecated_find_all_was_removed
- assert_raise(NoMethodError) { Entrant.find_all }
- end
-
- def test_deprecated_find_first_was_removed
- assert_raise(NoMethodError) { Entrant.find_first }
- end
-
- def test_deprecated_find_on_conditions_was_removed
- assert_raise(NoMethodError) { Entrant.find_on_conditions }
- end
-
- def test_count
- assert_equal(0, Entrant.count(:conditions => "id > 3"))
- assert_equal(1, Entrant.count(:conditions => ["id > ?", 2]))
- assert_equal(2, Entrant.count(:conditions => ["id > ?", 1]))
- end
-
- def test_count_by_sql
- assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
- assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
- assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
- end
-end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index b1ce846218..2650040a80 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -288,7 +288,7 @@ class DirtyTest < ActiveRecord::TestCase
with_partial_updates Pirate, false do
assert_queries(2) { 2.times { pirate.save! } }
- Pirate.update_all({ :updated_on => old_updated_on }, :id => pirate.id)
+ Pirate.where(id: pirate.id).update_all(:updated_on => old_updated_on)
end
with_partial_updates Pirate, true do
@@ -306,7 +306,7 @@ class DirtyTest < ActiveRecord::TestCase
with_partial_updates Person, false do
assert_queries(2) { 2.times { person.save! } }
- Person.update_all({ :first_name => 'baz' }, :id => person.id)
+ Person.where(id: person.id).update_all(:first_name => 'baz')
end
with_partial_updates Person, true do
@@ -497,6 +497,20 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.previous_changes.key?('created_on')
end
+ if ActiveRecord::Base.connection.supports_migrations?
+ class Testings < ActiveRecord::Base; end
+ def test_field_named_field
+ ActiveRecord::Base.connection.create_table :testings do |t|
+ t.string :field
+ end
+ assert_nothing_raised do
+ Testings.new.attributes
+ end
+ ensure
+ ActiveRecord::Base.connection.drop_table :testings rescue nil
+ end
+ end
+
private
def with_partial_updates(klass, on = true)
old = klass.partial_updates?
diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb
index 0236f9b0a1..9705a11387 100644
--- a/activerecord/test/cases/dup_test.rb
+++ b/activerecord/test/cases/dup_test.rb
@@ -99,5 +99,13 @@ module ActiveRecord
assert_not_nil new_topic.created_at
end
+ def test_dup_after_initialize_callbacks
+ topic = Topic.new
+ assert Topic.after_initialize_called
+ Topic.after_initialize_called = false
+ topic.dup
+ assert Topic.after_initialize_called
+ end
+
end
end
diff --git a/activerecord/test/cases/dynamic_finder_match_test.rb b/activerecord/test/cases/dynamic_finder_match_test.rb
deleted file mode 100644
index e576870317..0000000000
--- a/activerecord/test/cases/dynamic_finder_match_test.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- class DynamicFinderMatchTest < ActiveRecord::TestCase
- def test_find_or_create_by
- match = DynamicFinderMatch.match("find_or_create_by_age_and_sex_and_location")
- assert_not_nil match
- assert !match.finder?
- assert match.instantiator?
- assert_equal :first, match.finder
- assert_equal :create, match.instantiator
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_or_initialize_by
- match = DynamicFinderMatch.match("find_or_initialize_by_age_and_sex_and_location")
- assert_not_nil match
- assert !match.finder?
- assert match.instantiator?
- assert_equal :first, match.finder
- assert_equal :new, match.instantiator
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_no_match
- assert_nil DynamicFinderMatch.match("not_a_finder")
- end
-
- def find_by_bang
- match = DynamicFinderMatch.match("find_by_age_and_sex_and_location!")
- assert_not_nil match
- assert match.finder?
- assert match.bang?
- assert_equal :first, match.finder
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_by
- match = DynamicFinderMatch.match("find_by_age_and_sex_and_location")
- assert_not_nil match
- assert match.finder?
- assert_equal :first, match.finder
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_by_with_symbol
- m = DynamicFinderMatch.match(:find_by_foo)
- assert_equal :first, m.finder
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_all_by_with_symbol
- m = DynamicFinderMatch.match(:find_all_by_foo)
- assert_equal :all, m.finder
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_all_by
- match = DynamicFinderMatch.match("find_all_by_age_and_sex_and_location")
- assert_not_nil match
- assert match.finder?
- assert_equal :all, match.finder
- assert_equal %w(age sex location), match.attribute_names
- end
-
- def test_find_last_by
- m = DynamicFinderMatch.match(:find_last_by_foo)
- assert_equal :last, m.finder
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_by!
- m = DynamicFinderMatch.match(:find_by_foo!)
- assert_equal :first, m.finder
- assert m.bang?, 'should be banging'
- assert_equal %w{ foo }, m.attribute_names
- end
-
- def test_find_or_create
- m = DynamicFinderMatch.match(:find_or_create_by_foo)
- assert_equal :first, m.finder
- assert_equal %w{ foo }, m.attribute_names
- assert_equal :create, m.instantiator
- end
-
- def test_find_or_initialize
- m = DynamicFinderMatch.match(:find_or_initialize_by_foo)
- assert_equal :first, m.finder
- assert_equal %w{ foo }, m.attribute_names
- assert_equal :new, m.instantiator
- end
-
- def test_garbage
- assert !DynamicFinderMatch.match(:fooo), 'should be false'
- assert !DynamicFinderMatch.match(:find_by), 'should be false'
- end
- end
-end
diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb
new file mode 100644
index 0000000000..e118add44c
--- /dev/null
+++ b/activerecord/test/cases/explain_subscriber_test.rb
@@ -0,0 +1,48 @@
+require 'cases/helper'
+
+if ActiveRecord::Base.connection.supports_explain?
+ class ExplainSubscriberTest < ActiveRecord::TestCase
+ SUBSCRIBER = ActiveRecord::ExplainSubscriber.new
+
+ def test_collects_nothing_if_available_queries_for_explain_is_nil
+ with_queries(nil) do
+ SUBSCRIBER.call
+ assert_nil Thread.current[:available_queries_for_explain]
+ end
+ end
+
+ def test_collects_nothing_if_the_payload_has_an_exception
+ with_queries([]) do |queries|
+ SUBSCRIBER.call(:exception => Exception.new)
+ assert queries.empty?
+ end
+ end
+
+ def test_collects_nothing_for_ignored_payloads
+ with_queries([]) do |queries|
+ ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip|
+ SUBSCRIBER.call(:name => ip)
+ end
+ assert queries.empty?
+ end
+ end
+
+ def test_collects_pairs_of_queries_and_binds
+ sql = 'select 1 from users'
+ binds = [1, 2]
+ with_queries([]) do |queries|
+ SUBSCRIBER.call(:name => 'SQL', :sql => sql, :binds => binds)
+ assert_equal 1, queries.size
+ assert_equal sql, queries[0][0]
+ assert_equal binds, queries[0][1]
+ end
+ end
+
+ def with_queries(queries)
+ Thread.current[:available_queries_for_explain] = queries
+ yield queries
+ ensure
+ Thread.current[:available_queries_for_explain] = nil
+ end
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb
new file mode 100644
index 0000000000..cb7781f8e7
--- /dev/null
+++ b/activerecord/test/cases/explain_test.rb
@@ -0,0 +1,117 @@
+require 'cases/helper'
+require 'models/car'
+require 'active_support/core_ext/string/strip'
+
+if ActiveRecord::Base.connection.supports_explain?
+ class ExplainTest < ActiveRecord::TestCase
+ fixtures :cars
+
+ def base
+ ActiveRecord::Base
+ end
+
+ def connection
+ base.connection
+ end
+
+ def test_logging_query_plan_with_logger
+ base.logger.expects(:warn).with do |message|
+ message.starts_with?('EXPLAIN for:')
+ end
+
+ with_threshold(0) do
+ Car.where(:name => 'honda').all
+ end
+ end
+
+ def test_logging_query_plan_without_logger
+ original = base.logger
+ base.logger = nil
+
+ class << base.logger
+ def warn; raise "Should not be called" end
+ end
+
+ with_threshold(0) do
+ car = Car.where(:name => 'honda').first
+ assert_equal 'honda', car.name
+ end
+ ensure
+ base.logger = original
+ end
+
+ def test_collect_queries_for_explain
+ base.auto_explain_threshold_in_seconds = nil
+ queries = Thread.current[:available_queries_for_explain] = []
+
+ with_threshold(0) do
+ Car.where(:name => 'honda').all
+ end
+
+ sql, binds = queries[0]
+ assert_match "SELECT", sql
+ assert_match "honda", sql
+ assert_equal [], binds
+ ensure
+ Thread.current[:available_queries_for_explain] = nil
+ end
+
+ def test_collecting_queries_for_explain
+ result, queries = ActiveRecord::Base.collecting_queries_for_explain do
+ Car.where(:name => 'honda').all
+ end
+
+ sql, binds = queries[0]
+ assert_match "SELECT", sql
+ assert_match "honda", sql
+ assert_equal [], binds
+ assert_equal [cars(:honda)], result
+ end
+
+ def test_exec_explain_with_no_binds
+ sqls = %w(foo bar)
+ binds = [[], []]
+ queries = sqls.zip(binds)
+
+ connection.stubs(:explain).returns('query plan foo', 'query plan bar')
+ expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n")
+ assert_equal expected, base.exec_explain(queries)
+ end
+
+ def test_exec_explain_with_binds
+ cols = [Object.new, Object.new]
+ cols[0].expects(:name).returns('wadus')
+ cols[1].expects(:name).returns('chaflan')
+
+ sqls = %w(foo bar)
+ binds = [[[cols[0], 1]], [[cols[1], 2]]]
+ queries = sqls.zip(binds)
+
+ connection.stubs(:explain).returns("query plan foo\n", "query plan bar\n")
+ expected = <<-SQL.strip_heredoc
+ EXPLAIN for: #{sqls[0]} [["wadus", 1]]
+ query plan foo
+
+ EXPLAIN for: #{sqls[1]} [["chaflan", 2]]
+ query plan bar
+ SQL
+ assert_equal expected, base.exec_explain(queries)
+ end
+
+ def test_silence_auto_explain
+ base.expects(:collecting_sqls_for_explain).never
+ base.logger.expects(:warn).never
+ base.silence_auto_explain do
+ with_threshold(0) { Car.all }
+ end
+ end
+
+ def with_threshold(threshold)
+ current_threshold = base.auto_explain_threshold_in_seconds
+ base.auto_explain_threshold_in_seconds = threshold
+ yield
+ ensure
+ base.auto_explain_threshold_in_seconds = current_threshold
+ end
+ end
+end
diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb
index 235805a67c..810c1500cc 100644
--- a/activerecord/test/cases/finder_respond_to_test.rb
+++ b/activerecord/test/cases/finder_respond_to_test.rb
@@ -56,6 +56,16 @@ class FinderRespondToTest < ActiveRecord::TestCase
assert_respond_to Topic, :find_or_create_by_title_and_author_name
end
+ def test_should_respond_to_find_or_create_from_one_attribute_bang
+ ensure_topic_method_is_not_cached(:find_or_create_by_title!)
+ assert_respond_to Topic, :find_or_create_by_title!
+ end
+
+ def test_should_respond_to_find_or_create_from_two_attributes_bang
+ ensure_topic_method_is_not_cached(:find_or_create_by_title_and_author_name!)
+ assert_respond_to Topic, :find_or_create_by_title_and_author_name!
+ end
+
def test_should_not_respond_to_find_by_one_missing_attribute
assert !Topic.respond_to?(:find_by_undertitle)
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 3088ab012f..f7ecab28ce 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -32,6 +32,7 @@ class FinderTest < ActiveRecord::TestCase
assert Topic.exists?(:author_name => "Mary", :approved => true)
assert Topic.exists?(["parent_id = ?", 1])
assert !Topic.exists?(45)
+ assert !Topic.exists?(Topic.new)
begin
assert !Topic.exists?("foo")
@@ -57,6 +58,11 @@ class FinderTest < ActiveRecord::TestCase
assert Topic.first.replies.exists?
end
+ # ensures +exists?+ runs valid SQL by excluding order value
+ def test_exists_with_order
+ assert Topic.order(:id).uniq.exists?
+ end
+
def test_does_not_exist_with_empty_table_and_no_args_given
Topic.delete_all
assert !Topic.exists?
@@ -77,12 +83,6 @@ class FinderTest < ActiveRecord::TestCase
Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
end
- def test_exists_with_scoped_include
- Developer.send(:with_scope, :find => { :include => :projects, :order => "projects.name" }) do
- assert Developer.exists?
- end
- end
-
def test_exists_does_not_instantiate_records
Developer.expects(:instantiate).never
Developer.exists?
@@ -99,14 +99,14 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_by_ids_with_limit_and_offset
- assert_equal 2, Entrant.find([1,3,2], :limit => 2).size
- assert_equal 1, Entrant.find([1,3,2], :limit => 3, :offset => 2).size
+ assert_equal 2, Entrant.scoped(:limit => 2).find([1,3,2]).size
+ assert_equal 1, Entrant.scoped(:limit => 3, :offset => 2).find([1,3,2]).size
# Also test an edge case: If you have 11 results, and you set a
# limit of 3 and offset of 9, then you should find that there
# will be only 2 results, regardless of the limit.
- devs = Developer.find :all
- last_devs = Developer.find devs.map(&:id), :limit => 3, :offset => 9
+ devs = Developer.all
+ last_devs = Developer.scoped(:limit => 3, :offset => 9).find devs.map(&:id)
assert_equal 2, last_devs.size
end
@@ -114,58 +114,12 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [], Topic.find([])
end
- def test_find_by_ids_missing_one
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
- end
-
- def test_find_all_with_limit
- assert_equal(2, Entrant.find(:all, :limit => 2).size)
- assert_equal(0, Entrant.find(:all, :limit => 0).size)
- end
-
- def test_find_all_with_prepared_limit_and_offset
- entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 1)
-
- assert_equal(2, entrants.size)
- assert_equal(entrants(:second).name, entrants.first.name)
-
- assert_equal 3, Entrant.count
- entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2)
- assert_equal(1, entrants.size)
- assert_equal(entrants(:third).name, entrants.first.name)
- end
-
- def test_find_all_with_limit_and_offset_and_multiple_order_clauses
- first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0
- second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3
- third_three_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6
- last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 9
-
- assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] }
- assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] }
- assert_equal [[2,7],[2,9],[2,11]], third_three_posts.map { |p| [p.author_id, p.id] }
- assert_equal [[3,8],[3,10]], last_posts.map { |p| [p.author_id, p.id] }
+ def test_find_doesnt_have_implicit_ordering
+ assert_sql(/^((?!ORDER).)*$/) { Topic.find(1) }
end
-
- def test_find_with_group
- developers = Developer.find(:all, :group => "salary", :select => "salary")
- assert_equal 4, developers.size
- assert_equal 4, developers.map(&:salary).uniq.size
- end
-
- def test_find_with_group_and_having
- developers = Developer.find(:all, :group => "salary", :having => "sum(salary) > 10000", :select => "salary")
- assert_equal 3, developers.size
- assert_equal 3, developers.map(&:salary).uniq.size
- assert developers.all? { |developer| developer.salary > 10000 }
- end
-
- def test_find_with_group_and_sanitized_having
- developers = Developer.find(:all, :group => "salary", :having => ["sum(salary) > ?", 10000], :select => "salary")
- assert_equal 3, developers.size
- assert_equal 3, developers.map(&:salary).uniq.size
- assert developers.all? { |developer| developer.salary > 10000 }
+ def test_find_by_ids_missing_one
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
end
def test_find_with_group_and_sanitized_having_method
@@ -194,14 +148,24 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [Account], accounts.collect(&:class).uniq
end
- def test_find_first
- first = Topic.find(:first, :conditions => "title = 'The First Topic'")
- assert_equal(topics(:first).title, first.title)
+ def test_take
+ assert_equal topics(:first), Topic.take
end
- def test_find_first_failing
- first = Topic.find(:first, :conditions => "title = 'The First Topic!'")
- assert_nil(first)
+ def test_take_failing
+ assert_nil Topic.where("title = 'This title does not exist'").take
+ end
+
+ def test_take_bang_present
+ assert_nothing_raised do
+ assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").take!
+ end
+ end
+
+ def test_take_bang_missing
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.where("title = 'This title does not exist'").take!
+ end
end
def test_first
@@ -224,6 +188,12 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_first_have_primary_key_order_by_default
+ expected = topics(:first)
+ expected.touch # PostgreSQL changes the default order if no order clause is used
+ assert_equal expected, Topic.first
+ end
+
def test_model_class_responds_to_first_bang
assert Topic.first!
Topic.delete_all
@@ -252,7 +222,8 @@ class FinderTest < ActiveRecord::TestCase
end
end
- def test_first_and_last_with_integer_should_use_sql_limit
+ def test_take_and_first_and_last_with_integer_should_use_sql_limit
+ assert_sql(/LIMIT 3|ROWNUM <= 3/) { Topic.take(3).entries }
assert_sql(/LIMIT 2|ROWNUM <= 2/) { Topic.first(2).entries }
assert_sql(/LIMIT 5|ROWNUM <= 5/) { Topic.last(5).entries }
end
@@ -273,7 +244,8 @@ class FinderTest < ActiveRecord::TestCase
assert_no_match(/LIMIT/, query.first)
end
- def test_first_and_last_with_integer_should_return_an_array
+ def test_take_and_first_and_last_with_integer_should_return_an_array
+ assert_kind_of Array, Topic.take(5)
assert_kind_of Array, Topic.first(5)
assert_kind_of Array, Topic.last(5)
end
@@ -287,7 +259,7 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_only_some_columns
- topic = Topic.find(1, :select => "author_name")
+ topic = Topic.scoped(:select => "author_name").find(1)
assert_raise(ActiveModel::MissingAttributeError) {topic.title}
assert_nil topic.read_attribute("title")
assert_equal "David", topic.author_name
@@ -297,36 +269,24 @@ class FinderTest < ActiveRecord::TestCase
assert_respond_to topic, "author_name"
end
- def test_find_on_blank_conditions
- [nil, " ", [], {}].each do |blank|
- assert_nothing_raised { Topic.find(:first, :conditions => blank) }
- end
- end
-
- def test_find_on_blank_bind_conditions
- [ [""], ["",{}] ].each do |blank|
- assert_nothing_raised { Topic.find(:first, :conditions => blank) }
- end
- end
-
def test_find_on_array_conditions
- assert Topic.find(1, :conditions => ["approved = ?", false])
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) }
+ assert Topic.scoped(:where => ["approved = ?", false]).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => ["approved = ?", true]).find(1) }
end
def test_find_on_hash_conditions
- assert Topic.find(1, :conditions => { :approved => false })
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) }
+ assert Topic.scoped(:where => { :approved => false }).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :approved => true }).find(1) }
end
def test_find_on_hash_conditions_with_explicit_table_name
- assert Topic.find(1, :conditions => { 'topics.approved' => false })
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
+ assert Topic.scoped(:where => { 'topics.approved' => false }).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { 'topics.approved' => true }).find(1) }
end
def test_find_on_hash_conditions_with_hashed_table_name
- assert Topic.find(1, :conditions => {:topics => { :approved => false }})
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => {:topics => { :approved => true }}) }
+ assert Topic.scoped(:where => {:topics => { :approved => false }}).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => {:topics => { :approved => true }}).find(1) }
end
def test_find_with_hash_conditions_on_joined_table
@@ -336,85 +296,89 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_hash_conditions_on_joined_table_and_with_range
- firms = DependentFirm.all :joins => :account, :conditions => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}
+ firms = DependentFirm.scoped :joins => :account, :where => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}
assert_equal 1, firms.size
assert_equal companies(:rails_core), firms.first
end
def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
david = customers(:david)
- assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address })
+ assert Customer.scoped(:where => { 'customers.name' => david.name, :address => david.address }).find(david.id)
assert_raise(ActiveRecord::RecordNotFound) {
- Customer.find(david.id, :conditions => { 'customers.name' => david.name + "1", :address => david.address })
+ Customer.scoped(:where => { 'customers.name' => david.name + "1", :address => david.address }).find(david.id)
}
end
def test_find_on_association_proxy_conditions
- assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort
+ assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10, 12], Comment.where(post_id: authors(:david).posts).map(&:id).sort
end
def test_find_on_hash_conditions_with_range
- assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1..2 }).map(&:id).sort
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) }
+ assert_equal [1,2], Topic.scoped(:where => { :id => 1..2 }).all.map(&:id).sort
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2..3 }).find(1) }
end
def test_find_on_hash_conditions_with_end_exclusive_range
- assert_equal [1,2,3], Topic.find(:all, :conditions => { :id => 1..3 }).map(&:id).sort
- assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1...3 }).map(&:id).sort
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(3, :conditions => { :id => 2...3 }) }
+ assert_equal [1,2,3], Topic.scoped(:where => { :id => 1..3 }).all.map(&:id).sort
+ assert_equal [1,2], Topic.scoped(:where => { :id => 1...3 }).all.map(&:id).sort
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :id => 2...3 }).find(3) }
end
def test_find_on_hash_conditions_with_multiple_ranges
- assert_equal [1,2,3], Comment.find(:all, :conditions => { :id => 1..3, :post_id => 1..2 }).map(&:id).sort
- assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort
+ assert_equal [1,2,3], Comment.scoped(:where => { :id => 1..3, :post_id => 1..2 }).all.map(&:id).sort
+ assert_equal [1], Comment.scoped(:where => { :id => 1..1, :post_id => 1..10 }).all.map(&:id).sort
+ end
+
+ def test_find_on_hash_conditions_with_array_of_integers_and_ranges
+ assert_equal [1,2,3,5,6,7,8,9], Comment.scoped(:where => {:id => [1..2, 3, 5, 6..8, 9]}).all.map(&:id).sort
end
def test_find_on_multiple_hash_conditions
- assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false })
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) }
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
+ assert Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false }).find(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) }
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }).find(1) }
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.scoped(:where => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }).find(1) }
end
def test_condition_interpolation
- assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
- assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
- assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
- assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
+ assert_kind_of Firm, Company.where("name = '%s'", "37signals").first
+ assert_nil Company.scoped(:where => ["name = '%s'", "37signals!"]).first
+ assert_nil Company.scoped(:where => ["name = '%s'", "37signals!' OR 1=1"]).first
+ assert_kind_of Time, Topic.scoped(:where => ["id = %d", 1]).first.written_on
end
def test_condition_array_interpolation
- assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
- assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
- assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
- assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
+ assert_kind_of Firm, Company.scoped(:where => ["name = '%s'", "37signals"]).first
+ assert_nil Company.scoped(:where => ["name = '%s'", "37signals!"]).first
+ assert_nil Company.scoped(:where => ["name = '%s'", "37signals!' OR 1=1"]).first
+ assert_kind_of Time, Topic.scoped(:where => ["id = %d", 1]).first.written_on
end
def test_condition_hash_interpolation
- assert_kind_of Firm, Company.find(:first, :conditions => { :name => "37signals"})
- assert_nil Company.find(:first, :conditions => { :name => "37signals!"})
- assert_kind_of Time, Topic.find(:first, :conditions => {:id => 1}).written_on
+ assert_kind_of Firm, Company.scoped(:where => { :name => "37signals"}).first
+ assert_nil Company.scoped(:where => { :name => "37signals!"}).first
+ assert_kind_of Time, Topic.scoped(:where => {:id => 1}).first.written_on
end
def test_hash_condition_find_malformed
assert_raise(ActiveRecord::StatementInvalid) {
- Company.find(:first, :conditions => { :id => 2, :dhh => true })
+ Company.scoped(:where => { :id => 2, :dhh => true }).first
}
end
def test_hash_condition_find_with_escaped_characters
Company.create("name" => "Ain't noth'n like' \#stuff")
- assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff" })
+ assert Company.scoped(:where => { :name => "Ain't noth'n like' \#stuff" }).first
end
def test_hash_condition_find_with_array
- p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
- assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2] }, :order => 'id asc')
- assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2.id] }, :order => 'id asc')
+ p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').all
+ assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2] }, :order => 'id asc').all
+ assert_equal [p1, p2], Post.scoped(:where => { :id => [p1, p2.id] }, :order => 'id asc').all
end
def test_hash_condition_find_with_nil
- topic = Topic.find(:first, :conditions => { :last_read => nil } )
+ topic = Topic.scoped(:where => { :last_read => nil } ).first
assert_not_nil topic
assert_nil topic.last_read
end
@@ -422,42 +386,42 @@ class FinderTest < ActiveRecord::TestCase
def test_hash_condition_find_with_aggregate_having_one_mapping
balance = customers(:david).balance
assert_kind_of Money, balance
- found_customer = Customer.find(:first, :conditions => {:balance => balance})
+ found_customer = Customer.scoped(:where => {:balance => balance}).first
assert_equal customers(:david), found_customer
end
def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate
gps_location = customers(:david).gps_location
assert_kind_of GpsLocation, gps_location
- found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location})
+ found_customer = Customer.scoped(:where => {:gps_location => gps_location}).first
assert_equal customers(:david), found_customer
end
def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value
balance = customers(:david).balance
assert_kind_of Money, balance
- found_customer = Customer.find(:first, :conditions => {:balance => balance.amount})
+ found_customer = Customer.scoped(:where => {:balance => balance.amount}).first
assert_equal customers(:david), found_customer
end
def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value
gps_location = customers(:david).gps_location
assert_kind_of GpsLocation, gps_location
- found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location.gps_location})
+ found_customer = Customer.scoped(:where => {:gps_location => gps_location.gps_location}).first
assert_equal customers(:david), found_customer
end
def test_hash_condition_find_with_aggregate_having_three_mappings
address = customers(:david).address
assert_kind_of Address, address
- found_customer = Customer.find(:first, :conditions => {:address => address})
+ found_customer = Customer.scoped(:where => {:address => address}).first
assert_equal customers(:david), found_customer
end
def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not
address = customers(:david).address
assert_kind_of Address, address
- found_customer = Customer.find(:first, :conditions => {:address => address, :name => customers(:david).name})
+ found_customer = Customer.scoped(:where => {:address => address, :name => customers(:david).name}).first
assert_equal customers(:david), found_customer
end
@@ -465,7 +429,7 @@ class FinderTest < ActiveRecord::TestCase
with_env_tz 'America/New_York' do
with_active_record_default_timezone :local do
topic = Topic.first
- assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getutc])
+ assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getutc]).first
end
end
end
@@ -474,7 +438,7 @@ class FinderTest < ActiveRecord::TestCase
with_env_tz 'America/New_York' do
with_active_record_default_timezone :local do
topic = Topic.first
- assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getutc})
+ assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getutc}).first
end
end
end
@@ -483,7 +447,7 @@ class FinderTest < ActiveRecord::TestCase
with_env_tz 'America/New_York' do
with_active_record_default_timezone :utc do
topic = Topic.first
- assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getlocal])
+ assert_equal topic, Topic.scoped(:where => ['written_on = ?', topic.written_on.getlocal]).first
end
end
end
@@ -492,32 +456,32 @@ class FinderTest < ActiveRecord::TestCase
with_env_tz 'America/New_York' do
with_active_record_default_timezone :utc do
topic = Topic.first
- assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getlocal})
+ assert_equal topic, Topic.scoped(:where => {:written_on => topic.written_on.getlocal}).first
end
end
end
def test_bind_variables
- assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"])
- assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"])
- assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!' OR 1=1"])
- assert_kind_of Time, Topic.find(:first, :conditions => ["id = ?", 1]).written_on
+ assert_kind_of Firm, Company.scoped(:where => ["name = ?", "37signals"]).first
+ assert_nil Company.scoped(:where => ["name = ?", "37signals!"]).first
+ assert_nil Company.scoped(:where => ["name = ?", "37signals!' OR 1=1"]).first
+ assert_kind_of Time, Topic.scoped(:where => ["id = ?", 1]).first.written_on
assert_raise(ActiveRecord::PreparedStatementInvalid) {
- Company.find(:first, :conditions => ["id=? AND name = ?", 2])
+ Company.scoped(:where => ["id=? AND name = ?", 2]).first
}
assert_raise(ActiveRecord::PreparedStatementInvalid) {
- Company.find(:first, :conditions => ["id=?", 2, 3, 4])
+ Company.scoped(:where => ["id=?", 2, 3, 4]).first
}
end
def test_bind_variables_with_quotes
Company.create("name" => "37signals' go'es agains")
- assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"])
+ assert Company.scoped(:where => ["name = ?", "37signals' go'es agains"]).first
end
def test_named_bind_variables_with_quotes
Company.create("name" => "37signals' go'es agains")
- assert Company.find(:first, :conditions => ["name = :name", {:name => "37signals' go'es agains"}])
+ assert Company.scoped(:where => ["name = :name", {:name => "37signals' go'es agains"}]).first
end
def test_bind_arity
@@ -535,10 +499,10 @@ class FinderTest < ActiveRecord::TestCase
assert_nothing_raised { bind("'+00:00'", :foo => "bar") }
- assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }])
- assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }])
- assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }])
- assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on
+ assert_kind_of Firm, Company.scoped(:where => ["name = :name", { :name => "37signals" }]).first
+ assert_nil Company.scoped(:where => ["name = :name", { :name => "37signals!" }]).first
+ assert_nil Company.scoped(:where => ["name = :name", { :name => "37signals!' OR 1=1" }]).first
+ assert_kind_of Time, Topic.scoped(:where => ["id = :id", { :id => 1 }]).first.written_on
end
class SimpleEnumerable
@@ -609,12 +573,6 @@ class FinderTest < ActiveRecord::TestCase
assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
end
- def test_count
- assert_equal(0, Entrant.count(:conditions => "id > 3"))
- assert_equal(1, Entrant.count(:conditions => ["id > ?", 2]))
- assert_equal(2, Entrant.count(:conditions => ["id > ?", 1]))
- end
-
def test_count_by_sql
assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
@@ -631,13 +589,8 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ActiveRecord::RecordNotFound) { Topic.find_by_title!("The First Topic!") }
end
- def test_find_by_one_attribute_with_order_option
- assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
- assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
- end
-
def test_find_by_one_attribute_with_conditions
- assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
+ assert_equal accounts(:rails_core_account), Account.where('firm_id = ?', 6).find_by_credit_limit(50)
end
def test_find_by_one_attribute_that_is_an_aggregate
@@ -677,12 +630,12 @@ class FinderTest < ActiveRecord::TestCase
def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
# ensure this test can run independently of order
class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' }
- a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
- assert_equal a, Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) # find_by_credit_limit has been cached
+ a = Account.where('firm_id = ?', 6).find_by_credit_limit(50)
+ assert_equal a, Account.where('firm_id = ?', 6).find_by_credit_limit(50) # find_by_credit_limit has been cached
end
def test_find_by_one_attribute_with_several_options
- assert_equal accounts(:unknown), Account.find_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
+ assert_equal accounts(:unknown), Account.order('id DESC').where('id != ?', 3).find_by_credit_limit(50)
end
def test_find_by_one_missing_attribute
@@ -705,334 +658,26 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Topic.find_by_title_and_author_name("The First Topic") }
end
- def test_find_last_by_one_attribute
- assert_equal Topic.last, Topic.find_last_by_title(Topic.last.title)
- assert_nil Topic.find_last_by_title("A title with no matches")
- end
-
- def test_find_last_by_invalid_method_syntax
- assert_raise(NoMethodError) { Topic.fail_to_find_last_by_title("The First Topic") }
- assert_raise(NoMethodError) { Topic.find_last_by_title?("The First Topic") }
- end
-
- def test_find_last_by_one_attribute_with_several_options
- assert_equal accounts(:signals37), Account.find_last_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
- end
-
- def test_find_last_by_one_missing_attribute
- assert_raise(NoMethodError) { Topic.find_last_by_undertitle("The Last Topic!") }
- end
-
- def test_find_last_by_two_attributes
- topic = Topic.last
- assert_equal topic, Topic.find_last_by_title_and_author_name(topic.title, topic.author_name)
- assert_nil Topic.find_last_by_title_and_author_name(topic.title, "Anonymous")
- end
-
- def test_find_last_with_limit_gives_same_result_when_loaded_and_unloaded
- scope = Topic.limit(2)
- unloaded_last = scope.last
- loaded_last = scope.all.last
- assert_equal loaded_last, unloaded_last
- end
-
- def test_find_last_with_limit_and_offset_gives_same_result_when_loaded_and_unloaded
- scope = Topic.offset(2).limit(2)
- unloaded_last = scope.last
- loaded_last = scope.all.last
- assert_equal loaded_last, unloaded_last
- end
-
- def test_find_last_with_offset_gives_same_result_when_loaded_and_unloaded
- scope = Topic.offset(3)
- unloaded_last = scope.last
- loaded_last = scope.all.last
- assert_equal loaded_last, unloaded_last
- end
-
- def test_find_all_by_one_attribute
- topics = Topic.find_all_by_content("Have a nice day")
- assert_equal 2, topics.size
- assert topics.include?(topics(:first))
-
- assert_equal [], Topic.find_all_by_title("The First Topic!!")
- end
-
- def test_find_all_by_one_attribute_which_is_a_symbol
- topics = Topic.find_all_by_content("Have a nice day".to_sym)
- assert_equal 2, topics.size
- assert topics.include?(topics(:first))
-
- assert_equal [], Topic.find_all_by_title("The First Topic!!")
- end
-
- def test_find_all_by_one_attribute_that_is_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customers = Customer.find_all_by_balance(balance)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_two_attributes_that_are_both_aggregates
- balance = customers(:david).balance
- address = customers(:david).address
- assert_kind_of Money, balance
- assert_kind_of Address, address
- found_customers = Customer.find_all_by_balance_and_address(balance, address)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_two_attributes_with_one_being_an_aggregate
- balance = customers(:david).balance
- assert_kind_of Money, balance
- found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
- assert_equal 1, found_customers.size
- assert_equal customers(:david), found_customers.first
- end
-
- def test_find_all_by_one_attribute_with_options
- topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
- assert_equal topics(:first), topics.last
-
- topics = Topic.find_all_by_content("Have a nice day", :order => "id")
- assert_equal topics(:first), topics.first
- end
-
- def test_find_all_by_array_attribute
- assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size
- end
-
- def test_find_all_by_boolean_attribute
- topics = Topic.find_all_by_approved(false)
- assert_equal 1, topics.size
- assert topics.include?(topics(:first))
-
- topics = Topic.find_all_by_approved(true)
- assert_equal 3, topics.size
- assert topics.include?(topics(:second))
- end
-
def test_find_by_nil_attribute
topic = Topic.find_by_last_read nil
assert_not_nil topic
assert_nil topic.last_read
end
- def test_find_all_by_nil_attribute
- topics = Topic.find_all_by_last_read nil
- assert_equal 3, topics.size
- assert topics.collect(&:last_read).all?(&:nil?)
- end
-
def test_find_by_nil_and_not_nil_attributes
topic = Topic.find_by_last_read_and_author_name nil, "Mary"
assert_equal "Mary", topic.author_name
end
- def test_find_all_by_nil_and_not_nil_attributes
- topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
- assert_equal 1, topics.size
- assert_equal "Mary", topics[0].author_name
- end
-
- def test_find_or_create_from_one_attribute
- number_of_companies = Company.count
- sig38 = Company.find_or_create_by_name("38signals")
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name("38signals")
- assert sig38.persisted?
- end
-
- def test_find_or_create_from_two_attributes
- number_of_topics = Topic.count
- another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
- assert_equal number_of_topics + 1, Topic.count
- assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
- assert another.persisted?
- end
-
- def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
- number_of_customers = Customer.count
- created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
- assert created_customer.persisted?
- end
-
- def test_find_or_create_from_one_attribute_and_hash
- number_of_companies = Company.count
- sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert_equal number_of_companies + 1, Company.count
- assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert sig38.persisted?
- assert_equal "38signals", sig38.name
- assert_equal 17, sig38.firm_id
- assert_equal 23, sig38.client_of
- end
-
- def test_find_or_create_from_one_aggregate_attribute
- number_of_customers = Customer.count
- created_customer = Customer.find_or_create_by_balance(Money.new(123))
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
- assert created_customer.persisted?
- end
-
- def test_find_or_create_from_one_aggregate_attribute_and_hash
- number_of_customers = Customer.count
- balance = Money.new(123)
- name = "Elizabeth"
- created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
- assert_equal number_of_customers + 1, Customer.count
- assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
- assert created_customer.persisted?
- assert_equal balance, created_customer.balance
- assert_equal name, created_customer.name
- end
-
- def test_find_or_initialize_from_one_attribute
- sig38 = Company.find_or_initialize_by_name("38signals")
- assert_equal "38signals", sig38.name
- assert !sig38.persisted?
- end
-
- def test_find_or_initialize_from_one_aggregate_attribute
- new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
- assert_equal 123, new_customer.balance.amount
- assert !new_customer.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
- c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
- assert_equal "Fortune 1000", c.name
- assert_not_equal 1000, c.rating
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected
- c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000})
- assert_equal "Fortune 1000", c.name
- assert_not_equal 1000, c.rating
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
- c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
- c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000)
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
- c = Company.find_or_initialize_by_rating(1000, {:name => "Fortune 1000"})
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash
- c = Company.find_or_create_by_rating(1000, {:name => "Fortune 1000"})
- assert_equal "Fortune 1000", c.name
- assert_equal 1000, c.rating
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_should_set_protected_attributes_if_given_as_block
- c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
- assert_equal "Fortune 1000", c.name
- assert_equal 1000.to_f, c.rating.to_f
- assert c.valid?
- assert !c.persisted?
- end
-
- def test_find_or_create_should_set_protected_attributes_if_given_as_block
- c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
- assert_equal "Fortune 1000", c.name
- assert_equal 1000.to_f, c.rating.to_f
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_create_should_work_with_block_on_first_call
- class << Company
- undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name)
- end
- c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
- assert_equal "Fortune 1000", c.name
- assert_equal 1000.to_f, c.rating.to_f
- assert c.valid?
- assert c.persisted?
- end
-
- def test_find_or_initialize_from_two_attributes
- another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John")
- assert_equal "Another topic", another.title
- assert_equal "John", another.author_name
- assert !another.persisted?
- end
-
- def test_find_or_initialize_from_two_attributes_but_passing_only_one
- assert_raise(ArgumentError) { Topic.find_or_initialize_by_title_and_author_name("Another topic") }
- end
-
- def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
- new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
- assert_equal 123, new_customer.balance.amount
- assert_equal "Elizabeth", new_customer.name
- assert !new_customer.persisted?
- end
-
- def test_find_or_initialize_from_one_attribute_and_hash
- sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
- assert_equal "38signals", sig38.name
- assert_equal 17, sig38.firm_id
- assert_equal 23, sig38.client_of
- assert !sig38.persisted?
- end
-
- def test_find_or_initialize_from_one_aggregate_attribute_and_hash
- balance = Money.new(123)
- name = "Elizabeth"
- new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
- assert_equal balance, new_customer.balance
- assert_equal name, new_customer.name
- assert !new_customer.persisted?
- end
-
def test_find_with_bad_sql
assert_raise(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
end
- def test_find_with_invalid_params
- assert_raise(ArgumentError) { Topic.find :first, :join => "It should be `joins'" }
- assert_raise(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" }
- end
-
- def test_dynamic_finder_with_invalid_params
- assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
- end
-
def test_find_all_with_join
- developers_on_project_one = Developer.find(
- :all,
+ developers_on_project_one = Developer.scoped(
:joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
- :conditions => 'project_id=1'
- )
+ :where => 'project_id=1'
+ ).all
assert_equal 3, developers_on_project_one.length
developer_names = developers_on_project_one.map { |d| d.name }
assert developer_names.include?('David')
@@ -1040,17 +685,15 @@ class FinderTest < ActiveRecord::TestCase
end
def test_joins_dont_clobber_id
- first = Firm.find(
- :first,
+ first = Firm.scoped(
:joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id',
- :conditions => 'companies.id = 1'
- )
+ :where => 'companies.id = 1'
+ ).first
assert_equal 1, first.id
end
def test_joins_with_string_array
- person_with_reader_and_post = Post.find(
- :all,
+ person_with_reader_and_post = Post.scoped(
:joins => [
"INNER JOIN categorizations ON categorizations.post_id = posts.id",
"INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'"
@@ -1061,15 +704,14 @@ class FinderTest < ActiveRecord::TestCase
def test_find_by_id_with_conditions_with_or
assert_nothing_raised do
- Post.find([1,2,3],
- :conditions => "posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'")
+ Post.where("posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'").find([1,2,3])
end
end
# http://dev.rubyonrails.org/ticket/6778
def test_find_ignores_previously_inserted_record
Post.create!(:title => 'test', :body => 'it out')
- assert_equal [], Post.find_all_by_id(nil)
+ assert_equal [], Post.where(id: nil)
end
def test_find_by_empty_ids
@@ -1077,13 +719,13 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_by_empty_in_condition
- assert_equal [], Post.find(:all, :conditions => ['id in (?)', []])
+ assert_equal [], Post.where('id in (?)', [])
end
def test_find_by_records
- p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
- assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2]], :order => 'id asc')
- assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2.id]], :order => 'id asc')
+ p1, p2 = Post.scoped(:limit => 2, :order => 'id asc').all
+ assert_equal [p1, p2], Post.scoped(:where => ['id in (?)', [p1, p2]], :order => 'id asc')
+ assert_equal [p1, p2], Post.scoped(:where => ['id in (?)', [p1, p2.id]], :order => 'id asc')
end
def test_select_value
@@ -1110,16 +752,15 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
- assert_equal 2, Post.find(:all, :include => { :authors => :author_address }, :order => ' author_addresses.id DESC ', :limit => 2).size
+ assert_equal 2, Post.scoped(:includes => { :authors => :author_address }, :order => 'author_addresses.id DESC ', :limit => 2).all.size
- assert_equal 3, Post.find(:all, :include => { :author => :author_address, :authors => :author_address},
- :order => ' author_addresses_authors.id DESC ', :limit => 3).size
+ assert_equal 3, Post.scoped(:includes => { :author => :author_address, :authors => :author_address},
+ :order => 'author_addresses_authors.id DESC ', :limit => 3).all.size
end
def test_find_with_nil_inside_set_passed_for_one_attribute
- client_of = Company.find(
- :all,
- :conditions => {
+ client_of = Company.scoped(
+ :where => {
:client_of => [2, 1, nil],
:name => ['37signals', 'Summit', 'Microsoft'] },
:order => 'client_of DESC'
@@ -1130,9 +771,8 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_with_nil_inside_set_passed_for_attribute
- client_of = Company.find(
- :all,
- :conditions => { :client_of => [nil] },
+ client_of = Company.scoped(
+ :where => { :client_of => [nil] },
:order => 'client_of DESC'
).map { |x| x.client_of }
@@ -1140,21 +780,16 @@ class FinderTest < ActiveRecord::TestCase
end
def test_with_limiting_with_custom_select
- posts = Post.find(:all, :include => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id')
+ posts = Post.references(:authors).scoped(
+ :includes => :author, :select => ' posts.*, authors.id as "author_id"',
+ :limit => 3, :order => 'posts.id'
+ ).all
assert_equal 3, posts.size
assert_equal [0, 1, 1], posts.map(&:author_id).sort
end
- def test_finder_with_scoped_from
- all_topics = Topic.find(:all)
-
- Topic.send(:with_scope, :find => { :from => 'fake_topics' }) do
- assert_equal all_topics, Topic.from('topics').to_a
- end
- end
-
def test_find_one_message_with_custom_primary_key
- Toy.set_primary_key :name
+ Toy.primary_key = :name
begin
Toy.find 'Hello World!'
rescue ActiveRecord::RecordNotFound => e
@@ -1162,6 +797,10 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_finder_with_offset_string
+ assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.scoped(:offset => "3").all }
+ end
+
protected
def bind(statement, *vars)
if vars.first.is_a?(Hash)
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 7e2dafcd01..c28f8de682 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -1,31 +1,34 @@
-require "cases/helper"
-require 'models/post'
+require 'cases/helper'
+require 'models/admin'
+require 'models/admin/account'
+require 'models/admin/randomly_named_c1'
+require 'models/admin/user'
require 'models/binary'
-require 'models/topic'
+require 'models/book'
+require 'models/category'
+require 'models/company'
require 'models/computer'
+require 'models/course'
require 'models/developer'
-require 'models/company'
-require 'models/task'
-require 'models/reply'
require 'models/joke'
-require 'models/course'
-require 'models/category'
+require 'models/matey'
require 'models/parrot'
require 'models/pirate'
-require 'models/treasure'
-require 'models/traffic_light'
-require 'models/matey'
+require 'models/post'
+require 'models/randomly_named_c1'
+require 'models/reply'
require 'models/ship'
-require 'models/book'
-require 'models/admin'
-require 'models/admin/account'
-require 'models/admin/user'
+require 'models/task'
+require 'models/topic'
+require 'models/traffic_light'
+require 'models/treasure'
require 'tempfile'
class FixturesTest < ActiveRecord::TestCase
self.use_instantiated_fixtures = true
self.use_transactional_fixtures = false
+ # other_topics fixture should not be included here
fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes, :binaries, :traffic_lights
FIXTURES = %w( accounts binaries companies customers
@@ -62,8 +65,9 @@ class FixturesTest < ActiveRecord::TestCase
end
def test_create_fixtures
- ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots")
- assert Parrot.find_by_name('Curious George'), 'George is in the database'
+ fixtures = ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, "parrots")
+ assert Parrot.find_by_name('Curious George'), 'George is not in the database'
+ assert fixtures.detect { |f| f.name == 'parrots' }, "no fixtures named 'parrots' in #{fixtures.map(&:name).inspect}"
end
def test_multiple_clean_fixtures
@@ -73,6 +77,13 @@ class FixturesTest < ActiveRecord::TestCase
fixtures_array.each { |fixtures| assert_kind_of(ActiveRecord::Fixtures, fixtures) }
end
+ def test_create_symbol_fixtures
+ fixtures = ActiveRecord::Fixtures.create_fixtures(FIXTURES_ROOT, :collections, :collections => Course) { Course.connection }
+
+ assert Course.find_by_name('Collection'), 'course is not in the database'
+ assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}"
+ end
+
def test_attributes
topics = create_fixtures("topics").first
assert_equal("The First Topic", topics["first"]["title"])
@@ -93,7 +104,7 @@ class FixturesTest < ActiveRecord::TestCase
# Reset cache to make finds on the new table work
ActiveRecord::Fixtures.reset_cache
- ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t|
+ ActiveRecord::Base.connection.create_table :prefix_other_topics_suffix do |t|
t.column :title, :string
t.column :author_name, :string
t.column :author_email_address, :string
@@ -115,23 +126,36 @@ class FixturesTest < ActiveRecord::TestCase
ActiveRecord::Base.table_name_prefix = 'prefix_'
ActiveRecord::Base.table_name_suffix = '_suffix'
- topics = create_fixtures("topics")
-
- first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'David'")
- assert_equal("The First Topic", first_row["title"])
+ other_topic_klass = Class.new(ActiveRecord::Base) do
+ def self.name
+ "OtherTopic"
+ end
+ end
- second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'Mary'")
- assert_nil(second_row["author_email_address"])
+ topics = [create_fixtures("other_topics")].flatten.first
# This checks for a caching problem which causes a bug in the fixtures
# class-level configuration helper.
assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create"
+
+ first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'David'")
+ assert_not_nil first_row, "The prefix_other_topics_suffix table appears to be empty despite create_fixtures: the row with author_name = 'David' was not found"
+ assert_equal("The First Topic", first_row["title"])
+
+ second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_other_topics_suffix WHERE author_name = 'Mary'")
+ assert_nil(second_row["author_email_address"])
+
+ assert_equal :prefix_other_topics_suffix, topics.table_name.to_sym
+ # This assertion should preferably be the last in the list, because calling
+ # other_topic_klass.table_name sets a class-level instance variable
+ assert_equal :prefix_other_topics_suffix, other_topic_klass.table_name.to_sym
+
ensure
# Restore prefix/suffix to its previous values
ActiveRecord::Base.table_name_prefix = old_prefix
ActiveRecord::Base.table_name_suffix = old_suffix
- ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil
+ ActiveRecord::Base.connection.drop_table :prefix_other_topics_suffix rescue nil
end
end
@@ -179,7 +203,7 @@ class FixturesTest < ActiveRecord::TestCase
#sanity check to make sure that this file never exists
assert Dir[nonexistent_fixture_path+"*"].empty?
- assert_raise(FixturesFileNotFound) do
+ assert_raise(Errno::ENOENT) do
ActiveRecord::Fixtures.new( Account.connection, "companies", 'Company', nonexistent_fixture_path)
end
end
@@ -213,7 +237,7 @@ class FixturesTest < ActiveRecord::TestCase
def test_binary_in_fixtures
data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read }
- data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding)
+ data.force_encoding('ASCII-8BIT')
data.freeze
assert_equal data, @flowers.data
end
@@ -490,7 +514,7 @@ class InvalidTableNameFixturesTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
def test_raises_error
- assert_raise FixtureClassNotFound do
+ assert_raise ActiveRecord::FixtureClassNotFound do
funny_jokes(:a_joke)
end
end
@@ -573,7 +597,7 @@ class FasterFixturesTest < ActiveRecord::TestCase
load_extra_fixture('posts')
assert ActiveRecord::Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts')
- self.class.setup_fixture_accessors('posts')
+ self.class.setup_fixture_accessors :posts
assert_equal 'Welcome to the weblog', posts(:welcome).title
end
end
@@ -692,7 +716,7 @@ class FoxyFixturesTest < ActiveRecord::TestCase
end
def test_only_generates_a_pk_if_necessary
- m = Matey.find(:first)
+ m = Matey.first
m.pirate = pirates(:blackbeard)
m.target = pirates(:redbeard)
end
@@ -731,3 +755,34 @@ class FixtureLoadingTest < ActiveRecord::TestCase
ActiveRecord::TestCase.try_to_load_dependency(:works_out_fine)
end
end
+
+class CustomNameForFixtureOrModelTest < ActiveRecord::TestCase
+ ActiveRecord::Fixtures.reset_cache
+
+ set_fixture_class :randomly_named_a9 =>
+ ClassNameThatDoesNotFollowCONVENTIONS,
+ :'admin/randomly_named_a9' =>
+ Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+ 'admin/randomly_named_b0' =>
+ Admin::ClassNameThatDoesNotFollowCONVENTIONS
+
+ fixtures :randomly_named_a9, 'admin/randomly_named_a9',
+ :'admin/randomly_named_b0'
+
+ def test_named_accessor_for_randomly_named_fixture_and_class
+ assert_kind_of ClassNameThatDoesNotFollowCONVENTIONS,
+ randomly_named_a9(:first_instance)
+ end
+
+ def test_named_accessor_for_randomly_named_namespaced_fixture_and_class
+ assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+ admin_randomly_named_a9(:first_instance)
+ assert_kind_of Admin::ClassNameThatDoesNotFollowCONVENTIONS,
+ admin_randomly_named_b0(:second_instance)
+ end
+
+ def test_table_name_is_defined_in_the_model
+ assert_equal 'randomly_named_table', ActiveRecord::Fixtures::all_loaded_fixtures["admin/randomly_named_a9"].table_name
+ assert_equal 'randomly_named_table', Admin::ClassNameThatDoesNotFollowCONVENTIONS.table_name
+ end
+end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 6735bc521b..37fa13f771 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -2,12 +2,15 @@ require File.expand_path('../../../../load_paths', __FILE__)
require 'config'
-require 'test/unit'
+gem 'minitest'
+require 'minitest/autorun'
require 'stringio'
require 'mocha'
+require 'cases/test_case'
require 'active_record'
require 'active_support/dependencies'
+require 'active_support/logger'
require 'support/config'
require 'support/connection'
@@ -17,8 +20,8 @@ require 'support/connection'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
-# Enable Identity Map only when ENV['IM'] is set to "true"
-ActiveRecord::IdentityMap.enabled = (ENV['IM'] == "true")
+# Avoid deprecation warning setting dependent_restrict_raises to false. The default is true
+ActiveRecord::Base.dependent_restrict_raises = false
# Connect to the database
ARTest.connect
@@ -34,7 +37,7 @@ def current_adapter?(*types)
end
def in_memory_db?
- current_adapter?(:SQLiteAdapter) &&
+ current_adapter?(:SQLite3Adapter) &&
ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:"
end
@@ -56,36 +59,13 @@ ensure
ActiveRecord::Base.default_timezone = old_zone
end
-module ActiveRecord
- class SQLCounter
- cattr_accessor :ignored_sql
- self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
-
- # FIXME: this needs to be refactored so specific database can add their own
- # ignored SQL. This ignored SQL is for Oracle.
- ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
-
- cattr_accessor :log
- self.log = []
-
- def call(name, start, finish, message_id, values)
- sql = values[:sql]
-
- # FIXME: this seems bad. we should probably have a better way to indicate
- # the query was cached
- unless 'CACHE' == values[:name]
- self.class.log << sql unless self.class.ignored_sql.any? { |r| sql =~ r }
- end
- end
- end
-
- ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
-end
-
unless ENV['FIXTURE_DEBUG']
module ActiveRecord::TestFixtures::ClassMethods
def try_to_load_dependency_with_silence(*args)
- ActiveRecord::Base.logger.silence { try_to_load_dependency_without_silence(*args)}
+ old = ActiveRecord::Base.logger.level
+ ActiveRecord::Base.logger.level = ActiveSupport::Logger::ERROR
+ try_to_load_dependency_without_silence(*args)
+ ActiveRecord::Base.logger.level = old
end
alias_method_chain :try_to_load_dependency, :silence
@@ -101,8 +81,8 @@ class ActiveSupport::TestCase
self.use_instantiated_fixtures = false
self.use_transactional_fixtures = true
- def create_fixtures(*table_names, &block)
- ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, table_names, fixture_class_names, &block)
+ def create_fixtures(*fixture_set_names, &block)
+ ActiveRecord::Fixtures.create_fixtures(ActiveSupport::TestCase.fixture_path, fixture_set_names, fixture_class_names, &block)
end
end
diff --git a/activerecord/test/cases/identity_map/middleware_test.rb b/activerecord/test/cases/identity_map/middleware_test.rb
deleted file mode 100644
index 60dcad4586..0000000000
--- a/activerecord/test/cases/identity_map/middleware_test.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-require "cases/helper"
-
-module ActiveRecord
- module IdentityMap
- class MiddlewareTest < ActiveRecord::TestCase
- def setup
- super
- @enabled = IdentityMap.enabled
- IdentityMap.enabled = false
- end
-
- def teardown
- super
- IdentityMap.enabled = @enabled
- IdentityMap.clear
- end
-
- def test_delegates
- called = false
- mw = Middleware.new lambda { |env|
- called = true
- }
- mw.call({})
- assert called, 'middleware delegated'
- end
-
- def test_im_enabled_during_delegation
- mw = Middleware.new lambda { |env|
- assert IdentityMap.enabled?, 'identity map should be enabled'
- }
- mw.call({})
- end
-
- class Enum < Struct.new(:iter)
- def each(&b)
- iter.call(&b)
- end
- end
-
- def test_im_enabled_during_body_each
- mw = Middleware.new lambda { |env|
- [200, {}, Enum.new(lambda { |&b|
- assert IdentityMap.enabled?, 'identity map should be enabled'
- b.call "hello"
- })]
- }
- body = mw.call({}).last
- body.each { |x| assert_equal 'hello', x }
- end
-
- def test_im_disabled_after_body_close
- mw = Middleware.new lambda { |env| [200, {}, []] }
- body = mw.call({}).last
- assert IdentityMap.enabled?, 'identity map should be enabled'
- body.close
- assert !IdentityMap.enabled?, 'identity map should be disabled'
- end
-
- def test_im_cleared_after_body_close
- mw = Middleware.new lambda { |env| [200, {}, []] }
- body = mw.call({}).last
-
- IdentityMap.repository['hello'] = 'world'
- assert !IdentityMap.repository.empty?, 'repo should not be empty'
-
- body.close
- assert IdentityMap.repository.empty?, 'repo should be empty'
- end
- end
- end
-end
diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb
deleted file mode 100644
index 3efc8bf559..0000000000
--- a/activerecord/test/cases/identity_map_test.rb
+++ /dev/null
@@ -1,439 +0,0 @@
-require "cases/helper"
-
-require 'models/developer'
-require 'models/project'
-require 'models/company'
-require 'models/topic'
-require 'models/reply'
-require 'models/computer'
-require 'models/customer'
-require 'models/order'
-require 'models/post'
-require 'models/author'
-require 'models/tag'
-require 'models/tagging'
-require 'models/comment'
-require 'models/sponsor'
-require 'models/member'
-require 'models/essay'
-require 'models/subscriber'
-require "models/pirate"
-require "models/bird"
-require "models/parrot"
-
-if ActiveRecord::IdentityMap.enabled?
-class IdentityMapTest < ActiveRecord::TestCase
- fixtures :accounts, :companies, :developers, :projects, :topics,
- :developers_projects, :computers, :authors, :author_addresses,
- :posts, :tags, :taggings, :comments, :subscribers
-
- ##############################################################################
- # Basic tests checking if IM is functioning properly on basic find operations#
- ##############################################################################
-
- def test_find_id
- assert_same(Client.find(3), Client.find(3))
- end
-
- def test_find_id_without_identity_map
- ActiveRecord::IdentityMap.without do
- assert_not_same(Client.find(3), Client.find(3))
- end
- end
-
- def test_find_id_use_identity_map
- ActiveRecord::IdentityMap.enabled = false
- ActiveRecord::IdentityMap.use do
- assert_same(Client.find(3), Client.find(3))
- end
- ActiveRecord::IdentityMap.enabled = true
- end
-
- def test_find_pkey
- assert_same(
- Subscriber.find('swistak'),
- Subscriber.find('swistak')
- )
- end
-
- def test_find_by_id
- assert_same(
- Client.find_by_id(3),
- Client.find_by_id(3)
- )
- end
-
- def test_find_by_string_and_numeric_id
- assert_same(
- Client.find_by_id("3"),
- Client.find_by_id(3)
- )
- end
-
- def test_find_by_pkey
- assert_same(
- Subscriber.find_by_nick('swistak'),
- Subscriber.find_by_nick('swistak')
- )
- end
-
- def test_find_first_id
- assert_same(
- Client.find(:first, :conditions => {:id => 1}),
- Client.find(:first, :conditions => {:id => 1})
- )
- end
-
- def test_find_first_pkey
- assert_same(
- Subscriber.find(:first, :conditions => {:nick => 'swistak'}),
- Subscriber.find(:first, :conditions => {:nick => 'swistak'})
- )
- 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 #
- ##############################################################################
-
- def test_owner_object_is_associated_from_identity_map
- post = Post.find(1)
- comment = post.comments.first
-
- assert_no_queries do
- comment.post
- end
- assert_same post, comment.post
- end
-
- def test_associated_object_are_assigned_from_identity_map
- post = Post.find(1)
-
- post.comments.each do |comment|
- assert_same post, comment.post
- assert_equal post.object_id, comment.post.object_id
- end
- end
-
- def test_creation
- t1 = Topic.create("title" => "t1")
- t2 = Topic.find(t1.id)
- assert_same(t1, t2)
- end
-
- ##############################################################################
- # Tests checking if IM is functioning properly on classes with multiple #
- # types of inheritance #
- ##############################################################################
-
- def test_inherited_without_type_attribute_without_identity_map
- ActiveRecord::IdentityMap.without do
- p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate")
- p2 = Pirate.find(p1.id)
- assert_not_same(p1, p2)
- end
- end
-
- def test_inherited_with_type_attribute_without_identity_map
- ActiveRecord::IdentityMap.without do
- c = comments(:sub_special_comment)
- c1 = SubSpecialComment.find(c.id)
- c2 = Comment.find(c.id)
- assert_same(c1.class, c2.class)
- end
- end
-
- def test_inherited_without_type_attribute
- p1 = DestructivePirate.create!(:catchphrase => "I'm not a regular Pirate")
- p2 = Pirate.find(p1.id)
- assert_not_same(p1, p2)
- end
-
- def test_inherited_with_type_attribute
- c = comments(:sub_special_comment)
- c1 = SubSpecialComment.find(c.id)
- c2 = Comment.find(c.id)
- assert_same(c1, c2)
- end
-
- ##############################################################################
- # Tests checking dirty attribute behavior with IM #
- ##############################################################################
-
- def test_loading_new_instance_should_not_update_dirty_attributes
- swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'})
- swistak.name = "Swistak Sreberkowiec"
- assert_equal(["name"], swistak.changed)
- assert_equal({"name" => ["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
-
- assert swistak.name_changed?
- assert_equal("Swistak Sreberkowiec", swistak.name)
- end
-
- def test_loading_new_instance_should_change_dirty_attribute_original_value
- swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'})
- swistak.name = "Swistak Sreberkowiec"
-
- Subscriber.update_all({:name => "Raczkowski Marcin"}, {:name => "Marcin Raczkowski"})
-
- assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
- assert_equal("Swistak Sreberkowiec", swistak.name)
- end
-
- def test_loading_new_instance_should_remove_dirt
- swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'})
- swistak.name = "Swistak Sreberkowiec"
-
- assert_equal({"name" => ["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
-
- Subscriber.update_all({:name => "Swistak Sreberkowiec"}, {:name => "Marcin Raczkowski"})
-
- assert_equal("Swistak Sreberkowiec", swistak.name)
- assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes)
- assert swistak.name_changed?
- end
-
- def test_has_many_associations
- pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?")
- pirate.birds.create!(:name => 'Posideons Killer')
- pirate.birds.create!(:name => 'Killer bandita Dionne')
-
- posideons, _ = pirate.birds
-
- pirate.reload
-
- pirate.birds_attributes = [{ :id => posideons.id, :name => 'Grace OMalley' }]
- assert_equal 'Grace OMalley', pirate.birds.to_a.find { |r| r.id == posideons.id }.name
- end
-
- def test_changing_associations
- post1 = Post.create("title" => "One post", "body" => "Posting...")
- post2 = Post.create("title" => "Another post", "body" => "Posting... Again...")
- comment = Comment.new("body" => "comment")
-
- comment.post = post1
- assert comment.save
-
- assert_same(post1.comments.first, comment)
-
- comment.post = post2
- assert comment.save
-
- assert_same(post2.comments.first, comment)
- assert_equal(0, post1.comments.size)
- end
-
- def test_im_with_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins
- tag = posts(:welcome).tags.first
- tag_with_joins_and_select = posts(:welcome).tags.add_joins_and_select.first
- assert_same(tag, tag_with_joins_and_select)
- assert_nothing_raised(NoMethodError, "Joins/select was not loaded") { tag.author_id }
- end
-
- ##############################################################################
- # Tests checking Identity Map behavior with preloaded associations, joins, #
- # includes etc. #
- ##############################################################################
-
- def test_find_with_preloaded_associations
- assert_queries(2) do
- posts = Post.preload(:comments).order('posts.id')
- assert posts.first.comments.first
- end
-
- # With IM we'll retrieve post object from previous query, it'll have comments
- # already preloaded from first call
- assert_queries(1) do
- posts = Post.preload(:comments).order('posts.id')
- assert posts.first.comments.first
- end
-
- assert_queries(2) do
- posts = Post.preload(:author).order('posts.id')
- assert posts.first.author
- end
-
- # With IM we'll retrieve post object from previous query, it'll have comments
- # already preloaded from first call
- assert_queries(1) do
- posts = Post.preload(:author).order('posts.id')
- assert posts.first.author
- end
-
- assert_queries(1) do
- posts = Post.preload(:author, :comments).order('posts.id')
- assert posts.first.author
- assert posts.first.comments.first
- end
- end
-
- def test_find_with_included_associations
- assert_queries(2) do
- posts = Post.includes(:comments).order('posts.id')
- assert posts.first.comments.first
- end
-
- assert_queries(1) do
- posts = Post.scoped.includes(:comments).order('posts.id')
- assert posts.first.comments.first
- end
-
- assert_queries(2) do
- posts = Post.includes(:author).order('posts.id')
- assert posts.first.author
- end
-
- assert_queries(1) do
- posts = Post.includes(:author, :comments).order('posts.id')
- assert posts.first.author
- assert posts.first.comments.first
- end
- end
-
- def test_eager_loading_with_conditions_on_joined_table_preloads
- posts = Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
- assert_equal [posts(:welcome)], posts
- assert_equal authors(:david), assert_no_queries { posts[0].author}
- assert_same posts.first.author, Author.first
-
- posts = Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
- assert_equal [posts(:welcome)], posts
- assert_equal authors(:david), assert_no_queries { posts[0].author}
- assert_same posts.first.author, Author.first
-
- posts = Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id')
- assert_equal posts(:welcome, :thinking), posts
- assert_same posts.first.author, Author.first
-
- posts = Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id')
- assert_equal posts(:welcome, :thinking), posts
- assert_same posts.first.author, Author.first
- end
-
- def test_eager_loading_with_conditions_on_string_joined_table_preloads
- posts = assert_queries(2) do
- Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
- end
- assert_equal [posts(:welcome)], posts
- assert_equal authors(:david), assert_no_queries { posts[0].author}
-
- posts = assert_queries(1) do
- Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id')
- end
- assert_equal [posts(:welcome)], posts
- assert_equal authors(:david), assert_no_queries { posts[0].author}
- end
-
- ##############################################################################
- # Behaviour related to saving failures
- ##############################################################################
-
- def test_reload_object_if_save_failed
- developer = Developer.first
- developer.salary = 0
-
- assert !developer.save
-
- same_developer = Developer.first
-
- assert_not_same developer, same_developer
- assert_not_equal 0, same_developer.salary
- assert_not_equal developer.salary, same_developer.salary
- end
-
- def test_reload_object_if_forced_save_failed
- developer = Developer.first
- developer.salary = 0
-
- assert_raise(ActiveRecord::RecordInvalid) { developer.save! }
-
- same_developer = Developer.first
-
- assert_not_same developer, same_developer
- assert_not_equal 0, same_developer.salary
- assert_not_equal developer.salary, same_developer.salary
- end
-
- def test_reload_object_if_update_attributes_fails
- developer = Developer.first
- developer.salary = 0
-
- assert !developer.update_attributes(:salary => 0)
-
- same_developer = Developer.first
-
- assert_not_same developer, same_developer
- assert_not_equal 0, same_developer.salary
- assert_not_equal developer.salary, same_developer.salary
- end
-
- ##############################################################################
- # Behaviour of readonly, frozen, destroyed
- ##############################################################################
-
- def test_find_using_identity_map_respects_readonly_when_loading_associated_object_first
- author = Author.first
- readonly_comment = author.readonly_comments.first
-
- comment = Comment.first
- assert !comment.readonly?
-
- assert readonly_comment.readonly?
-
- assert_raise(ActiveRecord::ReadOnlyRecord) {readonly_comment.save}
- assert comment.save
- end
-
- def test_find_using_identity_map_respects_readonly
- comment = Comment.first
- assert !comment.readonly?
-
- author = Author.first
- readonly_comment = author.readonly_comments.first
-
- assert readonly_comment.readonly?
-
- assert_raise(ActiveRecord::ReadOnlyRecord) {readonly_comment.save}
- assert comment.save
- end
-
- def test_find_using_select_and_identity_map
- author_id, author = Author.select('id').first, Author.first
-
- assert_equal author_id, author
- assert_same author_id, author
- assert_not_nil author.name
-
- post, post_id = Post.first, Post.select('id').first
-
- assert_equal post_id, post
- assert_same post_id, post
- assert_not_nil post.title
- 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
-# assert client = Client.find(3),
-# client.update_attribute(:id, 666)
-#
-# assert Client.find(666)
-# assert_same(client, Client.find(666))
-#
-# s = Subscriber.find_by_nick('swistak')
-# assert s.update_attribute(:nick, 'swistakTheJester')
-# assert_equal('swistakTheJester', s.nick)
-#
-# assert stj = Subscriber.find_by_nick('swistakTheJester')
-# assert_same(s, stj)
-# end
-
-end
-end
diff --git a/activerecord/test/cases/inclusion_test.rb b/activerecord/test/cases/inclusion_test.rb
new file mode 100644
index 0000000000..8726ba5e51
--- /dev/null
+++ b/activerecord/test/cases/inclusion_test.rb
@@ -0,0 +1,113 @@
+require 'cases/helper'
+require 'models/teapot'
+
+class BasicInclusionModelTest < ActiveRecord::TestCase
+ def test_basic_model
+ Teapot.create!(:name => "Ronnie Kemper")
+ assert_equal "Ronnie Kemper", Teapot.find(1).name
+ end
+
+ def test_initialization
+ t = Teapot.new(:name => "Bob")
+ assert_equal "Bob", t.name
+ end
+
+ def test_inherited_model
+ teapot = CoolTeapot.create!(:name => "Bob")
+ teapot.reload
+
+ assert_equal "Bob", teapot.name
+ assert_equal "mmm", teapot.aaahhh
+ end
+
+ def test_generated_feature_methods
+ assert Teapot < Teapot::GeneratedFeatureMethods
+ end
+
+ def test_exists
+ t = Teapot.create!(:name => "Ronnie Kemper")
+ assert Teapot.exists?(t)
+ end
+
+ def test_predicate_builder
+ t = Teapot.create!(:name => "Bob")
+ assert_equal "Bob", Teapot.where(:id => [t]).first.name
+ assert_equal "Bob", Teapot.where(:id => t).first.name
+ end
+
+ def test_nested_model
+ assert_equal "ceiling_teapots", Ceiling::Teapot.table_name
+ end
+end
+
+class InclusionUnitTest < ActiveRecord::TestCase
+ def setup
+ @klass = Class.new { include ActiveRecord::Model }
+ end
+
+ def test_non_abstract_class
+ assert !@klass.abstract_class?
+ end
+
+ def test_abstract_class
+ @klass.abstract_class = true
+ assert @klass.abstract_class?
+ end
+
+ def test_establish_connection
+ assert @klass.respond_to?(:establish_connection)
+ assert ActiveRecord::Model.respond_to?(:establish_connection)
+ end
+
+ def test_adapter_connection
+ name = "#{ActiveRecord::Base.connection_config[:adapter]}_connection"
+ assert @klass.respond_to?(name)
+ assert ActiveRecord::Model.respond_to?(name)
+ end
+
+ def test_connection_handler
+ assert_equal ActiveRecord::Base.connection_handler, @klass.connection_handler
+ end
+
+ def test_mirrored_configuration
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ assert @klass.time_zone_aware_attributes
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ assert !@klass.time_zone_aware_attributes
+ ensure
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ end
+
+ # Doesn't really test anything, but this is here to ensure warnings don't occur
+ def test_included_twice
+ @klass.send :include, ActiveRecord::Model
+ end
+
+ def test_deprecation_proxy
+ assert_equal ActiveRecord::Model.name, ActiveRecord::Model::DeprecationProxy.name
+ assert_equal ActiveRecord::Base.superclass, assert_deprecated { ActiveRecord::Model::DeprecationProxy.superclass }
+
+ sup, sup2 = nil, nil
+ ActiveSupport.on_load(:__test_active_record_model_deprecation) do
+ sup = superclass
+ sup2 = send(:superclass)
+ end
+ assert_deprecated do
+ ActiveSupport.run_load_hooks(:__test_active_record_model_deprecation, ActiveRecord::Model::DeprecationProxy)
+ end
+ assert_equal ActiveRecord::Base.superclass, sup
+ assert_equal ActiveRecord::Base.superclass, sup2
+ end
+end
+
+class InclusionFixturesTest < ActiveRecord::TestCase
+ fixtures :teapots
+
+ def test_fixtured_record
+ assert_equal "Bob", teapots(:bob).name
+ end
+
+ def test_timestamped_fixture
+ assert_not_nil teapots(:bob).created_at
+ end
+end
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index b5d8314541..06de51f5cd 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -15,7 +15,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_class_with_blank_sti_name
- company = Company.find(:first)
+ company = Company.first
company = company.dup
company.extend(Module.new {
def read_attribute(name)
@@ -24,7 +24,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
})
company.save!
- company = Company.find(:all).find { |x| x.id == company.id }
+ company = Company.all.find { |x| x.id == company.id }
assert_equal ' ', company.type
end
@@ -65,7 +65,6 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_company_descends_from_active_record
- assert_raise(NoMethodError) { ActiveRecord::Base.descends_from_active_record? }
assert AbstractCompany.descends_from_active_record?, 'AbstractCompany should descend from ActiveRecord::Base'
assert Company.descends_from_active_record?, 'Company should descend from ActiveRecord::Base'
assert !Class.new(Company).descends_from_active_record?, 'Company subclass should not descend from ActiveRecord::Base'
@@ -99,7 +98,7 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_inheritance_find_all
- companies = Company.find(:all, :order => 'id')
+ companies = Company.scoped(:order => 'id').all
assert_kind_of Firm, companies[0], "37signals should be a firm"
assert_kind_of Client, companies[1], "Summit should be a client"
end
@@ -150,9 +149,9 @@ class InheritanceTest < ActiveRecord::TestCase
def test_update_all_within_inheritance
Client.update_all "name = 'I am a client'"
- assert_equal "I am a client", Client.find(:all).first.name
+ assert_equal "I am a client", Client.all.first.name
# Order by added as otherwise Oracle tests were failing because of different order of results
- assert_equal "37signals", Firm.find(:all, :order => "id").first.name
+ assert_equal "37signals", Firm.scoped(:order => "id").all.first.name
end
def test_alt_update_all_within_inheritance
@@ -174,9 +173,9 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_find_first_within_inheritance
- assert_kind_of Firm, Company.find(:first, :conditions => "name = '37signals'")
- assert_kind_of Firm, Firm.find(:first, :conditions => "name = '37signals'")
- assert_nil Client.find(:first, :conditions => "name = '37signals'")
+ assert_kind_of Firm, Company.scoped(:where => "name = '37signals'").first
+ assert_kind_of Firm, Firm.scoped(:where => "name = '37signals'").first
+ assert_nil Client.scoped(:where => "name = '37signals'").first
end
def test_alt_find_first_within_inheritance
@@ -188,10 +187,10 @@ class InheritanceTest < ActiveRecord::TestCase
def test_complex_inheritance
very_special_client = VerySpecialClient.create("name" => "veryspecial")
assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first
- assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'")
- assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'")
- assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'")
- assert_equal 1, Client.find(:all, :conditions => "name = 'Summit'").size
+ assert_equal very_special_client, SpecialClient.scoped(:where => "name = 'veryspecial'").first
+ assert_equal very_special_client, Company.scoped(:where => "name = 'veryspecial'").first
+ assert_equal very_special_client, Client.scoped(:where => "name = 'veryspecial'").first
+ assert_equal 1, Client.scoped(:where => "name = 'Summit'").all.size
assert_equal very_special_client, Client.find(very_special_client.id)
end
@@ -202,14 +201,14 @@ class InheritanceTest < ActiveRecord::TestCase
end
def test_eager_load_belongs_to_something_inherited
- account = Account.find(1, :include => :firm)
+ account = Account.scoped(:includes => :firm).find(1)
assert account.association_cache.key?(:firm), "nil proves eager load failed"
end
def test_eager_load_belongs_to_primary_key_quoting
con = Account.connection
assert_sql(/#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)/) do
- Account.find(1, :include => :firm)
+ Account.scoped(:includes => :firm).find(1)
end
end
@@ -231,16 +230,16 @@ class InheritanceTest < ActiveRecord::TestCase
private
def switch_to_alt_inheritance_column
# we don't want misleading test results, so get rid of the values in the type column
- Company.find(:all, :order => 'id').each do |c|
+ Company.scoped(:order => 'id').all.each do |c|
c['type'] = nil
c.save
end
[ Company, Firm, Client].each { |klass| klass.reset_column_information }
- Company.set_inheritance_column('ruby_type')
+ Company.inheritance_column = 'ruby_type'
end
def switch_to_default_inheritance_column
[ Company, Firm, Client].each { |klass| klass.reset_column_information }
- Company.set_inheritance_column('type')
+ Company.inheritance_column = 'type'
end
end
@@ -260,7 +259,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase
def test_instantiation_doesnt_try_to_require_corresponding_file
ActiveRecord::Base.store_full_sti_class = false
- foo = Firm.find(:first).clone
+ foo = Firm.first.clone
foo.ruby_type = foo.type = 'FirmOnTheFly'
foo.save!
diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb
index 98cda010ae..426a350379 100644
--- a/activerecord/test/cases/invalid_date_test.rb
+++ b/activerecord/test/cases/invalid_date_test.rb
@@ -7,8 +7,6 @@ class InvalidDateTest < ActiveRecord::TestCase
invalid_dates = [[2007, 11, 31], [1993, 2, 29], [2007, 2, 29]]
- topic = Topic.new
-
valid_dates.each do |date_src|
topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s)
# Oracle DATE columns are datetime columns and Oracle adapter returns Time value
diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb
index 3ae7b63dff..8f1cdd47ea 100644
--- a/activerecord/test/cases/invertible_migration_test.rb
+++ b/activerecord/test/cases/invertible_migration_test.rb
@@ -88,5 +88,17 @@ module ActiveRecord
LegacyMigration.down
assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"
end
+
+ def test_migrate_down_with_table_name_prefix
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ ActiveRecord::Base.table_name_suffix = '_s'
+ migration = InvertibleMigration.new
+ migration.migrate(:up)
+ assert_nothing_raised { migration.migrate(:down) }
+ assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist"
+ ensure
+ ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ''
+ end
+
end
end
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index 75e5dfa49b..0b78f2e46b 100644
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -137,7 +137,7 @@ class LifecycleTest < ActiveRecord::TestCase
def test_auto_observer
topic_observer = TopicaAuditor.instance
assert_nil TopicaAuditor.observed_class
- assert_equal [Topic], TopicaAuditor.instance.observed_classes.to_a
+ assert_equal [Topic], TopicaAuditor.observed_classes.to_a
topic = Topic.find(1)
assert_equal topic.title, topic_observer.topic.title
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index e9bd7f07b6..afb0bd6fd9 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -6,12 +6,16 @@ require 'models/reader'
require 'models/legacy_thing'
require 'models/reference'
require 'models/string_key_object'
+require 'models/car'
+require 'models/engine'
+require 'models/wheel'
+require 'models/treasure'
class LockWithoutDefault < ActiveRecord::Base; end
class LockWithCustomColumnWithoutDefault < ActiveRecord::Base
- set_table_name :lock_without_defaults_cust
- set_locking_column :custom_lock_version
+ self.table_name = :lock_without_defaults_cust
+ self.locking_column = :custom_lock_version
end
class ReadonlyFirstNamePerson < Person
@@ -19,7 +23,7 @@ class ReadonlyFirstNamePerson < Person
end
class OptimisticLockingTest < ActiveRecord::TestCase
- fixtures :people, :legacy_things, :references, :string_key_objects
+ fixtures :people, :legacy_things, :references, :string_key_objects, :peoples_treasures
def test_non_integer_lock_existing
s1 = StringKeyObject.find("record1")
@@ -224,6 +228,27 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal lock_version, p1.lock_version
end
end
+
+ def test_polymorphic_destroy_with_dependencies_and_lock_version
+ car = Car.create!
+
+ assert_difference 'car.wheels.count' do
+ car.wheels << Wheel.create!
+ end
+ assert_difference 'car.wheels.count', -1 do
+ car.destroy
+ end
+ assert car.destroyed?
+ end
+
+ def test_removing_has_and_belongs_to_many_associations_upon_destroy
+ p = RichPerson.create! first_name: 'Jon'
+ p.treasures.create!
+ assert !p.treasures.empty?
+ p.destroy
+ assert p.treasures.empty?
+ assert RichPerson.connection.select_all("SELECT * FROM peoples_treasures WHERE rich_person_id = 1").empty?
+ end
end
class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
@@ -278,6 +303,8 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
assert_equal true, p1.frozen?
assert_raises(ActiveRecord::RecordNotFound) { Person.find(p1.id) }
assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) }
+ ensure
+ remove_counter_column_from(Person, 'legacy_things_count')
end
private
@@ -289,14 +316,14 @@ class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
model.update_all(col => 0) if current_adapter?(:OpenBaseAdapter)
end
- def remove_counter_column_from(model)
- model.connection.remove_column model.table_name, :test_count
+ def remove_counter_column_from(model, col = :test_count)
+ model.connection.remove_column model.table_name, col
model.reset_column_information
end
def counter_test(model, expected_count)
add_counter_column_to(model)
- object = model.find(:first)
+ object = model.first
assert_equal 0, object.test_count
assert_equal 0, object.send(model.locking_column)
yield object.id
@@ -331,18 +358,7 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
def test_sane_find_with_lock
assert_nothing_raised do
Person.transaction do
- Person.find 1, :lock => true
- end
- end
- end
-
- # Test scoped lock.
- def test_sane_find_with_scoped_lock
- assert_nothing_raised do
- Person.transaction do
- Person.send(:with_scope, :find => { :lock => true }) do
- Person.find 1
- end
+ Person.lock.find(1)
end
end
end
@@ -353,7 +369,7 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
def test_eager_find_with_lock
assert_nothing_raised do
Person.transaction do
- Person.find 1, :include => :readers, :lock => true
+ Person.includes(:readers).lock.find(1)
end
end
end
@@ -371,22 +387,32 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
end
end
+ def test_with_lock_commits_transaction
+ person = Person.find 1
+ person.with_lock do
+ person.first_name = 'fooman'
+ person.save!
+ end
+ assert_equal 'fooman', person.reload.first_name
+ end
+
+ def test_with_lock_rolls_back_transaction
+ person = Person.find 1
+ old = person.first_name
+ person.with_lock do
+ person.first_name = 'fooman'
+ person.save!
+ raise 'oops'
+ end rescue nil
+ assert_equal old, person.reload.first_name
+ end
+
if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
def test_no_locks_no_wait
first, second = duel { Person.find 1 }
assert first.end > second.end
end
- # Hit by ruby deadlock detection since connection checkout is mutexed.
- if RUBY_VERSION < '1.9.0'
- def test_second_lock_waits
- assert [0.2, 1, 5].any? { |zzz|
- first, second = duel(zzz) { Person.find 1, :lock => true }
- second.end > first.end
- }
- end
- end
-
protected
def duel(zzz = 5)
t0, t1, t2, t3 = nil, nil, nil, nil
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 9e8475465e..acd2fcdad4 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -5,14 +5,13 @@ require "active_support/log_subscriber/test_helper"
class LogSubscriberTest < ActiveRecord::TestCase
include ActiveSupport::LogSubscriber::TestHelper
- include ActiveSupport::BufferedLogger::Severity
+ include ActiveSupport::Logger::Severity
fixtures :posts
def setup
@old_logger = ActiveRecord::Base.logger
- @using_identity_map = ActiveRecord::IdentityMap.enabled?
- ActiveRecord::IdentityMap.enabled = false
+ Developer.primary_key
super
ActiveRecord::LogSubscriber.attach_to(:active_record)
end
@@ -21,7 +20,6 @@ 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)
@@ -102,13 +100,4 @@ class LogSubscriberTest < ActiveRecord::TestCase
def test_initializes_runtime
Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join
end
-
- def test_log
- ActiveRecord::IdentityMap.use do
- Post.find 1
- Post.find 1
- end
- wait
- assert_match(/From Identity Map/, @logger.logged(:debug).last)
- end
end
diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb
index ef35f3341e..2f98d3c646 100644
--- a/activerecord/test/cases/mass_assignment_security_test.rb
+++ b/activerecord/test/cases/mass_assignment_security_test.rb
@@ -50,6 +50,13 @@ module MassAssignmentTestHelpers
assert_equal 'm', person.gender
assert_equal 'rides a sweet bike', person.comments
end
+
+ def with_strict_sanitizer
+ ActiveRecord::Base.mass_assignment_sanitizer = :strict
+ yield
+ ensure
+ ActiveRecord::Base.mass_assignment_sanitizer = :logger
+ end
end
module MassAssignmentRelationTestHelpers
@@ -231,13 +238,32 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase
def test_protection_against_class_attribute_writers
[:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names,
- :default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method|
+ :default_timezone, :schema_format, :lock_optimistically, :timestamped_migrations, :default_scopes,
+ :connection_handler, :nested_attributes_options, :_attr_readonly, :attribute_types_cached_by_default,
+ :attribute_method_matchers, :time_zone_aware_attributes, :skip_time_zone_conversion_for_attributes].each do |method|
assert_respond_to Task, method
assert_respond_to Task, "#{method}="
assert_respond_to Task.new, method
assert !Task.new.respond_to?("#{method}=")
end
end
+end
+
+
+# This class should be deleted when we removed active_record_deprecated_finders as a
+# dependency.
+class MassAssignmentSecurityDeprecatedFindersTest < ActiveRecord::TestCase
+ include MassAssignmentTestHelpers
+
+ def setup
+ super
+ @deprecation_behavior = ActiveSupport::Deprecation.behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.behavior = @deprecation_behavior
+ end
def test_find_or_initialize_by_with_attr_accessible_attributes
p = TightPerson.find_or_initialize_by_first_name('Josh', attributes_hash)
@@ -321,6 +347,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_all_attributes(best_friend)
end
+ def test_has_one_build_with_strict_sanitizer
+ with_strict_sanitizer do
+ best_friend = @person.build_best_friend(attributes_hash.except(:id, :comments))
+ assert_equal @person.id, best_friend.best_friend_id
+ end
+ end
+
# create
def test_has_one_create_with_attr_protected_attributes
@@ -348,6 +381,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_all_attributes(best_friend)
end
+ def test_has_one_create_with_strict_sanitizer
+ with_strict_sanitizer do
+ best_friend = @person.create_best_friend(attributes_hash.except(:id, :comments))
+ assert_equal @person.id, best_friend.best_friend_id
+ end
+ end
+
# create!
def test_has_one_create_with_bang_with_attr_protected_attributes
@@ -375,6 +415,13 @@ class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase
assert_all_attributes(best_friend)
end
+ def test_has_one_create_with_bang_with_strict_sanitizer
+ with_strict_sanitizer do
+ best_friend = @person.create_best_friend!(attributes_hash.except(:id, :comments))
+ assert_equal @person.id, best_friend.best_friend_id
+ end
+ end
+
end
@@ -436,6 +483,13 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_all_attributes(best_friend)
end
+ def test_belongs_to_create_with_strict_sanitizer
+ with_strict_sanitizer do
+ best_friend = @person.create_best_friend_of(attributes_hash.except(:id, :comments))
+ assert_equal best_friend.id, @person.best_friend_of_id
+ end
+ end
+
# create!
def test_belongs_to_create_with_bang_with_attr_protected_attributes
@@ -463,6 +517,13 @@ class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase
assert_all_attributes(best_friend)
end
+ def test_belongs_to_create_with_bang_with_strict_sanitizer
+ with_strict_sanitizer do
+ best_friend = @person.create_best_friend_of!(attributes_hash.except(:id, :comments))
+ assert_equal best_friend.id, @person.best_friend_of_id
+ end
+ end
+
end
@@ -497,6 +558,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_all_attributes(best_friend)
end
+ def test_has_many_build_with_strict_sanitizer
+ with_strict_sanitizer do
+ best_friend = @person.best_friends.build(attributes_hash.except(:id, :comments))
+ assert_equal @person.id, best_friend.best_friend_id
+ end
+ end
+
# create
def test_has_many_create_with_attr_protected_attributes
@@ -524,6 +592,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_all_attributes(best_friend)
end
+ def test_has_many_create_with_strict_sanitizer
+ with_strict_sanitizer do
+ best_friend = @person.best_friends.create(attributes_hash.except(:id, :comments))
+ assert_equal @person.id, best_friend.best_friend_id
+ end
+ end
+
# create!
def test_has_many_create_with_bang_with_attr_protected_attributes
@@ -551,6 +626,13 @@ class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase
assert_all_attributes(best_friend)
end
+ def test_has_many_create_with_bang_with_strict_sanitizer
+ with_strict_sanitizer do
+ best_friend = @person.best_friends.create!(attributes_hash.except(:id, :comments))
+ assert_equal @person.id, best_friend.best_friend_id
+ end
+ end
+
end
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
deleted file mode 100644
index 0ab4f30363..0000000000
--- a/activerecord/test/cases/method_scoping_test.rb
+++ /dev/null
@@ -1,558 +0,0 @@
-# This file can be removed once with_exclusive_scope and with_scope are removed.
-# All the tests were already ported to relation_scoping_test.rb when the new
-# relation scoping API was added.
-
-require "cases/helper"
-require 'models/post'
-require 'models/author'
-require 'models/developer'
-require 'models/project'
-require 'models/comment'
-
-class MethodScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
-
- def test_set_conditions
- Developer.send(:with_scope, :find => { :conditions => 'just a test...' }) do
- assert_match '(just a test...)', Developer.scoped.to_sql
- end
- end
-
- def test_scoped_find
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- assert_nothing_raised { Developer.find(1) }
- end
- end
-
- def test_scoped_find_first
- Developer.send(:with_scope, :find => { :conditions => "salary = 100000" }) do
- assert_equal Developer.find(10), Developer.find(:first, :order => 'name')
- end
- end
-
- def test_scoped_find_last
- highest_salary = Developer.find(:first, :order => "salary DESC")
-
- Developer.send(:with_scope, :find => { :order => "salary" }) do
- assert_equal highest_salary, Developer.last
- end
- end
-
- def test_scoped_find_last_preserves_scope
- lowest_salary = Developer.find(:first, :order => "salary ASC")
- highest_salary = Developer.find(:first, :order => "salary DESC")
-
- Developer.send(:with_scope, :find => { :order => "salary" }) do
- assert_equal highest_salary, Developer.last
- assert_equal lowest_salary, Developer.first
- end
- end
-
- def test_scoped_find_combines_conditions
- Developer.send(:with_scope, :find => { :conditions => "salary = 9000" }) do
- assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => "name = 'Jamis'")
- end
- end
-
- def test_scoped_find_sanitizes_conditions
- Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do
- assert_equal developers(:poor_jamis), Developer.find(:first)
- end
- end
-
- def test_scoped_find_combines_and_sanitizes_conditions
- Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do
- assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
- end
- end
-
- def test_scoped_find_all
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- assert_equal [developers(:david)], Developer.all
- end
- end
-
- def test_scoped_find_select
- Developer.send(:with_scope, :find => { :select => "id, name" }) do
- developer = Developer.find(:first, :conditions => "name = 'David'")
- assert_equal "David", developer.name
- assert !developer.has_attribute?(:salary)
- end
- end
-
- def test_scope_select_concatenates
- Developer.send(:with_scope, :find => { :select => "name" }) do
- developer = Developer.find(:first, :select => 'id, salary', :conditions => "name = 'David'")
- assert_equal 80000, developer.salary
- assert developer.has_attribute?(:id)
- assert developer.has_attribute?(:name)
- assert developer.has_attribute?(:salary)
- end
- end
-
- def test_scoped_count
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- assert_equal 1, Developer.count
- end
-
- Developer.send(:with_scope, :find => { :conditions => 'salary = 100000' }) do
- assert_equal 8, Developer.count
- assert_equal 1, Developer.count(:conditions => "name LIKE 'fixture_1%'")
- end
- end
-
- def test_scoped_find_include
- # with the include, will retrieve only developers for the given project
- scoped_developers = Developer.send(:with_scope, :find => { :include => :projects }) do
- Developer.find(:all, :conditions => 'projects.id = 2')
- end
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 1, scoped_developers.size
- end
-
- def test_scoped_find_joins
- scoped_developers = Developer.send(:with_scope, :find => { :joins => 'JOIN developers_projects ON id = developer_id' } ) do
- Developer.find(:all, :conditions => 'developers_projects.project_id = 2')
- end
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 1, scoped_developers.size
- assert_equal developers(:david).attributes, scoped_developers.first.attributes
- end
-
- def test_scoped_find_using_new_style_joins
- scoped_developers = Developer.send(:with_scope, :find => { :joins => :projects }) do
- Developer.find(:all, :conditions => 'projects.id = 2')
- end
- assert scoped_developers.include?(developers(:david))
- assert !scoped_developers.include?(developers(:jamis))
- assert_equal 1, scoped_developers.size
- assert_equal developers(:david).attributes, scoped_developers.first.attributes
- end
-
- def test_scoped_find_merges_old_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id ' }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_merges_new_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => :comments, :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_merges_new_and_old_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_merges_string_array_style_and_string_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => ["INNER JOIN posts ON posts.author_id = authors.id"]}) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_merges_string_array_style_and_hash_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => :posts}) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN comments ON posts.id = comments.post_id'], :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_merges_joins_and_eliminates_duplicate_string_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON posts.author_id = authors.id'}) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => ["INNER JOIN posts ON posts.author_id = authors.id", "INNER JOIN comments ON posts.id = comments.post_id"], :conditions => 'comments.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_find_strips_spaces_from_string_joins_and_eliminates_duplicate_string_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => ' INNER JOIN posts ON posts.author_id = authors.id '}) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN posts ON posts.author_id = authors.id'], :conditions => 'posts.id = 1')
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_scoped_count_include
- # with the include, will retrieve only developers for the given project
- Developer.send(:with_scope, :find => { :include => :projects }) do
- assert_equal 1, Developer.count(:conditions => 'projects.id = 2')
- end
- end
-
- def test_scope_for_create_only_uses_equal
- table = VerySpecialComment.arel_table
- relation = VerySpecialComment.scoped
- relation.where_values << table[:id].not_eq(1)
- assert_equal({:type => "VerySpecialComment"}, relation.send(:scope_for_create))
- end
-
- def test_scoped_create
- new_comment = nil
-
- VerySpecialComment.send(:with_scope, :create => { :post_id => 1 }) do
- assert_equal({:post_id => 1, :type => 'VerySpecialComment' }, VerySpecialComment.scoped.send(:scope_for_create))
- new_comment = VerySpecialComment.create :body => "Wonderful world"
- end
-
- assert Post.find(1).comments.include?(new_comment)
- end
-
- def test_scoped_create_with_join_and_merge
- Comment.where(:body => "but Who's Buying?").joins(:post).merge(Post.where(:body => 'Peace Sells...')).with_scope do
- assert_equal({:body => "but Who's Buying?"}, Comment.scoped.scope_for_create)
- end
- end
-
- def test_immutable_scope
- options = { :conditions => "name = 'David'" }
- Developer.send(:with_scope, :find => options) do
- assert_equal %w(David), Developer.all.map(&:name)
- options[:conditions] = "name != 'David'"
- assert_equal %w(David), Developer.all.map(&:name)
- end
-
- scope = { :find => { :conditions => "name = 'David'" }}
- Developer.send(:with_scope, scope) do
- assert_equal %w(David), Developer.all.map(&:name)
- scope[:find][:conditions] = "name != 'David'"
- assert_equal %w(David), Developer.all.map(&:name)
- end
- end
-
- def test_scoped_with_duck_typing
- scoping = Struct.new(:current_scope).new(:find => { :conditions => ["name = ?", 'David'] })
- Developer.send(:with_scope, scoping) do
- assert_equal %w(David), Developer.all.map(&:name)
- end
- end
-
- def test_ensure_that_method_scoping_is_correctly_restored
- begin
- Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do
- raise "an exception"
- end
- rescue
- end
-
- assert !Developer.scoped.where_values.include?("name = 'Jamis'")
- end
-end
-
-class NestedScopingTest < ActiveRecord::TestCase
- fixtures :authors, :developers, :projects, :comments, :posts
-
- def test_merge_options
- Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
- Developer.send(:with_scope, :find => { :limit => 10 }) do
- devs = Developer.scoped
- assert_match '(salary = 80000)', devs.to_sql
- assert_equal 10, devs.taken
- end
- end
- end
-
- def test_merge_inner_scope_has_priority
- Developer.send(:with_scope, :find => { :limit => 5 }) do
- Developer.send(:with_scope, :find => { :limit => 10 }) do
- assert_equal 10, Developer.scoped.taken
- end
- end
- end
-
- def test_replace_options
- Developer.send(:with_scope, :find => { :conditions => {:name => 'David'} }) do
- Developer.send(:with_exclusive_scope, :find => { :conditions => {:name => 'Jamis'} }) do
- assert_equal 'Jamis', Developer.scoped.send(:scope_for_create)[:name]
- end
-
- assert_equal 'David', Developer.scoped.send(:scope_for_create)[:name]
- end
- end
-
- def test_with_exclusive_scope_with_relation
- assert_raise(ArgumentError) do
- Developer.all_johns
- end
- end
-
- def test_append_conditions
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
- devs = Developer.scoped
- assert_match "(name = 'David') AND (salary = 80000)", devs.to_sql
- assert_equal(1, Developer.count)
- end
- Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do
- assert_equal(0, Developer.count)
- end
- end
- end
-
- def test_merge_and_append_options
- Developer.send(:with_scope, :find => { :conditions => 'salary = 80000', :limit => 10 }) do
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- devs = Developer.scoped
- assert_match "(salary = 80000) AND (name = 'David')", devs.to_sql
- assert_equal 10, devs.taken
- end
- end
- end
-
- def test_nested_scoped_find
- Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do
- Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'David'" }) do
- assert_nothing_raised { Developer.find(1) }
- assert_equal('David', Developer.find(:first).name)
- end
- assert_equal('Jamis', Developer.find(:first).name)
- end
- end
-
- def test_nested_scoped_find_include
- Developer.send(:with_scope, :find => { :include => :projects }) do
- Developer.send(:with_scope, :find => { :conditions => "projects.id = 2" }) do
- assert_nothing_raised { Developer.find(1) }
- assert_equal('David', Developer.find(:first).name)
- end
- end
- end
-
- def test_nested_scoped_find_merged_include
- # :include's remain unique and don't "double up" when merging
- Developer.send(:with_scope, :find => { :include => :projects, :conditions => "projects.id = 2" }) do
- Developer.send(:with_scope, :find => { :include => :projects }) do
- assert_equal 1, Developer.scoped.includes_values.uniq.length
- assert_equal 'David', Developer.find(:first).name
- end
- end
-
- # the nested scope doesn't remove the first :include
- Developer.send(:with_scope, :find => { :include => :projects, :conditions => "projects.id = 2" }) do
- Developer.send(:with_scope, :find => { :include => [] }) do
- assert_equal 1, Developer.scoped.includes_values.uniq.length
- assert_equal('David', Developer.find(:first).name)
- end
- end
-
- # mixing array and symbol include's will merge correctly
- Developer.send(:with_scope, :find => { :include => [:projects], :conditions => "projects.id = 2" }) do
- Developer.send(:with_scope, :find => { :include => :projects }) do
- assert_equal 1, Developer.scoped.includes_values.uniq.length
- assert_equal('David', Developer.find(:first).name)
- end
- end
- end
-
- def test_nested_scoped_find_replace_include
- Developer.send(:with_scope, :find => { :include => :projects }) do
- Developer.send(:with_exclusive_scope, :find => { :include => [] }) do
- assert_equal 0, Developer.scoped.includes_values.length
- end
- end
- end
-
- def test_three_level_nested_exclusive_scoped_find
- Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do
- assert_equal('Jamis', Developer.find(:first).name)
-
- Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'David'" }) do
- assert_equal('David', Developer.find(:first).name)
-
- Developer.send(:with_exclusive_scope, :find => { :conditions => "name = 'Maiha'" }) do
- assert_equal(nil, Developer.find(:first))
- end
-
- # ensure that scoping is restored
- assert_equal('David', Developer.find(:first).name)
- end
-
- # ensure that scoping is restored
- assert_equal('Jamis', Developer.find(:first).name)
- end
- end
-
- def test_merged_scoped_find
- poor_jamis = developers(:poor_jamis)
- Developer.send(:with_scope, :find => { :conditions => "salary < 100000" }) do
- Developer.send(:with_scope, :find => { :offset => 1, :order => 'id asc' }) do
- # Oracle adapter does not generated space after asc therefore trailing space removed from regex
- assert_sql(/ORDER BY\s+id asc/) do
- assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
- end
- end
- end
- end
-
- def test_merged_scoped_find_sanitizes_conditions
- Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do
- Developer.send(:with_scope, :find => { :conditions => ['salary = ?', 9000] }) do
- assert_raise(ActiveRecord::RecordNotFound) { developers(:poor_jamis) }
- end
- end
- end
-
- def test_nested_scoped_find_combines_and_sanitizes_conditions
- Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do
- Developer.send(:with_exclusive_scope, :find => { :conditions => ['salary = ?', 9000] }) do
- assert_equal developers(:poor_jamis), Developer.find(:first)
- assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
- end
- end
- end
-
- def test_merged_scoped_find_combines_and_sanitizes_conditions
- Developer.send(:with_scope, :find => { :conditions => ["name = ?", 'David'] }) do
- Developer.send(:with_scope, :find => { :conditions => ['salary > ?', 9000] }) do
- assert_equal %w(David), Developer.all.map(&:name)
- end
- end
- end
-
- def test_nested_scoped_create
- comment = nil
- Comment.send(:with_scope, :create => { :post_id => 1}) do
- Comment.send(:with_scope, :create => { :post_id => 2}) do
- assert_equal({:post_id => 2}, Comment.scoped.send(:scope_for_create))
- comment = Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
- end
- end
- assert_equal 2, comment.post_id
- end
-
- def test_nested_exclusive_scope_for_create
- comment = nil
-
- Comment.send(:with_scope, :create => { :body => "Hey guys, nested scopes are broken. Please fix!" }) do
- Comment.send(:with_exclusive_scope, :create => { :post_id => 1 }) do
- assert_equal({:post_id => 1}, Comment.scoped.send(:scope_for_create))
- assert_blank Comment.new.body
- comment = Comment.create :body => "Hey guys"
- end
- end
- assert_equal 1, comment.post_id
- assert_equal 'Hey guys', comment.body
- end
-
- def test_merged_scoped_find_on_blank_conditions
- [nil, " ", [], {}].each do |blank|
- Developer.send(:with_scope, :find => {:conditions => blank}) do
- Developer.send(:with_scope, :find => {:conditions => blank}) do
- assert_nothing_raised { Developer.find(:first) }
- end
- end
- end
- end
-
- def test_merged_scoped_find_on_blank_bind_conditions
- [ [""], ["",{}] ].each do |blank|
- Developer.send(:with_scope, :find => {:conditions => blank}) do
- Developer.send(:with_scope, :find => {:conditions => blank}) do
- assert_nothing_raised { Developer.find(:first) }
- end
- end
- end
- end
-
- def test_immutable_nested_scope
- options1 = { :conditions => "name = 'Jamis'" }
- options2 = { :conditions => "name = 'David'" }
- Developer.send(:with_scope, :find => options1) do
- Developer.send(:with_exclusive_scope, :find => options2) do
- assert_equal %w(David), Developer.all.map(&:name)
- options1[:conditions] = options2[:conditions] = nil
- assert_equal %w(David), Developer.all.map(&:name)
- end
- end
- end
-
- def test_immutable_merged_scope
- options1 = { :conditions => "name = 'Jamis'" }
- options2 = { :conditions => "salary > 10000" }
- Developer.send(:with_scope, :find => options1) do
- Developer.send(:with_scope, :find => options2) do
- assert_equal %w(Jamis), Developer.all.map(&:name)
- options1[:conditions] = options2[:conditions] = nil
- assert_equal %w(Jamis), Developer.all.map(&:name)
- end
- end
- end
-
- def test_ensure_that_method_scoping_is_correctly_restored
- Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
- begin
- Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do
- raise "an exception"
- end
- rescue
- end
-
- assert Developer.scoped.where_values.include?("name = 'David'")
- assert !Developer.scoped.where_values.include?("name = 'Maiha'")
- end
- end
-
- def test_nested_scoped_find_merges_old_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id' }) do
- Author.send(:with_scope, :find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1')
- end
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_nested_scoped_find_merges_new_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do
- Author.send(:with_scope, :find => { :joins => :comments }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1')
- end
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-
- def test_nested_scoped_find_merges_new_and_old_style_joins
- scoped_authors = Author.send(:with_scope, :find => { :joins => :posts }) do
- Author.send(:with_scope, :find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do
- Author.find(:all, :select => 'DISTINCT authors.*', :joins => '', :conditions => 'comments.id = 1')
- end
- end
- assert scoped_authors.include?(authors(:david))
- assert !scoped_authors.include?(authors(:mary))
- assert_equal 1, scoped_authors.size
- assert_equal authors(:david).attributes, scoped_authors.first.attributes
- end
-end
diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb
new file mode 100644
index 0000000000..ab61a4dcef
--- /dev/null
+++ b/activerecord/test/cases/migration/change_schema_test.rb
@@ -0,0 +1,324 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class ChangeSchemaTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @table_name = :testings
+ end
+
+ def teardown
+ super
+ connection.drop_table :testings rescue nil
+ ActiveRecord::Base.primary_key_prefix_type = nil
+ end
+
+ def test_create_table_without_id
+ testing_table_with_only_foo_attribute do
+ assert_equal connection.columns(:testings).size, 1
+ end
+ end
+
+ def test_add_column_with_primary_key_attribute
+ testing_table_with_only_foo_attribute do
+ connection.add_column :testings, :id, :primary_key
+ assert_equal connection.columns(:testings).size, 2
+ end
+ end
+
+ def test_create_table_adds_id
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert_equal %w(foo id), connection.columns(:testings).map(&:name).sort
+ end
+
+ def test_create_table_with_not_null_column
+ connection.create_table :testings do |t|
+ t.column :foo, :string, :null => false
+ end
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ connection.execute "insert into testings (foo) values (NULL)"
+ end
+ end
+
+ def test_create_table_with_defaults
+ # MySQL doesn't allow defaults on TEXT or BLOB columns.
+ mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
+
+ connection.create_table :testings do |t|
+ t.column :one, :string, :default => "hello"
+ t.column :two, :boolean, :default => true
+ t.column :three, :boolean, :default => false
+ t.column :four, :integer, :default => 1
+ t.column :five, :text, :default => "hello" unless mysql
+ end
+
+ columns = connection.columns(:testings)
+ one = columns.detect { |c| c.name == "one" }
+ two = columns.detect { |c| c.name == "two" }
+ three = columns.detect { |c| c.name == "three" }
+ four = columns.detect { |c| c.name == "four" }
+ five = columns.detect { |c| c.name == "five" } unless mysql
+
+ assert_equal "hello", one.default
+ assert_equal true, two.default
+ assert_equal false, three.default
+ assert_equal 1, four.default
+ assert_equal "hello", five.default unless mysql
+ end
+
+ def test_create_table_with_limits
+ connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 255
+
+ t.column :default_int, :integer
+
+ t.column :one_int, :integer, :limit => 1
+ t.column :four_int, :integer, :limit => 4
+ t.column :eight_int, :integer, :limit => 8
+ end
+
+ columns = connection.columns(:testings)
+ foo = columns.detect { |c| c.name == "foo" }
+ assert_equal 255, foo.limit
+
+ default = columns.detect { |c| c.name == "default_int" }
+ one = columns.detect { |c| c.name == "one_int" }
+ four = columns.detect { |c| c.name == "four_int" }
+ eight = columns.detect { |c| c.name == "eight_int" }
+
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal 'integer', default.sql_type
+ assert_equal 'smallint', one.sql_type
+ assert_equal 'integer', four.sql_type
+ assert_equal 'bigint', eight.sql_type
+ elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ assert_match 'int(11)', default.sql_type
+ assert_match 'tinyint', one.sql_type
+ assert_match 'int', four.sql_type
+ assert_match 'bigint', eight.sql_type
+ elsif current_adapter?(:OracleAdapter)
+ assert_equal 'NUMBER(38)', default.sql_type
+ assert_equal 'NUMBER(1)', one.sql_type
+ assert_equal 'NUMBER(4)', four.sql_type
+ assert_equal 'NUMBER(8)', eight.sql_type
+ end
+ end
+
+ def test_create_table_with_primary_key_prefix_as_table_name_with_underscore
+ ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
+
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert_equal %w(foo testing_id), connection.columns(:testings).map(&:name).sort
+ end
+
+ def test_create_table_with_primary_key_prefix_as_table_name
+ ActiveRecord::Base.primary_key_prefix_type = :table_name
+
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert_equal %w(foo testingid), connection.columns(:testings).map(&:name).sort
+ end
+
+ def test_create_table_with_timestamps_should_create_datetime_columns
+ connection.create_table table_name do |t|
+ t.timestamps
+ end
+ created_columns = connection.columns(table_name)
+
+ created_at_column = created_columns.detect {|c| c.name == 'created_at' }
+ updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
+
+ assert !created_at_column.null
+ assert !updated_at_column.null
+ end
+
+ def test_create_table_with_timestamps_should_create_datetime_columns_with_options
+ connection.create_table table_name do |t|
+ t.timestamps :null => false
+ end
+ created_columns = connection.columns(table_name)
+
+ created_at_column = created_columns.detect {|c| c.name == 'created_at' }
+ updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
+
+ assert !created_at_column.null
+ assert !updated_at_column.null
+ end
+
+ def test_create_table_without_a_block
+ connection.create_table table_name
+ end
+
+ def test_add_column_not_null_without_default
+ # Sybase, and SQLite3 will not allow you to add a NOT NULL
+ # column to a table without a default value.
+ if current_adapter?(:SybaseAdapter, :SQLite3Adapter)
+ skip "not supported on #{connection.class}"
+ end
+
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+ connection.add_column :testings, :bar, :string, :null => false
+
+ assert_raise(ActiveRecord::StatementInvalid) do
+ connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
+ end
+ end
+
+ def test_add_column_not_null_with_default
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ con = connection
+ connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)
+ connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
+ connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)
+ assert_nothing_raised {connection.add_column :testings, :bar, :string, :null => false, :default => "default" }
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ unless current_adapter?(:OpenBaseAdapter)
+ connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)"
+ else
+ connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)",
+ "Testing Insert","id",2)
+ end
+ end
+ end
+
+ def test_change_column_quotes_column_names
+ connection.create_table :testings do |t|
+ t.column :select, :string
+ end
+
+ connection.change_column :testings, :select, :string, :limit => 10
+
+ # Oracle needs primary key value from sequence
+ if current_adapter?(:OracleAdapter)
+ connection.execute "insert into testings (id, #{connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')"
+ else
+ connection.execute "insert into testings (#{connection.quote_column_name('select')}) values ('7 chars')"
+ end
+ end
+
+ def test_keeping_default_and_notnull_constaint_on_change
+ connection.create_table :testings do |t|
+ t.column :title, :string
+ end
+ person_klass = Class.new(ActiveRecord::Base)
+ person_klass.table_name = 'testings'
+
+ person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99
+ person_klass.reset_column_information
+ assert_equal 99, person_klass.columns_hash["wealth"].default
+ assert_equal false, person_klass.columns_hash["wealth"].null
+ # Oracle needs primary key value from sequence
+ if current_adapter?(:OracleAdapter)
+ assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")}
+ else
+ assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
+ end
+
+ # change column default to see that column doesn't lose its not null definition
+ person_klass.connection.change_column_default "testings", "wealth", 100
+ person_klass.reset_column_information
+ assert_equal 100, person_klass.columns_hash["wealth"].default
+ assert_equal false, person_klass.columns_hash["wealth"].null
+
+ # rename column to see that column doesn't lose its not null and/or default definition
+ person_klass.connection.rename_column "testings", "wealth", "money"
+ person_klass.reset_column_information
+ assert_nil person_klass.columns_hash["wealth"]
+ assert_equal 100, person_klass.columns_hash["money"].default
+ assert_equal false, person_klass.columns_hash["money"].null
+
+ # change column
+ person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000
+ person_klass.reset_column_information
+ assert_equal 1000, person_klass.columns_hash["money"].default
+ assert_equal false, person_klass.columns_hash["money"].null
+
+ # change column, make it nullable and clear default
+ person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil
+ person_klass.reset_column_information
+ assert_nil person_klass.columns_hash["money"].default
+ assert_equal true, person_klass.columns_hash["money"].null
+
+ # change_column_null, make it not nullable and set null values to a default value
+ person_klass.connection.execute('UPDATE testings SET money = NULL')
+ person_klass.connection.change_column_null "testings", "money", false, 2000
+ person_klass.reset_column_information
+ assert_nil person_klass.columns_hash["money"].default
+ assert_equal false, person_klass.columns_hash["money"].null
+ assert_equal 2000, connection.select_values("SELECT money FROM testings").first.to_i
+ end
+
+ def test_column_exists
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert connection.column_exists?(:testings, :foo)
+ refute connection.column_exists?(:testings, :bar)
+ end
+
+ def test_column_exists_with_type
+ connection.create_table :testings do |t|
+ t.column :foo, :string
+ t.column :bar, :decimal, :precision => 8, :scale => 2
+ end
+
+ assert connection.column_exists?(:testings, :foo, :string)
+ refute connection.column_exists?(:testings, :foo, :integer)
+
+ assert connection.column_exists?(:testings, :bar, :decimal)
+ refute connection.column_exists?(:testings, :bar, :integer)
+ end
+
+ def test_column_exists_with_definition
+ connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 100
+ t.column :bar, :decimal, :precision => 8, :scale => 2
+ end
+
+ assert connection.column_exists?(:testings, :foo, :string, :limit => 100)
+ refute connection.column_exists?(:testings, :foo, :string, :limit => 50)
+ assert connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2)
+ refute connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2)
+ end
+
+ def test_column_exists_on_table_with_no_options_parameter_supplied
+ connection.create_table :testings do |t|
+ t.string :foo
+ end
+ connection.change_table :testings do |t|
+ assert t.column_exists?(:foo)
+ assert !(t.column_exists?(:bar))
+ end
+ end
+
+ private
+ def testing_table_with_only_foo_attribute
+ connection.create_table :testings, :id => false do |t|
+ t.column :foo, :string
+ end
+
+ yield
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb
new file mode 100644
index 0000000000..063209389f
--- /dev/null
+++ b/activerecord/test/cases/migration/change_table_test.rb
@@ -0,0 +1,221 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class TableTest < ActiveRecord::TestCase
+ class MockConnection < MiniTest::Mock
+ def native_database_types
+ {
+ :string => 'varchar(255)',
+ :integer => 'integer',
+ }
+ end
+
+ def type_to_sql(type, limit, precision, scale)
+ native_database_types[type]
+ end
+ end
+
+ def setup
+ @connection = MockConnection.new
+ end
+
+ def teardown
+ assert @connection.verify
+ end
+
+ def with_change_table
+ yield ConnectionAdapters::Table.new(:delete_me, @connection)
+ end
+
+ def test_references_column_type_adds_id
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}]
+ t.references :customer
+ end
+ end
+
+ def test_remove_references_column_type_removes_id
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'customer_id']
+ t.remove_references :customer
+ end
+ end
+
+ def test_add_belongs_to_works_like_add_references
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'customer_id', :integer, {}]
+ t.belongs_to :customer
+ end
+ end
+
+ def test_remove_belongs_to_works_like_remove_references
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'customer_id']
+ t.remove_belongs_to :customer
+ end
+ end
+
+ def test_references_column_type_with_polymorphic_adds_type
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {}]
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {}]
+ t.references :taggable, :polymorphic => true
+ end
+ end
+
+ def test_remove_references_column_type_with_polymorphic_removes_type
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_id']
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_type']
+ t.remove_references :taggable, :polymorphic => true
+ end
+ end
+
+ def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_id', :integer, {:null => false}]
+ @connection.expect :add_column, nil, [:delete_me, 'taggable_type', :string, {:null => false}]
+ t.references :taggable, :polymorphic => true, :null => false
+ end
+ end
+
+ def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_id']
+ @connection.expect :remove_column, nil, [:delete_me, 'taggable_type']
+ t.remove_references :taggable, :polymorphic => true, :null => false
+ end
+ end
+
+ def test_timestamps_creates_updated_at_and_created_at
+ with_change_table do |t|
+ @connection.expect :add_timestamps, nil, [:delete_me]
+ t.timestamps
+ end
+ end
+
+ def test_remove_timestamps_creates_updated_at_and_created_at
+ with_change_table do |t|
+ @connection.expect :remove_timestamps, nil, [:delete_me]
+ t.remove_timestamps
+ end
+ end
+
+ def string_column
+ @connection.native_database_types[:string]
+ end
+
+ def integer_column
+ @connection.native_database_types[:integer]
+ end
+
+ def test_integer_creates_integer_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :foo, integer_column, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, integer_column, {}]
+ t.integer :foo, :bar
+ end
+ end
+
+ def test_string_creates_string_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :foo, string_column, {}]
+ @connection.expect :add_column, nil, [:delete_me, :bar, string_column, {}]
+ t.string :foo, :bar
+ end
+ end
+
+ def test_column_creates_column
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}]
+ t.column :bar, :integer
+ end
+ end
+
+ def test_column_creates_column_with_options
+ with_change_table do |t|
+ @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {:null => false}]
+ t.column :bar, :integer, :null => false
+ end
+ end
+
+ def test_index_creates_index
+ with_change_table do |t|
+ @connection.expect :add_index, nil, [:delete_me, :bar, {}]
+ t.index :bar
+ end
+ end
+
+ def test_index_creates_index_with_options
+ with_change_table do |t|
+ @connection.expect :add_index, nil, [:delete_me, :bar, {:unique => true}]
+ t.index :bar, :unique => true
+ end
+ end
+
+ def test_index_exists
+ with_change_table do |t|
+ @connection.expect :index_exists?, nil, [:delete_me, :bar, {}]
+ t.index_exists?(:bar)
+ end
+ end
+
+ def test_index_exists_with_options
+ with_change_table do |t|
+ @connection.expect :index_exists?, nil, [:delete_me, :bar, {:unique => true}]
+ t.index_exists?(:bar, :unique => true)
+ end
+ end
+
+ def test_change_changes_column
+ with_change_table do |t|
+ @connection.expect :change_column, nil, [:delete_me, :bar, :string, {}]
+ t.change :bar, :string
+ end
+ end
+
+ def test_change_changes_column_with_options
+ with_change_table do |t|
+ @connection.expect :change_column, nil, [:delete_me, :bar, :string, {:null => true}]
+ t.change :bar, :string, :null => true
+ end
+ end
+
+ def test_change_default_changes_column
+ with_change_table do |t|
+ @connection.expect :change_column_default, nil, [:delete_me, :bar, :string]
+ t.change_default :bar, :string
+ end
+ end
+
+ def test_remove_drops_single_column
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, :bar]
+ t.remove :bar
+ end
+ end
+
+ def test_remove_drops_multiple_columns
+ with_change_table do |t|
+ @connection.expect :remove_column, nil, [:delete_me, :bar, :baz]
+ t.remove :bar, :baz
+ end
+ end
+
+ def test_remove_index_removes_index_with_options
+ with_change_table do |t|
+ @connection.expect :remove_index, nil, [:delete_me, {:unique => true}]
+ t.remove_index :unique => true
+ end
+ end
+
+ def test_rename_renames_column
+ with_change_table do |t|
+ @connection.expect :rename_column, nil, [:delete_me, :bar, :baz]
+ t.rename :bar, :baz
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/column_attributes_test.rb b/activerecord/test/cases/migration/column_attributes_test.rb
new file mode 100644
index 0000000000..18f8d82bfe
--- /dev/null
+++ b/activerecord/test/cases/migration/column_attributes_test.rb
@@ -0,0 +1,198 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class ColumnAttributesTest < ActiveRecord::TestCase
+ include ActiveRecord::Migration::TestHelper
+
+ self.use_transactional_fixtures = false
+
+ def test_add_remove_single_field_using_string_arguments
+ refute TestModel.column_methods_hash.key?(:last_name)
+
+ add_column 'test_models', 'last_name', :string
+
+ TestModel.reset_column_information
+
+ assert TestModel.column_methods_hash.key?(:last_name)
+
+ remove_column 'test_models', 'last_name'
+
+ TestModel.reset_column_information
+ refute TestModel.column_methods_hash.key?(:last_name)
+ end
+
+ def test_add_remove_single_field_using_symbol_arguments
+ refute TestModel.column_methods_hash.key?(:last_name)
+
+ add_column :test_models, :last_name, :string
+
+ TestModel.reset_column_information
+ assert TestModel.column_methods_hash.key?(:last_name)
+
+ remove_column :test_models, :last_name
+
+ TestModel.reset_column_information
+ refute TestModel.column_methods_hash.key?(:last_name)
+ end
+
+ def test_unabstracted_database_dependent_types
+ skip "not supported" unless current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+
+ add_column :test_models, :intelligence_quotient, :tinyint
+ TestModel.reset_column_information
+ assert_match(/tinyint/, TestModel.columns_hash['intelligence_quotient'].sql_type)
+ end
+
+ # We specifically do a manual INSERT here, and then test only the SELECT
+ # functionality. This allows us to more easily catch INSERT being broken,
+ # but SELECT actually working fine.
+ def test_native_decimal_insert_manual_vs_automatic
+ correct_value = '0012345678901234567890.0123456789'.to_d
+
+ connection.add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10'
+
+ # Do a manual insertion
+ if current_adapter?(:OracleAdapter)
+ connection.execute "insert into test_models (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, sysdate, sysdate)"
+ elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings
+ connection.execute "insert into test_models (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)"
+ elsif current_adapter?(:PostgreSQLAdapter)
+ connection.execute "insert into test_models (wealth, created_at, updated_at) values (12345678901234567890.0123456789, now(), now())"
+ else
+ connection.execute "insert into test_models (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)"
+ end
+
+ # SELECT
+ row = TestModel.first
+ assert_kind_of BigDecimal, row.wealth
+
+ # If this assert fails, that means the SELECT is broken!
+ unless current_adapter?(:SQLite3Adapter)
+ assert_equal correct_value, row.wealth
+ end
+
+ # Reset to old state
+ TestModel.delete_all
+
+ # Now use the Rails insertion
+ TestModel.create :wealth => BigDecimal.new("12345678901234567890.0123456789")
+
+ # SELECT
+ row = TestModel.first
+ assert_kind_of BigDecimal, row.wealth
+
+ # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
+ unless current_adapter?(:SQLite3Adapter)
+ assert_equal correct_value, row.wealth
+ end
+ end
+
+ def test_add_column_with_precision_and_scale
+ connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7
+
+ wealth_column = TestModel.columns_hash['wealth']
+ assert_equal 9, wealth_column.precision
+ assert_equal 7, wealth_column.scale
+ end
+
+ def test_change_column_preserve_other_column_precision_and_scale
+ skip "only on sqlite3" unless current_adapter?(:SQLite3Adapter)
+
+ connection.add_column 'test_models', 'last_name', :string
+ connection.add_column 'test_models', 'wealth', :decimal, :precision => 9, :scale => 7
+
+ wealth_column = TestModel.columns_hash['wealth']
+ assert_equal 9, wealth_column.precision
+ assert_equal 7, wealth_column.scale
+
+ connection.change_column 'test_models', 'last_name', :string, :null => false
+ TestModel.reset_column_information
+
+ wealth_column = TestModel.columns_hash['wealth']
+ assert_equal 9, wealth_column.precision
+ assert_equal 7, wealth_column.scale
+ end
+
+ def test_native_types
+ add_column "test_models", "first_name", :string
+ add_column "test_models", "last_name", :string
+ add_column "test_models", "bio", :text
+ add_column "test_models", "age", :integer
+ add_column "test_models", "height", :float
+ add_column "test_models", "wealth", :decimal, :precision => '30', :scale => '10'
+ add_column "test_models", "birthday", :datetime
+ add_column "test_models", "favorite_day", :date
+ add_column "test_models", "moment_of_truth", :datetime
+ add_column "test_models", "male", :boolean
+
+ TestModel.create :first_name => 'bob', :last_name => 'bobsen',
+ :bio => "I was born ....", :age => 18, :height => 1.78,
+ :wealth => BigDecimal.new("12345678901234567890.0123456789"),
+ :birthday => 18.years.ago, :favorite_day => 10.days.ago,
+ :moment_of_truth => "1782-10-10 21:40:18", :male => true
+
+ bob = TestModel.first
+ assert_equal 'bob', bob.first_name
+ assert_equal 'bobsen', bob.last_name
+ assert_equal "I was born ....", bob.bio
+ assert_equal 18, bob.age
+
+ # Test for 30 significant digits (beyond the 16 of float), 10 of them
+ # after the decimal place.
+
+ unless current_adapter?(:SQLite3Adapter)
+ assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
+ end
+
+ assert_equal true, bob.male?
+
+ assert_equal String, bob.first_name.class
+ assert_equal String, bob.last_name.class
+ assert_equal String, bob.bio.class
+ assert_equal Fixnum, bob.age.class
+ assert_equal Time, bob.birthday.class
+
+ if current_adapter?(:OracleAdapter, :SybaseAdapter)
+ # Sybase, and Oracle don't differentiate between date/time
+ assert_equal Time, bob.favorite_day.class
+ else
+ assert_equal Date, bob.favorite_day.class
+ end
+
+ # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column
+ # therefore no timezone change is done afterwards when default timezone is changed
+ unless current_adapter?(:OracleAdapter)
+ # Test DateTime column and defaults, including timezone.
+ # FIXME: moment of truth may be Time on 64-bit platforms.
+ if bob.moment_of_truth.is_a?(DateTime)
+
+ with_env_tz 'US/Eastern' do
+ bob.reload
+ assert_equal DateTime.local_offset, bob.moment_of_truth.offset
+ assert_not_equal 0, bob.moment_of_truth.offset
+ assert_not_equal "Z", bob.moment_of_truth.zone
+ # US/Eastern is -5 hours from GMT
+ assert_equal Rational(-5, 24), bob.moment_of_truth.offset
+ assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM
+ assert_equal DateTime::ITALY, bob.moment_of_truth.start
+ end
+ end
+ end
+
+ assert_instance_of TrueClass, bob.male?
+ assert_kind_of BigDecimal, bob.wealth
+ end
+
+ def test_out_of_range_limit_should_raise
+ skip("MySQL and PostgreSQL only") unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
+
+ assert_raise(ActiveRecordError) { add_column :test_models, :integer_too_big, :integer, :limit => 10 }
+
+ unless current_adapter?(:PostgreSQLAdapter)
+ assert_raise(ActiveRecordError) { add_column :test_models, :text_too_big, :integer, :limit => 0xfffffffff }
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb
new file mode 100644
index 0000000000..913d935f7c
--- /dev/null
+++ b/activerecord/test/cases/migration/column_positioning_test.rb
@@ -0,0 +1,60 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class ColumnPositioningTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+ alias :conn :connection
+
+ def setup
+ super
+
+ unless current_adapter?(:MysqlAdapter, :Mysql2Adapter)
+ skip "not supported on #{connection.class}"
+ end
+
+ @connection = ActiveRecord::Base.connection
+
+ connection.create_table :testings, :id => false do |t|
+ t.column :first, :integer
+ t.column :second, :integer
+ t.column :third, :integer
+ end
+ end
+
+ def teardown
+ super
+ connection.drop_table :testings rescue nil
+ ActiveRecord::Base.primary_key_prefix_type = nil
+ end
+
+ def test_column_positioning
+ assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name }
+ end
+
+ def test_add_column_with_positioning
+ conn.add_column :testings, :new_col, :integer
+ assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name }
+ end
+
+ def test_add_column_with_positioning_first
+ conn.add_column :testings, :new_col, :integer, :first => true
+ assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name }
+ end
+
+ def test_add_column_with_positioning_after
+ conn.add_column :testings, :new_col, :integer, :after => :first
+ assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name }
+ end
+
+ def test_change_column_with_positioning
+ conn.change_column :testings, :second, :integer, :first => true
+ assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name }
+
+ conn.change_column :testings, :second, :integer, :after => :third
+ assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name }
+ end
+
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb
index d108b456f0..7d026961be 100644
--- a/activerecord/test/cases/migration/command_recorder_test.rb
+++ b/activerecord/test/cases/migration/command_recorder_test.rb
@@ -67,6 +67,24 @@ module ActiveRecord
assert_equal [:drop_table, [:system_settings]], drop_table
end
+ def test_invert_create_table_with_options
+ @recorder.record :create_table, [:people_reminders, {:id => false}]
+ drop_table = @recorder.inverse.first
+ assert_equal [:drop_table, [:people_reminders]], drop_table
+ end
+
+ def test_invert_create_join_table
+ @recorder.record :create_join_table, [:musics, :artists]
+ drop_table = @recorder.inverse.first
+ assert_equal [:drop_table, [:artists_musics]], drop_table
+ end
+
+ def test_invert_create_join_table_with_table_name
+ @recorder.record :create_join_table, [:musics, :artists, {:table_name => :catalog}]
+ drop_table = @recorder.inverse.first
+ assert_equal [:drop_table, [:catalog]], drop_table
+ end
+
def test_invert_rename_table
@recorder.record :rename_table, [:old, :new]
rename = @recorder.inverse.first
diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb
new file mode 100644
index 0000000000..0428d9ba76
--- /dev/null
+++ b/activerecord/test/cases/migration/create_join_table_test.rb
@@ -0,0 +1,70 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class CreateJoinTableTest < ActiveRecord::TestCase
+ attr_reader :connection
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_create_join_table
+ connection.create_join_table :artists, :musics
+
+ assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort
+ ensure
+ connection.drop_table :artists_musics
+ end
+
+ def test_create_join_table_set_not_null_by_default
+ connection.create_join_table :artists, :musics
+
+ assert_equal [false, false], connection.columns(:artists_musics).map(&:null)
+ ensure
+ connection.drop_table :artists_musics
+ end
+
+ def test_create_join_table_with_strings
+ connection.create_join_table 'artists', 'musics'
+
+ assert_equal %w(artist_id music_id), connection.columns(:artists_musics).map(&:name).sort
+ ensure
+ connection.drop_table :artists_musics
+ end
+
+ def test_create_join_table_with_the_proper_order
+ connection.create_join_table :videos, :musics
+
+ assert_equal %w(music_id video_id), connection.columns(:musics_videos).map(&:name).sort
+ ensure
+ connection.drop_table :musics_videos
+ end
+
+ def test_create_join_table_with_the_table_name
+ connection.create_join_table :artists, :musics, :table_name => :catalog
+
+ assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort
+ ensure
+ connection.drop_table :catalog
+ end
+
+ def test_create_join_table_with_the_table_name_as_string
+ connection.create_join_table :artists, :musics, :table_name => 'catalog'
+
+ assert_equal %w(artist_id music_id), connection.columns(:catalog).map(&:name).sort
+ ensure
+ connection.drop_table :catalog
+ end
+
+ def test_create_join_table_with_column_options
+ connection.create_join_table :artists, :musics, :column_options => {:null => true}
+
+ assert_equal [true, true], connection.columns(:artists_musics).map(&:null)
+ ensure
+ connection.drop_table :artists_musics
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb
new file mode 100644
index 0000000000..fe53510ba2
--- /dev/null
+++ b/activerecord/test/cases/migration/helper.rb
@@ -0,0 +1,64 @@
+require "cases/helper"
+
+module ActiveRecord
+ class Migration
+ class << self
+ attr_accessor :message_count
+ end
+
+ def puts(text="")
+ ActiveRecord::Migration.message_count ||= 0
+ ActiveRecord::Migration.message_count += 1
+ end
+
+ module TestHelper
+ attr_reader :connection, :table_name
+
+ class TestModel < ActiveRecord::Base
+ self.table_name = 'test_models'
+ end
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ connection.create_table :test_models do |t|
+ t.timestamps
+ end
+
+ TestModel.reset_column_information
+ end
+
+ def teardown
+ super
+ TestModel.reset_table_name
+ TestModel.reset_sequence_name
+ connection.drop_table :test_models rescue nil
+ end
+
+ private
+ def add_column(*args)
+ connection.add_column(*args)
+ end
+
+ def remove_column(*args)
+ connection.remove_column(*args)
+ end
+
+ def rename_column(*args)
+ connection.rename_column(*args)
+ end
+
+ def add_index(*args)
+ connection.add_index(*args)
+ end
+
+ def change_column(*args)
+ connection.change_column(*args)
+ end
+
+ def rename_table(*args)
+ connection.rename_table(*args)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb
new file mode 100644
index 0000000000..dd9492924c
--- /dev/null
+++ b/activerecord/test/cases/migration/index_test.rb
@@ -0,0 +1,185 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class IndexTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @table_name = :testings
+
+ connection.create_table table_name do |t|
+ t.column :foo, :string, :limit => 100
+ t.column :bar, :string, :limit => 100
+
+ t.string :first_name
+ t.string :last_name, :limit => 100
+ t.string :key, :limit => 100
+ t.boolean :administrator
+ end
+ end
+
+ def teardown
+ super
+ connection.drop_table :testings rescue nil
+ ActiveRecord::Base.primary_key_prefix_type = nil
+ end
+
+ def test_rename_index
+ skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter)
+
+ # keep the names short to make Oracle and similar behave
+ connection.add_index(table_name, [:foo], :name => 'old_idx')
+ connection.rename_index(table_name, 'old_idx', 'new_idx')
+
+ # if the adapter doesn't support the indexes call, pick defaults that let the test pass
+ refute connection.index_name_exists?(table_name, 'old_idx', false)
+ assert connection.index_name_exists?(table_name, 'new_idx', true)
+ end
+
+ def test_double_add_index
+ skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter)
+
+ connection.add_index(table_name, [:foo], :name => 'some_idx')
+ assert_raises(ArgumentError) {
+ connection.add_index(table_name, [:foo], :name => 'some_idx')
+ }
+ end
+
+ def test_remove_nonexistent_index
+ skip "not supported on openbase" if current_adapter?(:OpenBaseAdapter)
+
+ # we do this by name, so OpenBase is a wash as noted above
+ assert_raise(ArgumentError) { connection.remove_index(table_name, "no_such_index") }
+ end
+
+ def test_add_index_name_length_limit
+ good_index_name = 'x' * connection.index_name_length
+ too_long_index_name = good_index_name + 'x'
+
+ assert_raises(ArgumentError) {
+ connection.add_index(table_name, "foo", :name => too_long_index_name)
+ }
+
+ refute connection.index_name_exists?(table_name, too_long_index_name, false)
+ connection.add_index(table_name, "foo", :name => good_index_name)
+
+ assert connection.index_name_exists?(table_name, good_index_name, false)
+ connection.remove_index(table_name, :name => good_index_name)
+ end
+
+ def test_index_symbol_names
+ connection.add_index table_name, :foo, :name => :symbol_index_name
+ assert connection.index_exists?(table_name, :foo, :name => :symbol_index_name)
+
+ connection.remove_index table_name, :name => :symbol_index_name
+ refute connection.index_exists?(table_name, :foo, :name => :symbol_index_name)
+ end
+
+ def test_index_exists
+ connection.add_index :testings, :foo
+
+ assert connection.index_exists?(:testings, :foo)
+ assert !connection.index_exists?(:testings, :bar)
+ end
+
+ def test_index_exists_on_multiple_columns
+ connection.add_index :testings, [:foo, :bar]
+
+ assert connection.index_exists?(:testings, [:foo, :bar])
+ end
+
+ def test_unique_index_exists
+ connection.add_index :testings, :foo, :unique => true
+
+ assert connection.index_exists?(:testings, :foo, :unique => true)
+ end
+
+ def test_named_index_exists
+ connection.add_index :testings, :foo, :name => "custom_index_name"
+
+ assert connection.index_exists?(:testings, :foo, :name => "custom_index_name")
+ end
+
+ def test_add_index_attribute_length_limit
+ connection.add_index :testings, [:foo, :bar], :length => {:foo => 10, :bar => nil}
+
+ assert connection.index_exists?(:testings, [:foo, :bar])
+ end
+
+ def test_add_index
+ connection.add_index("testings", "last_name")
+ connection.remove_index("testings", "last_name")
+
+ # Orcl nds shrt indx nms. Sybs 2.
+ # OpenBase does not have named indexes. You must specify a single column name
+ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", :column => ["last_name", "first_name"])
+
+ # Oracle adapter cannot have specified index name larger than 30 characters
+ # Oracle adapter is shortening index name when just column list is given
+ unless current_adapter?(:OracleAdapter)
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", :name => :index_testings_on_last_name_and_first_name)
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", "last_name_and_first_name")
+ end
+ connection.add_index("testings", ["last_name", "first_name"])
+ connection.remove_index("testings", ["last_name", "first_name"])
+
+ connection.add_index("testings", ["last_name"], :length => 10)
+ connection.remove_index("testings", "last_name")
+
+ connection.add_index("testings", ["last_name"], :length => {:last_name => 10})
+ connection.remove_index("testings", ["last_name"])
+
+ connection.add_index("testings", ["last_name", "first_name"], :length => 10)
+ connection.remove_index("testings", ["last_name", "first_name"])
+
+ connection.add_index("testings", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20})
+ connection.remove_index("testings", ["last_name", "first_name"])
+ end
+
+ # quoting
+ # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word
+ # OpenBase does not have named indexes. You must specify a single column name
+ unless current_adapter?(:OpenBaseAdapter)
+ connection.add_index("testings", ["key"], :name => "key_idx", :unique => true)
+ connection.remove_index("testings", :name => "key_idx", :unique => true)
+ end
+
+ # Sybase adapter does not support indexes on :boolean columns
+ # OpenBase does not have named indexes. You must specify a single column
+ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
+ connection.add_index("testings", %w(last_name first_name administrator), :name => "named_admin")
+ connection.remove_index("testings", :name => "named_admin")
+ end
+
+ # Selected adapters support index sort order
+ if current_adapter?(:SQLite3Adapter, :MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
+ connection.add_index("testings", ["last_name"], :order => {:last_name => :desc})
+ connection.remove_index("testings", ["last_name"])
+ connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc})
+ connection.remove_index("testings", ["last_name", "first_name"])
+ connection.add_index("testings", ["last_name", "first_name"], :order => {:last_name => :desc, :first_name => :asc})
+ connection.remove_index("testings", ["last_name", "first_name"])
+ connection.add_index("testings", ["last_name", "first_name"], :order => :desc)
+ connection.remove_index("testings", ["last_name", "first_name"])
+ end
+ end
+
+ def test_add_partial_index
+ skip 'only on pg' unless current_adapter?(:PostgreSQLAdapter)
+
+ connection.add_index("testings", "last_name", :where => "first_name = 'john doe'")
+ assert connection.index_exists?("testings", "last_name")
+
+ connection.remove_index("testings", "last_name")
+ assert !connection.index_exists?("testings", "last_name")
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb
new file mode 100644
index 0000000000..ee0c20747e
--- /dev/null
+++ b/activerecord/test/cases/migration/logger_test.rb
@@ -0,0 +1,37 @@
+require "cases/helper"
+
+module ActiveRecord
+ class Migration
+ class LoggerTest < ActiveRecord::TestCase
+ # mysql can't roll back ddl changes
+ self.use_transactional_fixtures = false
+
+ Migration = Struct.new(:name, :version) do
+ def migrate direction
+ # do nothing
+ end
+ end
+
+ def setup
+ super
+ ActiveRecord::SchemaMigration.create_table
+ ActiveRecord::SchemaMigration.delete_all
+ end
+
+ def teardown
+ super
+ ActiveRecord::SchemaMigration.drop_table
+ end
+
+ def test_migration_should_be_run_without_logger
+ previous_logger = ActiveRecord::Base.logger
+ ActiveRecord::Base.logger = nil
+ migrations = [Migration.new('a', 1), Migration.new('b', 2), Migration.new('c', 3)]
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ ensure
+ ActiveRecord::Base.logger = previous_logger
+ end
+ end
+ end
+end
+
diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb
new file mode 100644
index 0000000000..8ab1c59724
--- /dev/null
+++ b/activerecord/test/cases/migration/references_index_test.rb
@@ -0,0 +1,99 @@
+require 'cases/helper'
+
+module ActiveRecord
+ class Migration
+ class ReferencesIndexTest < ActiveRecord::TestCase
+ attr_reader :connection, :table_name
+
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ @table_name = :testings
+ end
+
+ def teardown
+ super
+ connection.drop_table :testings rescue nil
+ end
+
+ def test_creates_index
+ connection.create_table table_name do |t|
+ t.references :foo, :index => true
+ end
+
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index
+ connection.create_table table_name do |t|
+ t.references :foo
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index_explicit
+ connection.create_table table_name do |t|
+ t.references :foo, :index => false
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_creates_index_with_options
+ connection.create_table table_name do |t|
+ t.references :foo, :index => {:name => :index_testings_on_yo_momma}
+ t.references :bar, :index => {:unique => true}
+ end
+
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_yo_momma)
+ assert connection.index_exists?(table_name, :bar_id, :name => :index_testings_on_bar_id, :unique => true)
+ end
+
+ def test_creates_polymorphic_index
+ connection.create_table table_name do |t|
+ t.references :foo, :polymorphic => true, :index => true
+ end
+
+ assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type)
+ end
+
+ def test_creates_index_for_existing_table
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo, :index => true
+ end
+
+ assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index_for_existing_table
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_does_not_create_index_for_existing_table_explicit
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo, :index => false
+ end
+
+ refute connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_foo_id)
+ end
+
+ def test_creates_polymorphic_index_for_existing_table
+ connection.create_table table_name
+ connection.change_table table_name do |t|
+ t.references :foo, :polymorphic => true, :index => true
+ end
+
+ assert connection.index_exists?(table_name, [:foo_id, :foo_type], :name => :index_testings_on_foo_id_and_foo_type)
+ end
+
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/rename_column_test.rb b/activerecord/test/cases/migration/rename_column_test.rb
new file mode 100644
index 0000000000..d1a85ee5e4
--- /dev/null
+++ b/activerecord/test/cases/migration/rename_column_test.rb
@@ -0,0 +1,194 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class RenameColumnTest < ActiveRecord::TestCase
+ include ActiveRecord::Migration::TestHelper
+
+ self.use_transactional_fixtures = false
+
+ # FIXME: this is more of an integration test with AR::Base and the
+ # schema modifications. Maybe we should move this?
+ def test_add_rename
+ add_column "test_models", "girlfriend", :string
+ TestModel.reset_column_information
+
+ TestModel.create :girlfriend => 'bobette'
+
+ rename_column "test_models", "girlfriend", "exgirlfriend"
+
+ TestModel.reset_column_information
+ bob = TestModel.first
+
+ assert_equal "bobette", bob.exgirlfriend
+ end
+
+ # FIXME: another integration test. We should decouple this from the
+ # AR::Base implementation.
+ def test_rename_column_using_symbol_arguments
+ add_column :test_models, :first_name, :string
+
+ TestModel.create :first_name => 'foo'
+
+ rename_column :test_models, :first_name, :nick_name
+ TestModel.reset_column_information
+ assert TestModel.column_names.include?("nick_name")
+ assert_equal ['foo'], TestModel.all.map(&:nick_name)
+ end
+
+ # FIXME: another integration test. We should decouple this from the
+ # AR::Base implementation.
+ def test_rename_column
+ add_column "test_models", "first_name", "string"
+
+ TestModel.create :first_name => 'foo'
+
+ rename_column "test_models", "first_name", "nick_name"
+ TestModel.reset_column_information
+ assert TestModel.column_names.include?("nick_name")
+ assert_equal ['foo'], TestModel.all.map(&:nick_name)
+ end
+
+ def test_rename_column_preserves_default_value_not_null
+ add_column 'test_models', 'salary', :integer, :default => 70000
+
+ default_before = connection.columns("test_models").find { |c| c.name == "salary" }.default
+ assert_equal 70000, default_before
+
+ rename_column "test_models", "salary", "anual_salary"
+
+ assert TestModel.column_names.include?("anual_salary")
+ default_after = connection.columns("test_models").find { |c| c.name == "anual_salary" }.default
+ assert_equal 70000, default_after
+ end
+
+ def test_rename_nonexistent_column
+ exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
+ ActiveRecord::StatementInvalid
+ else
+ ActiveRecord::ActiveRecordError
+ end
+ assert_raise(exception) do
+ rename_column "test_models", "nonexistent", "should_fail"
+ end
+ end
+
+ def test_rename_column_with_sql_reserved_word
+ add_column 'test_models', 'first_name', :string
+ rename_column "test_models", "first_name", "group"
+
+ assert TestModel.column_names.include?("group")
+ end
+
+ def test_rename_column_with_an_index
+ add_column "test_models", :hat_name, :string
+ add_index :test_models, :hat_name
+
+ # FIXME: we should test that the index goes away
+ rename_column "test_models", "hat_name", "name"
+ end
+
+ def test_remove_column_with_index
+ add_column "test_models", :hat_name, :string
+ add_index :test_models, :hat_name
+
+ # FIXME: we should test that the index goes away
+ remove_column("test_models", "hat_name")
+ end
+
+ def test_remove_column_with_multi_column_index
+ add_column "test_models", :hat_size, :integer
+ add_column "test_models", :hat_style, :string, :limit => 100
+ add_index "test_models", ["hat_style", "hat_size"], :unique => true
+
+ # FIXME: we should test that the index goes away
+ remove_column("test_models", "hat_size")
+ end
+
+ # FIXME: we need to test that these calls do something
+ def test_change_type_of_not_null_column
+ change_column "test_models", "updated_at", :datetime, :null => false
+ change_column "test_models", "updated_at", :datetime, :null => false
+ change_column "test_models", "updated_at", :datetime, :null => true
+ end
+
+ def test_change_column_nullability
+ add_column "test_models", "funny", :boolean
+ assert TestModel.columns_hash["funny"].null, "Column 'funny' must initially allow nulls"
+
+ change_column "test_models", "funny", :boolean, :null => false, :default => true
+
+ TestModel.reset_column_information
+ refute TestModel.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point"
+
+ change_column "test_models", "funny", :boolean, :null => true
+ TestModel.reset_column_information
+ assert TestModel.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point"
+ end
+
+ def test_change_column
+ add_column 'test_models', 'age', :integer
+ add_column 'test_models', 'approved', :boolean, :default => true
+
+ old_columns = connection.columns(TestModel.table_name)
+
+ assert old_columns.find { |c| c.name == 'age' && c.type == :integer }
+
+ change_column "test_models", "age", :string
+
+ new_columns = connection.columns(TestModel.table_name)
+
+ refute new_columns.find { |c| c.name == 'age' and c.type == :integer }
+ assert new_columns.find { |c| c.name == 'age' and c.type == :string }
+
+ old_columns = connection.columns(TestModel.table_name)
+ assert old_columns.find { |c|
+ c.name == 'approved' && c.type == :boolean && c.default == true
+ }
+
+ change_column :test_models, :approved, :boolean, :default => false
+ new_columns = connection.columns(TestModel.table_name)
+
+ refute new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
+ assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false }
+ change_column :test_models, :approved, :boolean, :default => true
+ end
+
+ def test_change_column_with_nil_default
+ add_column "test_models", "contributor", :boolean, :default => true
+ assert TestModel.new.contributor?
+
+ change_column "test_models", "contributor", :boolean, :default => nil
+ TestModel.reset_column_information
+ refute TestModel.new.contributor?
+ assert_nil TestModel.new.contributor
+ end
+
+ def test_change_column_with_new_default
+ add_column "test_models", "administrator", :boolean, :default => true
+ assert TestModel.new.administrator?
+
+ change_column "test_models", "administrator", :boolean, :default => false
+ TestModel.reset_column_information
+ refute TestModel.new.administrator?
+ end
+
+ def test_change_column_default
+ add_column "test_models", "first_name", :string
+ connection.change_column_default "test_models", "first_name", "Tester"
+
+ assert_equal "Tester", TestModel.new.first_name
+ end
+
+ def test_change_column_default_to_null
+ add_column "test_models", "first_name", :string
+ connection.change_column_default "test_models", "first_name", nil
+ assert_nil TestModel.new.first_name
+ end
+
+ def test_remove_column_no_second_parameter_raises_exception
+ assert_raise(ArgumentError) { connection.remove_column("funny") }
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb
new file mode 100644
index 0000000000..d5ff2c607f
--- /dev/null
+++ b/activerecord/test/cases/migration/rename_table_test.rb
@@ -0,0 +1,72 @@
+require "cases/migration/helper"
+
+module ActiveRecord
+ class Migration
+ class RenameTableTest < ActiveRecord::TestCase
+ include ActiveRecord::Migration::TestHelper
+
+ self.use_transactional_fixtures = false
+
+ def setup
+ super
+ add_column 'test_models', :url, :string
+ remove_column 'test_models', :created_at
+ remove_column 'test_models', :updated_at
+ end
+
+ def test_rename_table_for_sqlite_should_work_with_reserved_words
+ renamed = false
+
+ skip "not supported" unless current_adapter?(:SQLite3Adapter)
+
+ add_column :test_models, :url, :string
+ connection.rename_table :references, :old_references
+ connection.rename_table :test_models, :references
+
+ renamed = true
+
+ # Using explicit id in insert for compatibility across all databases
+ con = connection
+ con.execute "INSERT INTO 'references' (url, created_at, updated_at) VALUES ('http://rubyonrails.com', 0, 0)"
+ assert_equal 'http://rubyonrails.com', connection.select_value("SELECT url FROM 'references' WHERE id=1")
+ ensure
+ return unless renamed
+ connection.rename_table :references, :test_models
+ connection.rename_table :old_references, :references
+ end
+
+ def test_rename_table
+ rename_table :test_models, :octopi
+
+ # Using explicit id in insert for compatibility across all databases
+ con = connection
+ con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
+
+ con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
+
+ con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
+
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
+
+ rename_table :octopi, :test_models
+ end
+
+ def test_rename_table_with_an_index
+ add_index :test_models, :url
+
+ rename_table :test_models, :octopi
+
+ # Using explicit id in insert for compatibility across all databases
+ con = ActiveRecord::Base.connection
+ con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
+ con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')"
+ con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
+
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', connection.select_value("SELECT url FROM octopi WHERE id=1")
+ assert connection.indexes(:octopi).first.columns.include?("url")
+
+ rename_table :octopi, :test_models
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb
new file mode 100644
index 0000000000..8fd770abd1
--- /dev/null
+++ b/activerecord/test/cases/migration/table_and_index_test.rb
@@ -0,0 +1,24 @@
+require "cases/helper"
+
+module ActiveRecord
+ class Migration
+ class TableAndIndexTest < ActiveRecord::TestCase
+ def test_add_schema_info_respects_prefix_and_suffix
+ conn = ActiveRecord::Base.connection
+
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ ActiveRecord::Base.table_name_suffix = '_s'
+ conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+
+ conn.initialize_schema_migrations_table
+
+ assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name]
+ ensure
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 4b5dd18be8..5d1bad0d54 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require "cases/migration/helper"
require 'bigdecimal/util'
require 'models/person'
@@ -6,2235 +7,741 @@ require 'models/topic'
require 'models/developer'
require MIGRATIONS_ROOT + "/valid/2_we_need_reminders"
+require MIGRATIONS_ROOT + "/rename/1_we_need_things"
+require MIGRATIONS_ROOT + "/rename/2_rename_things"
require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers"
-if ActiveRecord::Base.connection.supports_migrations?
- class BigNumber < ActiveRecord::Base; end
+class BigNumber < ActiveRecord::Base; end
- class Reminder < ActiveRecord::Base; end
+class Reminder < ActiveRecord::Base; end
- class ActiveRecord::Migration
- class << self
- attr_accessor :message_count
- end
-
- def puts(text="")
- ActiveRecord::Migration.message_count ||= 0
- ActiveRecord::Migration.message_count += 1
- end
- end
-
- class MigrationTableAndIndexTest < ActiveRecord::TestCase
- def test_add_schema_info_respects_prefix_and_suffix
- conn = ActiveRecord::Base.connection
+class Thing < ActiveRecord::Base; end
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
- # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters
- ActiveRecord::Base.table_name_prefix = 'p_'
- ActiveRecord::Base.table_name_suffix = '_s'
- conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+class MigrationTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
- conn.initialize_schema_migrations_table
+ fixtures :people
- assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name]
- ensure
- ActiveRecord::Base.table_name_prefix = ""
- ActiveRecord::Base.table_name_suffix = ""
+ def setup
+ super
+ %w(reminders people_reminders prefix_reminders_suffix).each do |table|
+ Reminder.connection.drop_table(table) rescue nil
end
+ Reminder.reset_column_information
+ ActiveRecord::Migration.verbose = true
+ ActiveRecord::Migration.message_count = 0
end
- class MigrationTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
-
- fixtures :people
-
- def setup
- ActiveRecord::Migration.verbose = true
- ActiveRecord::Migration.message_count = 0
- end
-
- def teardown
- ActiveRecord::Base.connection.initialize_schema_migrations_table
- ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
-
- %w(reminders people_reminders prefix_reminders_suffix).each do |table|
- Reminder.connection.drop_table(table) rescue nil
- end
- Reminder.reset_column_information
-
- %w(last_name key bio age height wealth birthday favorite_day
- moment_of_truth male administrator funny).each do |column|
- Person.connection.remove_column('people', column) rescue nil
- end
- Person.connection.remove_column("people", "first_name") rescue nil
- Person.connection.remove_column("people", "middle_name") rescue nil
- Person.connection.add_column("people", "first_name", :string, :limit => 40)
- Person.reset_column_information
- end
-
- def test_add_index
- # Limit size of last_name and key columns to support Firebird index limitations
- Person.connection.add_column "people", "last_name", :string, :limit => 100
- Person.connection.add_column "people", "key", :string, :limit => 100
- Person.connection.add_column "people", "administrator", :boolean
-
- assert_nothing_raised { Person.connection.add_index("people", "last_name") }
- assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
-
- # Orcl nds shrt indx nms. Sybs 2.
- # OpenBase does not have named indexes. You must specify a single column name
- unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.remove_index("people", :column => ["last_name", "first_name"]) }
- # Oracle adapter cannot have specified index name larger than 30 characters
- # Oracle adapter is shortening index name when just column list is given
- unless current_adapter?(:OracleAdapter)
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.remove_index("people", :name => :index_people_on_last_name_and_first_name) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") }
- end
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => 10) }
- assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => {:last_name => 10}) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => 10) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
- assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) }
- assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
- end
-
- # quoting
- # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word
- # OpenBase does not have named indexes. You must specify a single column name
- unless current_adapter?(:OpenBaseAdapter)
- Person.update_all "#{Person.connection.quote_column_name 'key'}=#{Person.connection.quote_column_name 'id'}" #some databases (including sqlite2 won't add a unique index if existing data non unique)
- assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key_idx", :unique => true) }
- assert_nothing_raised { Person.connection.remove_index("people", :name => "key_idx", :unique => true) }
- end
-
- # Sybase adapter does not support indexes on :boolean columns
- # OpenBase does not have named indexes. You must specify a single column
- unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
- assert_nothing_raised { Person.connection.add_index("people", %w(last_name first_name administrator), :name => "named_admin") }
- assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") }
- end
- end
-
- def test_index_symbol_names
- assert_nothing_raised { Person.connection.add_index :people, :primary_contact_id, :name => :symbol_index_name }
- assert Person.connection.index_exists?(:people, :primary_contact_id, :name => :symbol_index_name)
- assert_nothing_raised { Person.connection.remove_index :people, :name => :symbol_index_name }
- assert !Person.connection.index_exists?(:people, :primary_contact_id, :name => :symbol_index_name)
- end
-
- def test_add_index_length_limit
- good_index_name = 'x' * Person.connection.index_name_length
- too_long_index_name = good_index_name + 'x'
- assert_raise(ArgumentError) { Person.connection.add_index("people", "first_name", :name => too_long_index_name) }
- assert !Person.connection.index_name_exists?("people", too_long_index_name, false)
- assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => good_index_name) }
- assert Person.connection.index_name_exists?("people", good_index_name, false)
- Person.connection.remove_index("people", :name => good_index_name)
- end
-
- def test_remove_nonexistent_index
- # we do this by name, so OpenBase is a wash as noted above
- unless current_adapter?(:OpenBaseAdapter)
- assert_raise(ArgumentError) { Person.connection.remove_index("people", "no_such_index") }
- end
- end
+ def teardown
+ ActiveRecord::Base.connection.initialize_schema_migrations_table
+ ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}"
- def test_rename_index
- unless current_adapter?(:OpenBaseAdapter)
- # keep the names short to make Oracle and similar behave
- Person.connection.add_index('people', [:first_name], :name => 'old_idx')
- assert_nothing_raised { Person.connection.rename_index('people', 'old_idx', 'new_idx') }
- # if the adapter doesn't support the indexes call, pick defaults that let the test pass
- assert !Person.connection.index_name_exists?('people', 'old_idx', false)
- assert Person.connection.index_name_exists?('people', 'new_idx', true)
- end
+ %w(things awesome_things prefix_things_suffix p_awesome_things_s ).each do |table|
+ Thing.connection.drop_table(table) rescue nil
end
+ Thing.reset_column_information
- def test_double_add_index
- unless current_adapter?(:OpenBaseAdapter)
- Person.connection.add_index('people', [:first_name], :name => 'some_idx')
- assert_raise(ArgumentError) { Person.connection.add_index('people', [:first_name], :name => 'some_idx') }
- end
+ %w(reminders people_reminders prefix_reminders_suffix).each do |table|
+ Reminder.connection.drop_table(table) rescue nil
end
+ Reminder.reset_column_information
- def test_index_exists
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- t.column :bar, :string, :limit => 100
- end
- Person.connection.add_index :testings, :foo
-
- assert Person.connection.index_exists?(:testings, :foo)
- assert !Person.connection.index_exists?(:testings, :bar)
- ensure
- Person.connection.drop_table :testings rescue nil
+ %w(last_name key bio age height wealth birthday favorite_day
+ moment_of_truth male administrator funny).each do |column|
+ Person.connection.remove_column('people', column) rescue nil
end
+ Person.connection.remove_column("people", "first_name") rescue nil
+ Person.connection.remove_column("people", "middle_name") rescue nil
+ Person.connection.add_column("people", "first_name", :string, :limit => 40)
+ Person.reset_column_information
+ end
- def test_index_exists_on_multiple_columns
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- t.column :bar, :string, :limit => 100
- end
- Person.connection.add_index :testings, [:foo, :bar]
-
- assert Person.connection.index_exists?(:testings, [:foo, :bar])
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_unique_index_exists
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- end
- Person.connection.add_index :testings, :foo, :unique => true
-
- assert Person.connection.index_exists?(:testings, :foo, :unique => true)
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_named_index_exists
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- end
- Person.connection.add_index :testings, :foo, :name => "custom_index_name"
-
- assert Person.connection.index_exists?(:testings, :foo, :name => "custom_index_name")
- ensure
- Person.connection.drop_table :testings rescue nil
+ def test_create_table_with_force_true_does_not_drop_nonexisting_table
+ if Person.connection.table_exists?(:testings2)
+ Person.connection.drop_table :testings2
end
- def testing_table_with_only_foo_attribute
- Person.connection.create_table :testings, :id => false do |t|
- t.column :foo, :string
- end
-
- yield Person.connection
- ensure
- Person.connection.drop_table :testings rescue nil
- end
- protected :testing_table_with_only_foo_attribute
+ # using a copy as we need the drop_table method to
+ # continue to work for the ensure block of the test
+ temp_conn = Person.connection.dup
- def test_create_table_without_id
- testing_table_with_only_foo_attribute do |connection|
- assert_equal connection.columns(:testings).size, 1
- end
- end
+ assert_not_equal temp_conn, Person.connection
- def test_add_column_with_primary_key_attribute
- testing_table_with_only_foo_attribute do |connection|
- assert_nothing_raised { connection.add_column :testings, :id, :primary_key }
- assert_equal connection.columns(:testings).size, 2
- end
+ temp_conn.create_table :testings2, :force => true do |t|
+ t.column :foo, :string
end
+ ensure
+ Person.connection.drop_table :testings2 rescue nil
+ end
- def test_create_table_adds_id
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
-
- assert_equal %w(foo id),
- Person.connection.columns(:testings).map { |c| c.name }.sort
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_create_table_with_not_null_column
- assert_nothing_raised do
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :null => false
- end
- end
-
- assert_raise(ActiveRecord::StatementInvalid) do
- Person.connection.execute "insert into testings (foo) values (NULL)"
- end
- ensure
- Person.connection.drop_table :testings rescue nil
- end
+ def connection
+ ActiveRecord::Base.connection
+ end
- def test_create_table_with_defaults
- # MySQL doesn't allow defaults on TEXT or BLOB columns.
- mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
+ def test_migration_instance_has_connection
+ migration = Class.new(ActiveRecord::Migration).new
+ assert_equal connection, migration.connection
+ end
- Person.connection.create_table :testings do |t|
- t.column :one, :string, :default => "hello"
- t.column :two, :boolean, :default => true
- t.column :three, :boolean, :default => false
- t.column :four, :integer, :default => 1
- t.column :five, :text, :default => "hello" unless mysql
+ def test_method_missing_delegates_to_connection
+ migration = Class.new(ActiveRecord::Migration) {
+ def connection
+ Class.new {
+ def create_table; "hi mom!"; end
+ }.new
end
+ }.new
- columns = Person.connection.columns(:testings)
- one = columns.detect { |c| c.name == "one" }
- two = columns.detect { |c| c.name == "two" }
- three = columns.detect { |c| c.name == "three" }
- four = columns.detect { |c| c.name == "four" }
- five = columns.detect { |c| c.name == "five" } unless mysql
-
- assert_equal "hello", one.default
- assert_equal true, two.default
- assert_equal false, three.default
- assert_equal 1, four.default
- assert_equal "hello", five.default unless mysql
-
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_create_table_with_limits
- assert_nothing_raised do
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 255
-
- t.column :default_int, :integer
-
- t.column :one_int, :integer, :limit => 1
- t.column :four_int, :integer, :limit => 4
- t.column :eight_int, :integer, :limit => 8
- t.column :eleven_int, :integer, :limit => 11
- end
- end
+ assert_equal "hi mom!", migration.method_missing(:create_table)
+ end
- columns = Person.connection.columns(:testings)
- foo = columns.detect { |c| c.name == "foo" }
- assert_equal 255, foo.limit
-
- default = columns.detect { |c| c.name == "default_int" }
- one = columns.detect { |c| c.name == "one_int" }
- four = columns.detect { |c| c.name == "four_int" }
- eight = columns.detect { |c| c.name == "eight_int" }
- eleven = columns.detect { |c| c.name == "eleven_int" }
-
- if current_adapter?(:PostgreSQLAdapter)
- assert_equal 'integer', default.sql_type
- assert_equal 'smallint', one.sql_type
- assert_equal 'integer', four.sql_type
- assert_equal 'bigint', eight.sql_type
- assert_equal 'integer', eleven.sql_type
- elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- assert_match 'int(11)', default.sql_type
- assert_match 'tinyint', one.sql_type
- assert_match 'int', four.sql_type
- assert_match 'bigint', eight.sql_type
- assert_match 'int(11)', eleven.sql_type
- elsif current_adapter?(:OracleAdapter)
- assert_equal 'NUMBER(38)', default.sql_type
- assert_equal 'NUMBER(1)', one.sql_type
- assert_equal 'NUMBER(4)', four.sql_type
- assert_equal 'NUMBER(8)', eight.sql_type
- end
- ensure
- Person.connection.drop_table :testings rescue nil
+ def test_add_table_with_decimals
+ Person.connection.drop_table :big_numbers rescue nil
+
+ assert !BigNumber.table_exists?
+ GiveMeBigNumbers.up
+
+ assert BigNumber.create(
+ :bank_balance => 1586.43,
+ :big_bank_balance => BigDecimal("1000234000567.95"),
+ :world_population => 6000000000,
+ :my_house_population => 3,
+ :value_of_e => BigDecimal("2.7182818284590452353602875")
+ )
+
+ b = BigNumber.first
+ assert_not_nil b
+
+ assert_not_nil b.bank_balance
+ assert_not_nil b.big_bank_balance
+ assert_not_nil b.world_population
+ assert_not_nil b.my_house_population
+ assert_not_nil b.value_of_e
+
+ # TODO: set world_population >= 2**62 to cover 64-bit platforms and test
+ # is_a?(Bignum)
+ assert_kind_of Integer, b.world_population
+ assert_equal 6000000000, b.world_population
+ assert_kind_of Fixnum, b.my_house_population
+ assert_equal 3, b.my_house_population
+ assert_kind_of BigDecimal, b.bank_balance
+ assert_equal BigDecimal("1586.43"), b.bank_balance
+ assert_kind_of BigDecimal, b.big_bank_balance
+ assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
+
+ # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with
+ # precision/scale explicitly left out. By the SQL standard, numbers
+ # assigned to this field should be truncated but that's seldom respected.
+ if current_adapter?(:PostgreSQLAdapter)
+ # - PostgreSQL changes the SQL spec on columns declared simply as
+ # "decimal" to something more useful: instead of being given a scale
+ # of 0, they take on the compile-time limit for precision and scale,
+ # so the following should succeed unless you have used really wacky
+ # compilation options
+ # - SQLite2 has the default behavior of preserving all data sent in,
+ # so this happens there too
+ assert_kind_of BigDecimal, b.value_of_e
+ assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e
+ elsif current_adapter?(:SQLite3Adapter)
+ # - SQLite3 stores a float, in violation of SQL
+ assert_kind_of BigDecimal, b.value_of_e
+ assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001
+ else
+ # - SQL standard is an integer
+ assert_kind_of Fixnum, b.value_of_e
+ assert_equal 2, b.value_of_e
end
- def test_create_table_with_primary_key_prefix_as_table_name_with_underscore
- ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
-
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
+ GiveMeBigNumbers.down
+ assert_raise(ActiveRecord::StatementInvalid) { BigNumber.first }
+ end
- assert_equal %w(foo testing_id), Person.connection.columns(:testings).map { |c| c.name }.sort
- ensure
- Person.connection.drop_table :testings rescue nil
- ActiveRecord::Base.primary_key_prefix_type = nil
- end
+ def test_filtering_migrations
+ assert !Person.column_methods_hash.include?(:last_name)
+ assert !Reminder.table_exists?
- def test_create_table_with_primary_key_prefix_as_table_name
- ActiveRecord::Base.primary_key_prefix_type = :table_name
+ name_filter = lambda { |migration| migration.name == "ValidPeopleHaveLastNames" }
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", &name_filter)
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name)
+ assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
- assert_equal %w(foo testingid), Person.connection.columns(:testings).map { |c| c.name }.sort
- ensure
- Person.connection.drop_table :testings rescue nil
- ActiveRecord::Base.primary_key_prefix_type = nil
- end
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", &name_filter)
- def test_create_table_with_force_true_does_not_drop_nonexisting_table
- if Person.connection.table_exists?(:testings2)
- Person.connection.drop_table :testings2
- end
+ Person.reset_column_information
+ assert !Person.column_methods_hash.include?(:last_name)
+ assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
+ end
- # using a copy as we need the drop_table method to
- # continue to work for the ensure block of the test
- temp_conn = Person.connection.dup
- temp_conn.expects(:drop_table).never
- temp_conn.create_table :testings2, :force => true do |t|
- t.column :foo, :string
- end
- ensure
- Person.connection.drop_table :testings2 rescue nil
+ class MockMigration < ActiveRecord::Migration
+ attr_reader :went_up, :went_down
+ def initialize
+ @went_up = false
+ @went_down = false
end
- def test_create_table_with_timestamps_should_create_datetime_columns
- table_name = :testings
-
- Person.connection.create_table table_name do |t|
- t.timestamps
- end
- created_columns = Person.connection.columns(table_name)
-
- created_at_column = created_columns.detect {|c| c.name == 'created_at' }
- updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
-
- assert !created_at_column.null
- assert !updated_at_column.null
- ensure
- Person.connection.drop_table table_name rescue nil
+ def up
+ @went_up = true
+ super
end
- def test_create_table_with_timestamps_should_create_datetime_columns_with_options
- table_name = :testings
-
- Person.connection.create_table table_name do |t|
- t.timestamps :null => false
- end
- created_columns = Person.connection.columns(table_name)
-
- created_at_column = created_columns.detect {|c| c.name == 'created_at' }
- updated_at_column = created_columns.detect {|c| c.name == 'updated_at' }
-
- assert !created_at_column.null
- assert !updated_at_column.null
- ensure
- Person.connection.drop_table table_name rescue nil
+ def down
+ @went_down = true
+ super
end
+ end
- def test_create_table_without_a_block
- table_name = :testings
- Person.connection.create_table table_name
- ensure
- Person.connection.drop_table table_name rescue nil
- end
+ def test_instance_based_migration_up
+ migration = MockMigration.new
+ assert !migration.went_up, 'have not gone up'
+ assert !migration.went_down, 'have not gone down'
- # Sybase, and SQLite3 will not allow you to add a NOT NULL
- # column to a table without a default value.
- unless current_adapter?(:SybaseAdapter, :SQLite3Adapter)
- def test_add_column_not_null_without_default
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
- Person.connection.add_column :testings, :bar, :string, :null => false
+ migration.migrate :up
+ assert migration.went_up, 'have gone up'
+ assert !migration.went_down, 'have not gone down'
+ end
- assert_raise(ActiveRecord::StatementInvalid) do
- Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
- end
- ensure
- Person.connection.drop_table :testings rescue nil
- end
- end
+ def test_instance_based_migration_down
+ migration = MockMigration.new
+ assert !migration.went_up, 'have not gone up'
+ assert !migration.went_down, 'have not gone down'
- def test_add_column_not_null_with_default
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- end
+ migration.migrate :down
+ assert !migration.went_up, 'have gone up'
+ assert migration.went_down, 'have not gone down'
+ end
- con = Person.connection
- Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)
- Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
- Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)
- assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" }
-
- assert_raise(ActiveRecord::StatementInvalid) do
- unless current_adapter?(:OpenBaseAdapter)
- Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)"
- else
- Person.connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)",
- "Testing Insert","id",2)
- end
- end
- ensure
- Person.connection.drop_table :testings rescue nil
+ def test_migrator_one_up_with_exception_and_rollback
+ unless ActiveRecord::Base.connection.supports_ddl_transactions?
+ skip "not supported on #{ActiveRecord::Base.connection.class}"
end
- # We specifically do a manual INSERT here, and then test only the SELECT
- # functionality. This allows us to more easily catch INSERT being broken,
- # but SELECT actually working fine.
- def test_native_decimal_insert_manual_vs_automatic
- correct_value = '0012345678901234567890.0123456789'.to_d
-
- Person.delete_all
- Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
- Person.reset_column_information
-
- # Do a manual insertion
- if current_adapter?(:OracleAdapter)
- Person.connection.execute "insert into people (id, wealth, created_at, updated_at) values (people_seq.nextval, 12345678901234567890.0123456789, 0, 0)"
- elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings
- Person.connection.execute "insert into people (wealth, created_at, updated_at) values ('12345678901234567890.0123456789', 0, 0)"
- else
- Person.connection.execute "insert into people (wealth, created_at, updated_at) values (12345678901234567890.0123456789, 0, 0)"
- end
-
- # SELECT
- row = Person.find(:first)
- assert_kind_of BigDecimal, row.wealth
-
- # If this assert fails, that means the SELECT is broken!
- unless current_adapter?(:SQLite3Adapter)
- assert_equal correct_value, row.wealth
- end
-
- # Reset to old state
- Person.delete_all
-
- # Now use the Rails insertion
- assert_nothing_raised { Person.create :wealth => BigDecimal.new("12345678901234567890.0123456789") }
+ refute Person.column_methods_hash.include?(:last_name)
- # SELECT
- row = Person.find(:first)
- assert_kind_of BigDecimal, row.wealth
+ migration = Struct.new(:name, :version) {
+ def migrate(x); raise 'Something broke'; end
+ }.new('zomg', 100)
- # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
- unless current_adapter?(:SQLite3Adapter)
- assert_equal correct_value, row.wealth
- end
-
- # Reset to old state
- Person.connection.del_column "people", "wealth" rescue nil
- Person.reset_column_information
- end
+ migrator = ActiveRecord::Migrator.new(:up, [migration], 100)
- def test_add_column_with_precision_and_scale
- Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7
- Person.reset_column_information
-
- wealth_column = Person.columns_hash['wealth']
- assert_equal 9, wealth_column.precision
- assert_equal 7, wealth_column.scale
- end
+ e = assert_raise(StandardError) { migrator.migrate }
- def test_native_types
- Person.delete_all
- Person.connection.add_column "people", "last_name", :string
- Person.connection.add_column "people", "bio", :text
- Person.connection.add_column "people", "age", :integer
- Person.connection.add_column "people", "height", :float
- Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
- Person.connection.add_column "people", "birthday", :datetime
- Person.connection.add_column "people", "favorite_day", :date
- Person.connection.add_column "people", "moment_of_truth", :datetime
- Person.connection.add_column "people", "male", :boolean
- Person.reset_column_information
-
- assert_nothing_raised do
- Person.create :first_name => 'bob', :last_name => 'bobsen',
- :bio => "I was born ....", :age => 18, :height => 1.78,
- :wealth => BigDecimal.new("12345678901234567890.0123456789"),
- :birthday => 18.years.ago, :favorite_day => 10.days.ago,
- :moment_of_truth => "1782-10-10 21:40:18", :male => true
- end
+ assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message
- bob = Person.find(:first)
- assert_equal 'bob', bob.first_name
- assert_equal 'bobsen', bob.last_name
- assert_equal "I was born ....", bob.bio
- assert_equal 18, bob.age
+ Person.reset_column_information
+ refute Person.column_methods_hash.include?(:last_name)
+ end
- # Test for 30 significant digits (beyond the 16 of float), 10 of them
- # after the decimal place.
+ def test_schema_migrations_table_name
+ ActiveRecord::Base.table_name_prefix = "prefix_"
+ ActiveRecord::Base.table_name_suffix = "_suffix"
+ Reminder.reset_table_name
+ assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ Reminder.reset_table_name
+ assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
+ ensure
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ end
- unless current_adapter?(:SQLite3Adapter)
- assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
- end
+ def test_proper_table_name
+ assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
+ assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
+ assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder)
+ Reminder.reset_table_name
+ assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
+
+ # Use the model's own prefix/suffix if a model is given
+ ActiveRecord::Base.table_name_prefix = "ARprefix_"
+ ActiveRecord::Base.table_name_suffix = "_ARsuffix"
+ Reminder.table_name_prefix = 'prefix_'
+ Reminder.table_name_suffix = '_suffix'
+ Reminder.reset_table_name
+ assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
+ Reminder.table_name_prefix = ''
+ Reminder.table_name_suffix = ''
+ Reminder.reset_table_name
+
+ # Use AR::Base's prefix/suffix if string or symbol is given
+ ActiveRecord::Base.table_name_prefix = "prefix_"
+ ActiveRecord::Base.table_name_suffix = "_suffix"
+ Reminder.reset_table_name
+ assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
+ assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table)
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ Reminder.reset_table_name
+ end
- assert_equal true, bob.male?
+ def test_rename_table_with_prefix_and_suffix
+ assert !Thing.table_exists?
+ ActiveRecord::Base.table_name_prefix = 'p_'
+ ActiveRecord::Base.table_name_suffix = '_s'
+ Thing.reset_table_name
+ Thing.reset_sequence_name
+ WeNeedThings.up
+
+ assert Thing.create("content" => "hello world")
+ assert_equal "hello world", Thing.first.content
+
+ RenameThings.up
+ Thing.table_name = "p_awesome_things_s"
+
+ assert_equal "hello world", Thing.first.content
+ ensure
+ ActiveRecord::Base.table_name_prefix = ''
+ ActiveRecord::Base.table_name_suffix = ''
+ Thing.reset_table_name
+ Thing.reset_sequence_name
+ end
- assert_equal String, bob.first_name.class
- assert_equal String, bob.last_name.class
- assert_equal String, bob.bio.class
- assert_equal Fixnum, bob.age.class
- assert_equal Time, bob.birthday.class
+ def test_add_drop_table_with_prefix_and_suffix
+ assert !Reminder.table_exists?
+ ActiveRecord::Base.table_name_prefix = 'prefix_'
+ ActiveRecord::Base.table_name_suffix = '_suffix'
+ Reminder.reset_table_name
+ Reminder.reset_sequence_name
+ WeNeedReminders.up
+ assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
+ assert_equal "hello world", Reminder.first.content
+
+ WeNeedReminders.down
+ assert_raise(ActiveRecord::StatementInvalid) { Reminder.first }
+ ensure
+ ActiveRecord::Base.table_name_prefix = ''
+ ActiveRecord::Base.table_name_suffix = ''
+ Reminder.reset_table_name
+ Reminder.reset_sequence_name
+ end
- if current_adapter?(:OracleAdapter, :SybaseAdapter)
- # Sybase, and Oracle don't differentiate between date/time
- assert_equal Time, bob.favorite_day.class
- else
- assert_equal Date, bob.favorite_day.class
- end
+ def test_create_table_with_binary_column
+ Person.connection.drop_table :binary_testings rescue nil
- # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column
- # therefore no timezone change is done afterwards when default timezone is changed
- unless current_adapter?(:OracleAdapter)
- # Test DateTime column and defaults, including timezone.
- # FIXME: moment of truth may be Time on 64-bit platforms.
- if bob.moment_of_truth.is_a?(DateTime)
-
- with_env_tz 'US/Eastern' do
- bob.reload
- assert_equal DateTime.local_offset, bob.moment_of_truth.offset
- assert_not_equal 0, bob.moment_of_truth.offset
- assert_not_equal "Z", bob.moment_of_truth.zone
- # US/Eastern is -5 hours from GMT
- assert_equal Rational(-5, 24), bob.moment_of_truth.offset
- assert_match(/\A-05:?00\Z/, bob.moment_of_truth.zone) #ruby 1.8.6 uses HH:MM, prior versions use HHMM
- assert_equal DateTime::ITALY, bob.moment_of_truth.start
- end
- end
+ assert_nothing_raised {
+ Person.connection.create_table :binary_testings do |t|
+ t.column "data", :binary, :null => false
end
+ }
- assert_instance_of TrueClass, bob.male?
- assert_kind_of BigDecimal, bob.wealth
- end
+ columns = Person.connection.columns(:binary_testings)
+ data_column = columns.detect { |c| c.name == "data" }
if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- def test_unabstracted_database_dependent_types
- Person.delete_all
-
- ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint
- Person.reset_column_information
- assert_match(/tinyint/, Person.columns_hash['intelligence_quotient'].sql_type)
- ensure
- ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil
- end
- end
-
- def test_add_remove_single_field_using_string_arguments
- assert !Person.column_methods_hash.include?(:last_name)
-
- ActiveRecord::Migration.add_column 'people', 'last_name', :string
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
-
- ActiveRecord::Migration.remove_column 'people', 'last_name'
-
- Person.reset_column_information
- assert !Person.column_methods_hash.include?(:last_name)
- end
-
- def test_add_remove_single_field_using_symbol_arguments
- assert !Person.column_methods_hash.include?(:last_name)
-
- ActiveRecord::Migration.add_column :people, :last_name, :string
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
-
- ActiveRecord::Migration.remove_column :people, :last_name
-
- Person.reset_column_information
- assert !Person.column_methods_hash.include?(:last_name)
- end
-
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- def testing_table_for_positioning
- Person.connection.create_table :testings, :id => false do |t|
- t.column :first, :integer
- t.column :second, :integer
- t.column :third, :integer
- end
-
- yield Person.connection
- ensure
- Person.connection.drop_table :testings rescue nil
- end
- protected :testing_table_for_positioning
-
- def test_column_positioning
- testing_table_for_positioning do |conn|
- assert_equal %w(first second third), conn.columns(:testings).map {|c| c.name }
- end
- end
-
- def test_add_column_with_positioning
- testing_table_for_positioning do |conn|
- conn.add_column :testings, :new_col, :integer
- assert_equal %w(first second third new_col), conn.columns(:testings).map {|c| c.name }
- end
- testing_table_for_positioning do |conn|
- conn.add_column :testings, :new_col, :integer, :first => true
- assert_equal %w(new_col first second third), conn.columns(:testings).map {|c| c.name }
- end
- testing_table_for_positioning do |conn|
- conn.add_column :testings, :new_col, :integer, :after => :first
- assert_equal %w(first new_col second third), conn.columns(:testings).map {|c| c.name }
- end
- end
-
- def test_change_column_with_positioning
- testing_table_for_positioning do |conn|
- conn.change_column :testings, :second, :integer, :first => true
- assert_equal %w(second first third), conn.columns(:testings).map {|c| c.name }
- end
- testing_table_for_positioning do |conn|
- conn.change_column :testings, :second, :integer, :after => :third
- assert_equal %w(first third second), conn.columns(:testings).map {|c| c.name }
- end
- end
- end
-
- def test_add_rename
- Person.delete_all
-
- begin
- Person.connection.add_column "people", "girlfriend", :string
- Person.reset_column_information
- Person.create :girlfriend => 'bobette'
-
- Person.connection.rename_column "people", "girlfriend", "exgirlfriend"
-
- Person.reset_column_information
- bob = Person.find(:first)
-
- assert_equal "bobette", bob.exgirlfriend
- ensure
- Person.connection.remove_column("people", "girlfriend") rescue nil
- Person.connection.remove_column("people", "exgirlfriend") rescue nil
- end
-
- end
-
- def test_rename_column_using_symbol_arguments
- begin
- names_before = Person.find(:all).map(&:first_name)
- Person.connection.rename_column :people, :first_name, :nick_name
- Person.reset_column_information
- assert Person.column_names.include?("nick_name")
- assert_equal names_before, Person.find(:all).map(&:nick_name)
- ensure
- Person.connection.remove_column("people","nick_name")
- Person.connection.add_column("people","first_name", :string)
- end
- end
-
- def test_rename_column
- begin
- names_before = Person.find(:all).map(&:first_name)
- Person.connection.rename_column "people", "first_name", "nick_name"
- Person.reset_column_information
- assert Person.column_names.include?("nick_name")
- assert_equal names_before, Person.find(:all).map(&:nick_name)
- ensure
- Person.connection.remove_column("people","nick_name")
- Person.connection.add_column("people","first_name", :string)
- end
- end
-
- def test_rename_column_preserves_default_value_not_null
- begin
- default_before = Developer.connection.columns("developers").find { |c| c.name == "salary" }.default
- assert_equal 70000, default_before
- Developer.connection.rename_column "developers", "salary", "anual_salary"
- Developer.reset_column_information
- assert Developer.column_names.include?("anual_salary")
- default_after = Developer.connection.columns("developers").find { |c| c.name == "anual_salary" }.default
- assert_equal 70000, default_after
- ensure
- Developer.connection.rename_column "developers", "anual_salary", "salary"
- Developer.reset_column_information
- end
- end
-
- def test_rename_nonexistent_column
- ActiveRecord::Base.connection.create_table(:hats) do |table|
- table.column :hat_name, :string, :default => nil
- end
- exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
- ActiveRecord::StatementInvalid
- else
- ActiveRecord::ActiveRecordError
- end
- assert_raise(exception) do
- Person.connection.rename_column "hats", "nonexistent", "should_fail"
- end
- ensure
- ActiveRecord::Base.connection.drop_table(:hats)
- end
-
- def test_rename_column_with_sql_reserved_word
- begin
- assert_nothing_raised { Person.connection.rename_column "people", "first_name", "group" }
- Person.reset_column_information
- assert Person.column_names.include?("group")
- ensure
- Person.connection.remove_column("people", "group") rescue nil
- Person.connection.add_column("people", "first_name", :string) rescue nil
- end
- end
-
- def test_rename_column_with_an_index
- ActiveRecord::Base.connection.create_table(:hats) do |table|
- table.column :hat_name, :string, :limit => 100
- table.column :hat_size, :integer
- end
- Person.connection.add_index :hats, :hat_name
- assert_nothing_raised do
- Person.connection.rename_column "hats", "hat_name", "name"
- end
- ensure
- ActiveRecord::Base.connection.drop_table(:hats)
- end
-
- def test_remove_column_with_index
- ActiveRecord::Base.connection.create_table(:hats) do |table|
- table.column :hat_name, :string, :limit => 100
- table.column :hat_size, :integer
- end
- ActiveRecord::Base.connection.add_index "hats", "hat_size"
-
- assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
- ensure
- ActiveRecord::Base.connection.drop_table(:hats)
- end
-
- def test_remove_column_with_multi_column_index
- ActiveRecord::Base.connection.create_table(:hats) do |table|
- table.column :hat_name, :string, :limit => 100
- table.column :hat_size, :integer
- table.column :hat_style, :string, :limit => 100
- end
- ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true
-
- assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
- ensure
- ActiveRecord::Base.connection.drop_table(:hats)
- end
-
- def test_remove_column_no_second_parameter_raises_exception
- assert_raise(ArgumentError) { Person.connection.remove_column("funny") }
+ assert_equal '', data_column.default
+ else
+ assert_nil data_column.default
end
- def test_change_type_of_not_null_column
- assert_nothing_raised do
- Topic.connection.change_column "topics", "written_on", :datetime, :null => false
- Topic.reset_column_information
-
- Topic.connection.change_column "topics", "written_on", :datetime, :null => false
- Topic.reset_column_information
-
- Topic.connection.change_column "topics", "written_on", :datetime, :null => true
- Topic.reset_column_information
- end
- end
+ Person.connection.drop_table :binary_testings rescue nil
+ end
- if current_adapter?(:SQLite3Adapter)
- def test_rename_table_for_sqlite_should_work_with_reserved_words
- begin
- assert_nothing_raised do
- ActiveRecord::Base.connection.rename_table :references, :old_references
- ActiveRecord::Base.connection.create_table :octopuses do |t|
- t.column :url, :string
- end
- end
-
- assert_nothing_raised { ActiveRecord::Base.connection.rename_table :octopuses, :references }
-
- # Using explicit id in insert for compatibility across all databases
- con = ActiveRecord::Base.connection
- assert_nothing_raised do
- con.execute "INSERT INTO 'references' (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://rubyonrails.com')"
- end
- assert_equal 'http://rubyonrails.com', ActiveRecord::Base.connection.select_value("SELECT url FROM 'references' WHERE id=1")
-
- ensure
- ActiveRecord::Base.connection.drop_table :references
- ActiveRecord::Base.connection.rename_table :old_references, :references
- end
- end
- end
+ def test_create_table_with_custom_sequence_name
+ skip "not supported" unless current_adapter? :OracleAdapter
- def test_rename_table
+ # table name is 29 chars, the standard sequence name will
+ # be 33 chars and should be shortened
+ assert_nothing_raised do
begin
- ActiveRecord::Base.connection.create_table :octopuses do |t|
- t.column :url, :string
+ Person.connection.create_table :table_with_name_thats_just_ok do |t|
+ t.column :foo, :string, :null => false
end
- ActiveRecord::Base.connection.rename_table :octopuses, :octopi
-
- # Using explicit id in insert for compatibility across all databases
- con = ActiveRecord::Base.connection
- con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
- assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" }
- con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
-
- assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
-
ensure
- ActiveRecord::Base.connection.drop_table :octopuses rescue nil
- ActiveRecord::Base.connection.drop_table :octopi rescue nil
+ Person.connection.drop_table :table_with_name_thats_just_ok rescue nil
end
end
- def test_change_column_nullability
- Person.delete_all
- Person.connection.add_column "people", "funny", :boolean
- Person.reset_column_information
- assert Person.columns_hash["funny"].null, "Column 'funny' must initially allow nulls"
- Person.connection.change_column "people", "funny", :boolean, :null => false, :default => true
- Person.reset_column_information
- assert !Person.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point"
- Person.connection.change_column "people", "funny", :boolean, :null => true
- Person.reset_column_information
- assert Person.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point"
- end
-
- def test_rename_table_with_an_index
+ # should be all good w/ a custom sequence name
+ assert_nothing_raised do
begin
- ActiveRecord::Base.connection.create_table :octopuses do |t|
- t.column :url, :string
+ Person.connection.create_table :table_with_name_thats_just_ok,
+ :sequence_name => 'suitably_short_seq' do |t|
+ t.column :foo, :string, :null => false
end
- ActiveRecord::Base.connection.add_index :octopuses, :url
-
- ActiveRecord::Base.connection.rename_table :octopuses, :octopi
- # Using explicit id in insert for compatibility across all databases
- con = ActiveRecord::Base.connection
- con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
- assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" }
- con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
+ Person.connection.execute("select suitably_short_seq.nextval from dual")
- assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
- assert ActiveRecord::Base.connection.indexes(:octopi).first.columns.include?("url")
ensure
- ActiveRecord::Base.connection.drop_table :octopuses rescue nil
- ActiveRecord::Base.connection.drop_table :octopi rescue nil
+ Person.connection.drop_table :table_with_name_thats_just_ok,
+ :sequence_name => 'suitably_short_seq' rescue nil
end
end
- def test_change_column
- Person.connection.add_column 'people', 'age', :integer
- label = "test_change_column Columns"
- old_columns = Person.connection.columns(Person.table_name, label)
- assert old_columns.find { |c| c.name == 'age' and c.type == :integer }
-
- assert_nothing_raised { Person.connection.change_column "people", "age", :string }
-
- new_columns = Person.connection.columns(Person.table_name, label)
- assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer }
- assert new_columns.find { |c| c.name == 'age' and c.type == :string }
-
- old_columns = Topic.connection.columns(Topic.table_name, label)
- assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
- assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false }
- new_columns = Topic.connection.columns(Topic.table_name, label)
- assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
- assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false }
- assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true }
- end
-
- def test_change_column_with_nil_default
- Person.connection.add_column "people", "contributor", :boolean, :default => true
- Person.reset_column_information
- assert Person.new.contributor?
-
- assert_nothing_raised { Person.connection.change_column "people", "contributor", :boolean, :default => nil }
- Person.reset_column_information
- assert !Person.new.contributor?
- assert_nil Person.new.contributor
- ensure
- Person.connection.remove_column("people", "contributor") rescue nil
- end
-
- def test_change_column_with_new_default
- Person.connection.add_column "people", "administrator", :boolean, :default => true
- Person.reset_column_information
- assert Person.new.administrator?
-
- assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => false }
- Person.reset_column_information
- assert !Person.new.administrator?
- ensure
- Person.connection.remove_column("people", "administrator") rescue nil
- end
-
- def test_change_column_default
- Person.connection.change_column_default "people", "first_name", "Tester"
- Person.reset_column_information
- assert_equal "Tester", Person.new.first_name
- end
-
- def test_change_column_quotes_column_names
- Person.connection.create_table :testings do |t|
- t.column :select, :string
- end
-
- assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 }
-
- # Oracle needs primary key value from sequence
- if current_adapter?(:OracleAdapter)
- assert_nothing_raised { Person.connection.execute "insert into testings (id, #{Person.connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" }
- else
- assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" }
- end
- ensure
- Person.connection.drop_table :testings rescue nil
- end
-
- def test_keeping_default_and_notnull_constaint_on_change
- Person.connection.create_table :testings do |t|
- t.column :title, :string
- end
- person_klass = Class.new(Person)
- person_klass.set_table_name 'testings'
-
- person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99
- person_klass.reset_column_information
- assert_equal 99, person_klass.columns_hash["wealth"].default
- assert_equal false, person_klass.columns_hash["wealth"].null
- # Oracle needs primary key value from sequence
- if current_adapter?(:OracleAdapter)
- assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")}
- else
- assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
- end
-
- # change column default to see that column doesn't lose its not null definition
- person_klass.connection.change_column_default "testings", "wealth", 100
- person_klass.reset_column_information
- assert_equal 100, person_klass.columns_hash["wealth"].default
- assert_equal false, person_klass.columns_hash["wealth"].null
-
- # rename column to see that column doesn't lose its not null and/or default definition
- person_klass.connection.rename_column "testings", "wealth", "money"
- person_klass.reset_column_information
- assert_nil person_klass.columns_hash["wealth"]
- assert_equal 100, person_klass.columns_hash["money"].default
- assert_equal false, person_klass.columns_hash["money"].null
-
- # change column
- person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000
- person_klass.reset_column_information
- assert_equal 1000, person_klass.columns_hash["money"].default
- assert_equal false, person_klass.columns_hash["money"].null
-
- # change column, make it nullable and clear default
- person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil
- person_klass.reset_column_information
- assert_nil person_klass.columns_hash["money"].default
- assert_equal true, person_klass.columns_hash["money"].null
-
- # change_column_null, make it not nullable and set null values to a default value
- person_klass.connection.execute('UPDATE testings SET money = NULL')
- person_klass.connection.change_column_null "testings", "money", false, 2000
- person_klass.reset_column_information
- assert_nil person_klass.columns_hash["money"].default
- assert_equal false, person_klass.columns_hash["money"].null
- assert_equal [2000], Person.connection.select_values("SELECT money FROM testings").map { |s| s.to_i }.sort
- ensure
- Person.connection.drop_table :testings rescue nil
+ # confirm the custom sequence got dropped
+ assert_raise(ActiveRecord::StatementInvalid) do
+ Person.connection.execute("select suitably_short_seq.nextval from dual")
end
+ end
- def test_change_column_default_to_null
- Person.connection.change_column_default "people", "first_name", nil
- Person.reset_column_information
- assert_nil Person.new.first_name
- end
+ def test_out_of_range_limit_should_raise
+ skip("MySQL and PostgreSQL only") unless current_adapter?(:MysqlAdapter, :Mysql2Adapter, :PostgreSQLAdapter)
- def test_column_exists
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
+ Person.connection.drop_table :test_limits rescue nil
+ assert_raise(ActiveRecord::ActiveRecordError, "integer limit didn't raise") do
+ Person.connection.create_table :test_integer_limits, :force => true do |t|
+ t.column :bigone, :integer, :limit => 10
end
-
- assert Person.connection.column_exists?(:testings, :foo)
- assert !Person.connection.column_exists?(:testings, :bar)
- ensure
- Person.connection.drop_table :testings rescue nil
end
- def test_column_exists_with_type
- Person.connection.create_table :testings do |t|
- t.column :foo, :string
- t.column :bar, :decimal, :precision => 8, :scale => 2
+ unless current_adapter?(:PostgreSQLAdapter)
+ assert_raise(ActiveRecord::ActiveRecordError, "text limit didn't raise") do
+ Person.connection.create_table :test_text_limits, :force => true do |t|
+ t.column :bigtext, :text, :limit => 0xfffffffff
+ end
end
-
- assert Person.connection.column_exists?(:testings, :foo, :string)
- assert !Person.connection.column_exists?(:testings, :foo, :integer)
- assert Person.connection.column_exists?(:testings, :bar, :decimal)
- assert !Person.connection.column_exists?(:testings, :bar, :integer)
- ensure
- Person.connection.drop_table :testings rescue nil
end
- def test_column_exists_with_definition
- Person.connection.create_table :testings do |t|
- t.column :foo, :string, :limit => 100
- t.column :bar, :decimal, :precision => 8, :scale => 2
- end
+ Person.connection.drop_table :test_limits rescue nil
+ end
- assert Person.connection.column_exists?(:testings, :foo, :string, :limit => 100)
- assert !Person.connection.column_exists?(:testings, :foo, :string, :limit => 50)
- assert Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2)
- assert !Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2)
+ protected
+ def with_env_tz(new_tz = 'US/Eastern')
+ old_tz, ENV['TZ'] = ENV['TZ'], new_tz
+ yield
ensure
- Person.connection.drop_table :testings rescue nil
+ old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
+end
- def test_column_exists_on_table_with_no_options_parameter_supplied
- Person.connection.create_table :testings do |t|
- t.string :foo
- end
- Person.connection.change_table :testings do |t|
- assert t.column_exists?(:foo)
- assert !(t.column_exists?(:bar))
- end
- ensure
- Person.connection.drop_table :testings rescue nil
+class ReservedWordsMigrationTest < ActiveRecord::TestCase
+ def test_drop_index_from_table_named_values
+ connection = Person.connection
+ connection.create_table :values, :force => true do |t|
+ t.integer :value
end
- def test_add_table
- assert !Reminder.table_exists?
-
- WeNeedReminders.up
-
- assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
-
- WeNeedReminders.down
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
+ assert_nothing_raised do
+ connection.add_index :values, :value
+ connection.remove_index :values, :column => :value
end
- def test_add_table_with_decimals
- Person.connection.drop_table :big_numbers rescue nil
-
- assert !BigNumber.table_exists?
- GiveMeBigNumbers.up
-
- assert BigNumber.create(
- :bank_balance => 1586.43,
- :big_bank_balance => BigDecimal("1000234000567.95"),
- :world_population => 6000000000,
- :my_house_population => 3,
- :value_of_e => BigDecimal("2.7182818284590452353602875")
- )
-
- b = BigNumber.find(:first)
- assert_not_nil b
-
- assert_not_nil b.bank_balance
- assert_not_nil b.big_bank_balance
- assert_not_nil b.world_population
- assert_not_nil b.my_house_population
- assert_not_nil b.value_of_e
-
- # TODO: set world_population >= 2**62 to cover 64-bit platforms and test
- # is_a?(Bignum)
- assert_kind_of Integer, b.world_population
- assert_equal 6000000000, b.world_population
- assert_kind_of Fixnum, b.my_house_population
- assert_equal 3, b.my_house_population
- assert_kind_of BigDecimal, b.bank_balance
- assert_equal BigDecimal("1586.43"), b.bank_balance
- assert_kind_of BigDecimal, b.big_bank_balance
- assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
-
- # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with
- # precision/scale explicitly left out. By the SQL standard, numbers
- # assigned to this field should be truncated but that's seldom respected.
- if current_adapter?(:PostgreSQLAdapter)
- # - PostgreSQL changes the SQL spec on columns declared simply as
- # "decimal" to something more useful: instead of being given a scale
- # of 0, they take on the compile-time limit for precision and scale,
- # so the following should succeed unless you have used really wacky
- # compilation options
- # - SQLite2 has the default behavior of preserving all data sent in,
- # so this happens there too
- assert_kind_of BigDecimal, b.value_of_e
- assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e
- elsif current_adapter?(:SQLite3Adapter)
- # - SQLite3 stores a float, in violation of SQL
- assert_kind_of BigDecimal, b.value_of_e
- assert_in_delta BigDecimal("2.71828182845905"), b.value_of_e, 0.00000000000001
- else
- # - SQL standard is an integer
- assert_kind_of Fixnum, b.value_of_e
- assert_equal 2, b.value_of_e
- end
+ connection.drop_table :values rescue nil
+ end
+end
- GiveMeBigNumbers.down
- assert_raise(ActiveRecord::StatementInvalid) { BigNumber.find(:first) }
+if ActiveRecord::Base.connection.supports_bulk_alter?
+ class BulkAlterTableMigrationsTest < ActiveRecord::TestCase
+ def setup
+ @connection = Person.connection
+ @connection.create_table(:delete_me, :force => true) {|t| }
end
- def test_migrator
- assert !Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
-
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
-
- assert_equal 3, ActiveRecord::Migrator.current_version
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
- assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
-
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
-
- assert_equal 0, ActiveRecord::Migrator.current_version
- Person.reset_column_information
- assert !Person.column_methods_hash.include?(:last_name)
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
+ def teardown
+ Person.connection.drop_table(:delete_me) rescue nil
end
- class MockMigration < ActiveRecord::Migration
- attr_reader :went_up, :went_down
- def initialize
- @went_up = false
- @went_down = false
- end
-
- def up
- @went_up = true
- super
- end
-
- def down
- @went_down = true
- super
+ def test_adding_multiple_columns
+ assert_queries(1) do
+ with_bulk_change_table do |t|
+ t.column :name, :string
+ t.string :qualification, :experience
+ t.integer :age, :default => 0
+ t.date :birthdate
+ t.timestamps
+ end
end
- end
-
- def test_instance_based_migration_up
- migration = MockMigration.new
- assert !migration.went_up, 'have not gone up'
- assert !migration.went_down, 'have not gone down'
-
- migration.migrate :up
- assert migration.went_up, 'have gone up'
- assert !migration.went_down, 'have not gone down'
- end
-
- def test_instance_based_migration_down
- migration = MockMigration.new
- assert !migration.went_up, 'have not gone up'
- assert !migration.went_down, 'have not gone down'
-
- migration.migrate :down
- assert !migration.went_up, 'have gone up'
- assert migration.went_down, 'have not gone down'
- end
-
- def test_migrator_one_up
- assert !Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
-
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 2)
-
- assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
- end
-
- def test_migrator_one_down
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
-
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1)
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
- end
-
- def test_migrator_one_up_one_down
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
-
- assert !Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
+ assert_equal 8, columns.size
+ [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type }
+ assert_equal 0, column(:age).default
end
- def test_migrator_double_up
- assert_equal(0, ActiveRecord::Migrator.current_version)
- ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1)
- assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) }
- assert_equal(1, ActiveRecord::Migrator.current_version)
- end
-
- def test_migrator_double_down
- assert_equal(0, ActiveRecord::Migrator.current_version)
- ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1)
- ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1)
- assert_nothing_raised { ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) }
- assert_equal(0, ActiveRecord::Migrator.current_version)
- end
+ def test_removing_columns
+ with_bulk_change_table do |t|
+ t.string :qualification, :experience
+ end
- if ActiveRecord::Base.connection.supports_ddl_transactions?
- def test_migrator_one_up_with_exception_and_rollback
- assert !Person.column_methods_hash.include?(:last_name)
+ [:qualification, :experience].each {|c| assert column(c) }
- e = assert_raise(StandardError) do
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/broken", 100)
+ assert_queries(1) do
+ with_bulk_change_table do |t|
+ t.remove :qualification, :experience
+ t.string :qualification_experience
end
-
- assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message
-
- Person.reset_column_information
- assert !Person.column_methods_hash.include?(:last_name)
end
- end
- def test_finds_migrations
- migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations
-
- [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i|
- assert_equal migrations[i].version, pair.first
- assert_equal migrations[i].name, pair.last
- end
+ [:qualification, :experience].each {|c| assert ! column(c) }
+ assert column(:qualification_experience)
end
- def test_finds_migrations_from_two_directories
- directories = [MIGRATIONS_ROOT + '/valid_with_timestamps', MIGRATIONS_ROOT + '/to_copy_with_timestamps']
- migrations = ActiveRecord::Migrator.new(:up, directories).migrations
-
- [[20090101010101, "PeopleHaveHobbies"],
- [20090101010202, "PeopleHaveDescriptions"],
- [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"],
- [20100201010101, "ValidWithTimestampsWeNeedReminders"],
- [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i|
- assert_equal pair.first, migrations[i].version
- assert_equal pair.last, migrations[i].name
+ def test_adding_indexes
+ with_bulk_change_table do |t|
+ t.string :username
+ t.string :name
+ t.integer :age
end
- end
-
- def test_finds_pending_migrations
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2", 1)
- migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/interleaved/pass_2").pending_migrations
- assert_equal 1, migrations.size
- assert_equal migrations[0].version, 3
- assert_equal migrations[0].name, 'InterleavedInnocentJointable'
- end
-
- def test_relative_migrations
- list = Dir.chdir(MIGRATIONS_ROOT) do
- ActiveRecord::Migrator.up("valid/", 1)
+ # Adding an index fires a query every time to check if an index already exists or not
+ assert_queries(3) do
+ with_bulk_change_table do |t|
+ t.index :username, :unique => true, :name => :awesome_username_index
+ t.index [:name, :age]
+ end
end
- migration_proxy = list.find { |item|
- item.name == 'ValidPeopleHaveLastNames'
- }
- assert migration_proxy, 'should find pending migration'
- end
-
- def test_only_loads_pending_migrations
- # migrate up to 1
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
-
- proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", nil)
-
- names = proxies.map(&:name)
- assert !names.include?('ValidPeopleHaveLastNames')
- assert names.include?('WeNeedReminders')
- assert names.include?('InnocentJointable')
- end
-
- def test_target_version_zero_should_run_only_once
- # migrate up to 1
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
+ assert_equal 2, indexes.size
- # migrate down to 0
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
+ name_age_index = index(:index_delete_me_on_name_and_age)
+ assert_equal ['name', 'age'].sort, name_age_index.columns.sort
+ assert ! name_age_index.unique
- # migrate down to 0 again
- proxies = ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
- assert_equal [], proxies
+ assert index(:awesome_username_index).unique
end
- def test_migrator_db_has_no_schema_migrations_table
- # Oracle adapter raises error if semicolon is present as last character
- if current_adapter?(:OracleAdapter)
- ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations")
- else
- ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;")
- end
- assert_nothing_raised do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
+ def test_removing_index
+ with_bulk_change_table do |t|
+ t.string :name
+ t.index :name
end
- end
-
- def test_migrator_verbosity
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
- assert_not_equal 0, ActiveRecord::Migration.message_count
- ActiveRecord::Migration.message_count = 0
-
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
- assert_not_equal 0, ActiveRecord::Migration.message_count
- ActiveRecord::Migration.message_count = 0
- end
-
- def test_migrator_verbosity_off
- ActiveRecord::Migration.verbose = false
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
- assert_equal 0, ActiveRecord::Migration.message_count
- ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
- assert_equal 0, ActiveRecord::Migration.message_count
- end
-
- def test_migrator_going_down_due_to_version_target
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
- assert !Person.column_methods_hash.include?(:last_name)
- assert !Reminder.table_exists?
+ assert index(:index_delete_me_on_name)
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
- assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
- end
-
- def test_migrator_rollback
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
- assert_equal(3, ActiveRecord::Migrator.current_version)
-
- ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
- assert_equal(2, ActiveRecord::Migrator.current_version)
-
- ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
- assert_equal(1, ActiveRecord::Migrator.current_version)
-
- ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
- assert_equal(0, ActiveRecord::Migrator.current_version)
-
- ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
- assert_equal(0, ActiveRecord::Migrator.current_version)
- end
-
- def test_migrator_forward
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
- assert_equal(1, ActiveRecord::Migrator.current_version)
-
- ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid", 2)
- assert_equal(3, ActiveRecord::Migrator.current_version)
-
- ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid")
- assert_equal(3, ActiveRecord::Migrator.current_version)
- end
-
- def test_get_all_versions
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
- assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions)
-
- ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
- assert_equal([1,2], ActiveRecord::Migrator.get_all_versions)
-
- ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
- assert_equal([1], ActiveRecord::Migrator.get_all_versions)
-
- ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
- assert_equal([], ActiveRecord::Migrator.get_all_versions)
- end
-
- def test_schema_migrations_table_name
- ActiveRecord::Base.table_name_prefix = "prefix_"
- ActiveRecord::Base.table_name_suffix = "_suffix"
- Reminder.reset_table_name
- assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name
- ActiveRecord::Base.table_name_prefix = ""
- ActiveRecord::Base.table_name_suffix = ""
- Reminder.reset_table_name
- assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
- ensure
- ActiveRecord::Base.table_name_prefix = ""
- ActiveRecord::Base.table_name_suffix = ""
- end
-
- def test_proper_table_name
- assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
- assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
- assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder)
- Reminder.reset_table_name
- assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
-
- # Use the model's own prefix/suffix if a model is given
- ActiveRecord::Base.table_name_prefix = "ARprefix_"
- ActiveRecord::Base.table_name_suffix = "_ARsuffix"
- Reminder.table_name_prefix = 'prefix_'
- Reminder.table_name_suffix = '_suffix'
- Reminder.reset_table_name
- assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
- Reminder.table_name_prefix = ''
- Reminder.table_name_suffix = ''
- Reminder.reset_table_name
-
- # Use AR::Base's prefix/suffix if string or symbol is given
- ActiveRecord::Base.table_name_prefix = "prefix_"
- ActiveRecord::Base.table_name_suffix = "_suffix"
- Reminder.reset_table_name
- assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
- assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table)
- ActiveRecord::Base.table_name_prefix = ""
- ActiveRecord::Base.table_name_suffix = ""
- Reminder.reset_table_name
- end
-
- def test_add_drop_table_with_prefix_and_suffix
- assert !Reminder.table_exists?
- ActiveRecord::Base.table_name_prefix = 'prefix_'
- ActiveRecord::Base.table_name_suffix = '_suffix'
- Reminder.reset_table_name
- Reminder.reset_sequence_name
- WeNeedReminders.up
- assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
- assert_equal "hello world", Reminder.find(:first).content
-
- WeNeedReminders.down
- assert_raise(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
- ensure
- ActiveRecord::Base.table_name_prefix = ''
- ActiveRecord::Base.table_name_suffix = ''
- Reminder.reset_table_name
- Reminder.reset_sequence_name
- end
-
- def test_create_table_with_binary_column
- Person.connection.drop_table :binary_testings rescue nil
-
- assert_nothing_raised {
- Person.connection.create_table :binary_testings do |t|
- t.column "data", :binary, :null => false
+ assert_queries(3) do
+ with_bulk_change_table do |t|
+ t.remove_index :name
+ t.index :name, :name => :new_name_index, :unique => true
end
- }
-
- columns = Person.connection.columns(:binary_testings)
- data_column = columns.detect { |c| c.name == "data" }
-
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- assert_equal '', data_column.default
- else
- assert_nil data_column.default
end
- Person.connection.drop_table :binary_testings rescue nil
- end
+ assert ! index(:index_delete_me_on_name)
- def test_migrator_with_duplicates
- assert_raise(ActiveRecord::DuplicateMigrationVersionError) do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate", nil)
- end
- end
-
- def test_migrator_with_duplicate_names
- assert_raise(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate_names", nil)
- end
+ new_name_index = index(:new_name_index)
+ assert new_name_index.unique
end
- def test_migrator_with_missing_version_numbers
- assert_raise(ActiveRecord::UnknownMigrationVersionError) do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500)
- end
- end
-
- def test_create_table_with_custom_sequence_name
- return unless current_adapter? :OracleAdapter
-
- # table name is 29 chars, the standard sequence name will
- # be 33 chars and should be shortened
- assert_nothing_raised do
- begin
- Person.connection.create_table :table_with_name_thats_just_ok do |t|
- t.column :foo, :string, :null => false
- end
- ensure
- Person.connection.drop_table :table_with_name_thats_just_ok rescue nil
- end
+ def test_changing_columns
+ with_bulk_change_table do |t|
+ t.string :name
+ t.date :birthdate
end
- # should be all good w/ a custom sequence name
- assert_nothing_raised do
- begin
- Person.connection.create_table :table_with_name_thats_just_ok,
- :sequence_name => 'suitably_short_seq' do |t|
- t.column :foo, :string, :null => false
- end
+ assert ! column(:name).default
+ assert_equal :date, column(:birthdate).type
- Person.connection.execute("select suitably_short_seq.nextval from dual")
-
- ensure
- Person.connection.drop_table :table_with_name_thats_just_ok,
- :sequence_name => 'suitably_short_seq' rescue nil
+ # One query for columns (delete_me table)
+ # One query for primary key (delete_me table)
+ # One query to do the bulk change
+ assert_queries(3) do
+ with_bulk_change_table do |t|
+ t.change :name, :string, :default => 'NONAME'
+ t.change :birthdate, :datetime
end
end
- # confirm the custom sequence got dropped
- assert_raise(ActiveRecord::StatementInvalid) do
- Person.connection.execute("select suitably_short_seq.nextval from dual")
- end
+ assert_equal 'NONAME', column(:name).default
+ assert_equal :datetime, column(:birthdate).type
end
protected
- def with_env_tz(new_tz = 'US/Eastern')
- old_tz, ENV['TZ'] = ENV['TZ'], new_tz
- yield
- ensure
- old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
- end
-
- end
-
- class SexyMigrationsTest < ActiveRecord::TestCase
- def test_references_column_type_adds_id
- with_new_table do |t|
- t.expects(:column).with('customer_id', :integer, {})
- t.references :customer
- end
- end
- def test_references_column_type_with_polymorphic_adds_type
- with_new_table do |t|
- t.expects(:column).with('taggable_type', :string, {})
- t.expects(:column).with('taggable_id', :integer, {})
- t.references :taggable, :polymorphic => true
- end
- end
+ def with_bulk_change_table
+ # Reset columns/indexes cache as we're changing the table
+ @columns = @indexes = nil
- def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
- with_new_table do |t|
- t.expects(:column).with('taggable_type', :string, {:null => false})
- t.expects(:column).with('taggable_id', :integer, {:null => false})
- t.references :taggable, :polymorphic => true, :null => false
+ Person.connection.change_table(:delete_me, :bulk => true) do |t|
+ yield t
end
end
- def test_belongs_to_works_like_references
- with_new_table do |t|
- t.expects(:column).with('customer_id', :integer, {})
- t.belongs_to :customer
- end
+ def column(name)
+ columns.detect {|c| c.name == name.to_s }
end
- def test_timestamps_creates_updated_at_and_created_at
- with_new_table do |t|
- t.expects(:column).with(:created_at, :datetime, kind_of(Hash))
- t.expects(:column).with(:updated_at, :datetime, kind_of(Hash))
- t.timestamps
- end
+ def columns
+ @columns ||= Person.connection.columns('delete_me')
end
- def test_integer_creates_integer_column
- with_new_table do |t|
- t.expects(:column).with(:foo, 'integer', {})
- t.expects(:column).with(:bar, 'integer', {})
- t.integer :foo, :bar
- end
+ def index(name)
+ indexes.detect {|i| i.name == name.to_s }
end
- def test_string_creates_string_column
- with_new_table do |t|
- t.expects(:column).with(:foo, 'string', {})
- t.expects(:column).with(:bar, 'string', {})
- t.string :foo, :bar
- end
+ def indexes
+ @indexes ||= Person.connection.indexes('delete_me')
end
+ end # AlterTableMigrationsTest
- if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:SQLite3Adapter) || current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
- def test_xml_creates_xml_column
- type = current_adapter?(:PostgreSQLAdapter) ? 'xml' : :text
-
- with_new_table do |t|
- t.expects(:column).with(:data, type, {})
- t.xml :data
- end
- end
- else
- def test_xml_creates_xml_column
- with_new_table do |t|
- assert_raises(NotImplementedError) do
- t.xml :data
- end
- end
- end
- end
-
- protected
- def with_new_table
- Person.connection.create_table :delete_me, :force => true do |t|
- yield t
- end
- ensure
- Person.connection.drop_table :delete_me rescue nil
- end
-
- end # SexyMigrationsTest
+end
- class MigrationLoggerTest < ActiveRecord::TestCase
- def test_migration_should_be_run_without_logger
- previous_logger = ActiveRecord::Base.logger
- ActiveRecord::Base.logger = nil
- assert_nothing_raised do
- ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
- end
- ensure
- ActiveRecord::Base.logger = previous_logger
- end
+class CopyMigrationsTest < ActiveRecord::TestCase
+ def setup
end
- class InterleavedMigrationsTest < ActiveRecord::TestCase
- def test_migrator_interleaved_migrations
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1")
-
- assert_nothing_raised do
- ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2")
- end
-
- Person.reset_column_information
- assert Person.column_methods_hash.include?(:last_name)
-
- assert_nothing_raised do
- proxies = ActiveRecord::Migrator.down(
- MIGRATIONS_ROOT + "/interleaved/pass_3")
- names = proxies.map(&:name)
- assert names.include?('InterleavedPeopleHaveLastNames')
- assert names.include?('InterleavedInnocentJointable')
- end
- end
+ def clear
+ ActiveRecord::Base.timestamped_migrations = true
+ to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations
+ File.delete(*to_delete)
end
- class ReservedWordsMigrationTest < ActiveRecord::TestCase
- def test_drop_index_from_table_named_values
- connection = Person.connection
- connection.create_table :values, :force => true do |t|
- t.integer :value
- end
-
- assert_nothing_raised do
- connection.add_index :values, :value
- connection.remove_index :values, :column => :value
- end
-
- connection.drop_table :values rescue nil
- end
+ def test_copying_migrations_without_timestamps
+ ActiveRecord::Base.timestamped_migrations = false
+ @migrations_path = MIGRATIONS_ROOT + "/valid"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
+
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"})
+ assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb")
+ assert_equal [@migrations_path + "/4_people_have_hobbies.bukkits.rb", @migrations_path + "/5_people_have_descriptions.bukkits.rb"], copied.map(&:filename)
+
+ expected = "# This migration comes from bukkits (originally 1)"
+ assert_equal expected, IO.readlines(@migrations_path + "/4_people_have_hobbies.bukkits.rb")[0].chomp
+
+ files_count = Dir[@migrations_path + "/*.rb"].length
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"})
+ assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
+ assert copied.empty?
+ ensure
+ clear
end
-
- class ChangeTableMigrationsTest < ActiveRecord::TestCase
- def setup
- @connection = Person.connection
- @connection.create_table :delete_me, :force => true do |t|
- end
- end
-
- def teardown
- Person.connection.drop_table :delete_me rescue nil
- end
-
- def test_references_column_type_adds_id
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
- t.references :customer
- end
- end
-
- def test_remove_references_column_type_removes_id
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'customer_id')
- t.remove_references :customer
- end
- end
-
- def test_add_belongs_to_works_like_add_references
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
- t.belongs_to :customer
- end
- end
-
- def test_remove_belongs_to_works_like_remove_references
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'customer_id')
- t.remove_belongs_to :customer
- end
- end
-
- def test_references_column_type_with_polymorphic_adds_type
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {})
- @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {})
- t.references :taggable, :polymorphic => true
- end
- end
-
- def test_remove_references_column_type_with_polymorphic_removes_type
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
- @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
- t.remove_references :taggable, :polymorphic => true
- end
- end
-
- def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null => false})
- @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false})
- t.references :taggable, :polymorphic => true, :null => false
- end
- end
-
- def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
- @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
- t.remove_references :taggable, :polymorphic => true, :null => false
- end
- end
-
- def test_timestamps_creates_updated_at_and_created_at
- with_change_table do |t|
- @connection.expects(:add_timestamps).with(:delete_me)
- t.timestamps
- end
- end
-
- def test_remove_timestamps_creates_updated_at_and_created_at
- with_change_table do |t|
- @connection.expects(:remove_timestamps).with(:delete_me)
- t.remove_timestamps
- end
- end
-
- def string_column
- if current_adapter?(:PostgreSQLAdapter)
- "character varying(255)"
- elsif current_adapter?(:OracleAdapter)
- 'VARCHAR2(255)'
- else
- 'varchar(255)'
- end
- end
-
- def integer_column
- if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
- 'int(11)'
- elsif current_adapter?(:OracleAdapter)
- 'NUMBER(38)'
- else
- 'integer'
- end
- end
-
- def test_integer_creates_integer_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {})
- @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {})
- t.integer :foo, :bar
- end
- end
-
- def test_string_creates_string_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :foo, string_column, {})
- @connection.expects(:add_column).with(:delete_me, :bar, string_column, {})
- t.string :foo, :bar
- end
- end
-
- def test_column_creates_column
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :bar, :integer, {})
- t.column :bar, :integer
- end
- end
-
- def test_column_creates_column_with_options
- with_change_table do |t|
- @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null => false})
- t.column :bar, :integer, :null => false
- end
- end
-
- def test_index_creates_index
- with_change_table do |t|
- @connection.expects(:add_index).with(:delete_me, :bar, {})
- t.index :bar
- end
- end
-
- def test_index_creates_index_with_options
- with_change_table do |t|
- @connection.expects(:add_index).with(:delete_me, :bar, {:unique => true})
- t.index :bar, :unique => true
- end
- end
-
- def test_index_exists
- with_change_table do |t|
- @connection.expects(:index_exists?).with(:delete_me, :bar, {})
- t.index_exists?(:bar)
- end
- end
-
- def test_index_exists_with_options
- with_change_table do |t|
- @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true})
- t.index_exists?(:bar, :unique => true)
- end
- end
-
- def test_change_changes_column
- with_change_table do |t|
- @connection.expects(:change_column).with(:delete_me, :bar, :string, {})
- t.change :bar, :string
- end
- end
-
- def test_change_changes_column_with_options
- with_change_table do |t|
- @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null => true})
- t.change :bar, :string, :null => true
- end
- end
-
- def test_change_default_changes_column
- with_change_table do |t|
- @connection.expects(:change_column_default).with(:delete_me, :bar, :string)
- t.change_default :bar, :string
- end
- end
-
- def test_remove_drops_single_column
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, [:bar])
- t.remove :bar
- end
- end
-
- def test_remove_drops_multiple_columns
- with_change_table do |t|
- @connection.expects(:remove_column).with(:delete_me, [:bar, :baz])
- t.remove :bar, :baz
- end
- end
-
- def test_remove_index_removes_index_with_options
- with_change_table do |t|
- @connection.expects(:remove_index).with(:delete_me, {:unique => true})
- t.remove_index :unique => true
- end
- end
-
- def test_rename_renames_column
- with_change_table do |t|
- @connection.expects(:rename_column).with(:delete_me, :bar, :baz)
- t.rename :bar, :baz
- end
- end
-
- protected
- def with_change_table
- Person.connection.change_table :delete_me do |t|
- yield t
- end
- end
+ def test_copying_migrations_without_timestamps_from_2_sources
+ ActiveRecord::Base.timestamped_migrations = false
+ @migrations_path = MIGRATIONS_ROOT + "/valid"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
+
+ sources = {}
+ sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy"
+ sources[:omg] = MIGRATIONS_ROOT + "/to_copy2"
+ ActiveRecord::Migration.copy(@migrations_path, sources)
+ assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb")
+ assert File.exists?(@migrations_path + "/6_create_articles.omg.rb")
+ assert File.exists?(@migrations_path + "/7_create_comments.omg.rb")
+
+ files_count = Dir[@migrations_path + "/*.rb"].length
+ ActiveRecord::Migration.copy(@migrations_path, sources)
+ assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
+ ensure
+ clear
end
- if ActiveRecord::Base.connection.supports_bulk_alter?
- class BulkAlterTableMigrationsTest < ActiveRecord::TestCase
- def setup
- @connection = Person.connection
- @connection.create_table(:delete_me, :force => true) {|t| }
- end
-
- def teardown
- Person.connection.drop_table(:delete_me) rescue nil
- end
-
- def test_adding_multiple_columns
- assert_queries(1) do
- with_bulk_change_table do |t|
- t.column :name, :string
- t.string :qualification, :experience
- t.integer :age, :default => 0
- t.date :birthdate
- t.timestamps
- end
- end
-
- assert_equal 8, columns.size
- [:name, :qualification, :experience].each {|s| assert_equal :string, column(s).type }
- assert_equal 0, column(:age).default
- end
-
- def test_removing_columns
- with_bulk_change_table do |t|
- t.string :qualification, :experience
- end
-
- [:qualification, :experience].each {|c| assert column(c) }
-
- assert_queries(1) do
- with_bulk_change_table do |t|
- t.remove :qualification, :experience
- t.string :qualification_experience
- end
- end
-
- [:qualification, :experience].each {|c| assert ! column(c) }
- assert column(:qualification_experience)
- end
-
- def test_adding_indexes
- with_bulk_change_table do |t|
- t.string :username
- t.string :name
- t.integer :age
- end
-
- # Adding an index fires a query every time to check if an index already exists or not
- assert_queries(3) do
- with_bulk_change_table do |t|
- t.index :username, :unique => true, :name => :awesome_username_index
- t.index [:name, :age]
- end
- end
-
- assert_equal 2, indexes.size
-
- name_age_index = index(:index_delete_me_on_name_and_age)
- assert_equal ['name', 'age'].sort, name_age_index.columns.sort
- assert ! name_age_index.unique
-
- assert index(:awesome_username_index).unique
- end
-
- def test_removing_index
- with_bulk_change_table do |t|
- t.string :name
- t.index :name
- end
-
- assert index(:index_delete_me_on_name)
-
- assert_queries(3) do
- with_bulk_change_table do |t|
- t.remove_index :name
- t.index :name, :name => :new_name_index, :unique => true
- end
- end
-
- assert ! index(:index_delete_me_on_name)
-
- new_name_index = index(:new_name_index)
- assert new_name_index.unique
- end
-
- def test_changing_columns
- with_bulk_change_table do |t|
- t.string :name
- t.date :birthdate
- end
-
- assert ! column(:name).default
- assert_equal :date, column(:birthdate).type
-
- # One query for columns (delete_me table)
- # One query for primary key (delete_me table)
- # One query to do the bulk change
- assert_queries(3) do
- with_bulk_change_table do |t|
- t.change :name, :string, :default => 'NONAME'
- t.change :birthdate, :datetime
- end
- end
-
- assert_equal 'NONAME', column(:name).default
- assert_equal :datetime, column(:birthdate).type
- end
-
- protected
-
- def with_bulk_change_table
- # Reset columns/indexes cache as we're changing the table
- @columns = @indexes = nil
-
- Person.connection.change_table(:delete_me, :bulk => true) do |t|
- yield t
- end
- end
-
- def column(name)
- columns.detect {|c| c.name == name.to_s }
- end
-
- def columns
- @columns ||= Person.connection.columns('delete_me')
- end
-
- def index(name)
- indexes.detect {|i| i.name == name.to_s }
- end
-
- def indexes
- @indexes ||= Person.connection.indexes('delete_me')
- end
- end # AlterTableMigrationsTest
+ def test_copying_migrations_with_timestamps
+ @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
- end
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
+ expected = [@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb",
+ @migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb"]
+ assert_equal expected, copied.map(&:filename)
- class CopyMigrationsTest < ActiveRecord::TestCase
- def setup
+ files_count = Dir[@migrations_path + "/*.rb"].length
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
+ assert copied.empty?
end
+ ensure
+ clear
+ end
- def clear
- ActiveRecord::Base.timestamped_migrations = true
- to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations
- File.delete(*to_delete)
- end
+ def test_copying_migrations_with_timestamps_from_2_sources
+ @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
- def test_copying_migrations_without_timestamps
- ActiveRecord::Base.timestamped_migrations = false
- @migrations_path = MIGRATIONS_ROOT + "/valid"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
+ sources = {}
+ sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
+ sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2"
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"})
- assert File.exists?(@migrations_path + "/4_people_have_hobbies.rb")
- assert File.exists?(@migrations_path + "/5_people_have_descriptions.rb")
- assert_equal [@migrations_path + "/4_people_have_hobbies.rb", @migrations_path + "/5_people_have_descriptions.rb"], copied.map(&:filename)
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ copied = ActiveRecord::Migration.copy(@migrations_path, sources)
+ assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100726101012_create_articles.omg.rb")
+ assert File.exists?(@migrations_path + "/20100726101013_create_comments.omg.rb")
+ assert_equal 4, copied.length
files_count = Dir[@migrations_path + "/*.rb"].length
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"})
+ ActiveRecord::Migration.copy(@migrations_path, sources)
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
- ensure
- clear
end
+ ensure
+ clear
+ end
- def test_copying_migrations_without_timestamps_from_2_sources
- ActiveRecord::Base.timestamped_migrations = false
- @migrations_path = MIGRATIONS_ROOT + "/valid"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
+ def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future
+ @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
- sources = ActiveSupport::OrderedHash.new
- sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy"
- sources[:omg] = MIGRATIONS_ROOT + "/to_copy2"
- ActiveRecord::Migration.copy(@migrations_path, sources)
- assert File.exists?(@migrations_path + "/4_people_have_hobbies.rb")
- assert File.exists?(@migrations_path + "/5_people_have_descriptions.rb")
- assert File.exists?(@migrations_path + "/6_create_articles.rb")
- assert File.exists?(@migrations_path + "/7_create_comments.rb")
+ Time.travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do
+ ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert File.exists?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb")
files_count = Dir[@migrations_path + "/*.rb"].length
- ActiveRecord::Migration.copy(@migrations_path, sources)
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- ensure
- clear
- end
-
- def test_copying_migrations_with_timestamps
- @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
-
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb")
- assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb")
- expected = [@migrations_path + "/20100726101010_people_have_hobbies.rb",
- @migrations_path + "/20100726101011_people_have_descriptions.rb"]
- assert_equal expected, copied.map(&:filename)
-
- files_count = Dir[@migrations_path + "/*.rb"].length
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
- end
- ensure
- clear
+ assert copied.empty?
end
+ ensure
+ clear
+ end
- def test_copying_migrations_with_timestamps_from_2_sources
- @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
-
- sources = ActiveSupport::OrderedHash.new
- sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
- sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps2"
-
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
- copied = ActiveRecord::Migration.copy(@migrations_path, sources)
- assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb")
- assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb")
- assert File.exists?(@migrations_path + "/20100726101012_create_articles.rb")
- assert File.exists?(@migrations_path + "/20100726101013_create_comments.rb")
- assert_equal 4, copied.length
-
- files_count = Dir[@migrations_path + "/*.rb"].length
- ActiveRecord::Migration.copy(@migrations_path, sources)
- assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- end
- ensure
- clear
- end
+ def test_skipping_migrations
+ @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
- def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future
- @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
+ sources = {}
+ sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
+ sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_name_collision"
- Time.travel_to(Time.utc(2010, 2, 20, 10, 10, 10)) do
- ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert File.exists?(@migrations_path + "/20100301010102_people_have_hobbies.rb")
- assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.rb")
+ skipped = []
+ on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" }
+ copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip)
+ assert_equal 2, copied.length
- files_count = Dir[@migrations_path + "/*.rb"].length
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
- assert copied.empty?
- end
- ensure
- clear
- end
+ assert_equal 1, skipped.length
+ assert_equal ["omg PeopleHaveHobbies"], skipped
+ ensure
+ clear
+ end
- def test_skipping_migrations
- @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
- @existing_migrations = Dir[@migrations_path + "/*.rb"]
+ def test_skip_is_not_called_if_migrations_are_from_the_same_plugin
+ @migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
+ @existing_migrations = Dir[@migrations_path + "/*.rb"]
- sources = ActiveSupport::OrderedHash.new
- sources[:bukkits] = sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
+ sources = {}
+ sources[:bukkits] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
- skipped = []
- on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" }
- copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip)
- assert_equal 2, copied.length
+ skipped = []
+ on_skip = Proc.new { |name, migration| skipped << "#{name} #{migration.name}" }
+ copied = ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip)
+ ActiveRecord::Migration.copy(@migrations_path, sources, :on_skip => on_skip)
- assert_equal 2, skipped.length
- assert_equal ["bukkits PeopleHaveHobbies", "bukkits PeopleHaveDescriptions"], skipped
- ensure
- clear
- end
+ assert_equal 2, copied.length
+ assert_equal 0, skipped.length
+ ensure
+ clear
+ end
- def test_copying_migrations_to_non_existing_directory
- @migrations_path = MIGRATIONS_ROOT + "/non_existing"
- @existing_migrations = []
+ def test_copying_migrations_to_non_existing_directory
+ @migrations_path = MIGRATIONS_ROOT + "/non_existing"
+ @existing_migrations = []
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb")
- assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb")
- assert_equal 2, copied.length
- end
- ensure
- clear
- Dir.delete(@migrations_path)
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
+ assert_equal 2, copied.length
end
+ ensure
+ clear
+ Dir.delete(@migrations_path)
+ end
- def test_copying_migrations_to_empty_directory
- @migrations_path = MIGRATIONS_ROOT + "/empty"
- @existing_migrations = []
+ def test_copying_migrations_to_empty_directory
+ @migrations_path = MIGRATIONS_ROOT + "/empty"
+ @existing_migrations = []
- Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
- copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
- assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.rb")
- assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.rb")
- assert_equal 2, copied.length
- end
- ensure
- clear
+ Time.travel_to(Time.utc(2010, 7, 26, 10, 10, 10)) do
+ copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
+ assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
+ assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
+ assert_equal 2, copied.length
end
+ ensure
+ clear
end
end
diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb
new file mode 100644
index 0000000000..1e16addcf3
--- /dev/null
+++ b/activerecord/test/cases/migrator_test.rb
@@ -0,0 +1,377 @@
+require "cases/helper"
+require "cases/migration/helper"
+
+module ActiveRecord
+ class MigratorTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ # Use this class to sense if migrations have gone
+ # up or down.
+ class Sensor < ActiveRecord::Migration
+ attr_reader :went_up, :went_down
+
+ def initialize name = self.class.name, version = nil
+ super
+ @went_up = false
+ @went_down = false
+ end
+
+ def up; @went_up = true; end
+ def down; @went_down = true; end
+ end
+
+ def setup
+ super
+ ActiveRecord::SchemaMigration.create_table
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ end
+
+ def teardown
+ super
+ ActiveRecord::SchemaMigration.delete_all rescue nil
+ end
+
+ def test_migrator_with_duplicate_names
+ assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do
+ list = [Migration.new('Chunky'), Migration.new('Chunky')]
+ ActiveRecord::Migrator.new(:up, list)
+ end
+ end
+
+ def test_migrator_with_duplicate_versions
+ assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
+ list = [Migration.new('Foo', 1), Migration.new('Bar', 1)]
+ ActiveRecord::Migrator.new(:up, list)
+ end
+ end
+
+ def test_migrator_with_missing_version_numbers
+ assert_raises(ActiveRecord::UnknownMigrationVersionError) do
+ list = [Migration.new('Foo', 1), Migration.new('Bar', 2)]
+ ActiveRecord::Migrator.new(:up, list, 3).run
+ end
+ end
+
+ def test_finds_migrations
+ migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid")
+
+ [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i|
+ assert_equal migrations[i].version, pair.first
+ assert_equal migrations[i].name, pair.last
+ end
+ end
+
+ def test_finds_migrations_in_subdirectories
+ migrations = ActiveRecord::Migrator.migrations(MIGRATIONS_ROOT + "/valid_with_subdirectories")
+
+ [[1, 'ValidPeopleHaveLastNames'], [2, 'WeNeedReminders'], [3, 'InnocentJointable']].each_with_index do |pair, i|
+ assert_equal migrations[i].version, pair.first
+ assert_equal migrations[i].name, pair.last
+ end
+ end
+
+ def test_finds_migrations_from_two_directories
+ directories = [MIGRATIONS_ROOT + '/valid_with_timestamps', MIGRATIONS_ROOT + '/to_copy_with_timestamps']
+ migrations = ActiveRecord::Migrator.migrations directories
+
+ [[20090101010101, "PeopleHaveHobbies"],
+ [20090101010202, "PeopleHaveDescriptions"],
+ [20100101010101, "ValidWithTimestampsPeopleHaveLastNames"],
+ [20100201010101, "ValidWithTimestampsWeNeedReminders"],
+ [20100301010101, "ValidWithTimestampsInnocentJointable"]].each_with_index do |pair, i|
+ assert_equal pair.first, migrations[i].version
+ assert_equal pair.last, migrations[i].name
+ end
+ end
+
+ def test_deprecated_constructor
+ assert_deprecated do
+ ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid")
+ end
+ end
+
+ def test_relative_migrations
+ list = Dir.chdir(MIGRATIONS_ROOT) do
+ ActiveRecord::Migrator.migrations("valid/")
+ end
+
+ migration_proxy = list.find { |item|
+ item.name == 'ValidPeopleHaveLastNames'
+ }
+ assert migration_proxy, 'should find pending migration'
+ end
+
+ def test_finds_pending_migrations
+ ActiveRecord::SchemaMigration.create!(:version => '1')
+ migration_list = [ Migration.new('foo', 1), Migration.new('bar', 3) ]
+ migrations = ActiveRecord::Migrator.new(:up, migration_list).pending_migrations
+
+ assert_equal 1, migrations.size
+ assert_equal migration_list.last, migrations.first
+ end
+
+ def test_migrator_interleaved_migrations
+ pass_one = [Sensor.new('One', 1)]
+
+ ActiveRecord::Migrator.new(:up, pass_one).migrate
+ assert pass_one.first.went_up
+ refute pass_one.first.went_down
+
+ pass_two = [Sensor.new('One', 1), Sensor.new('Three', 3)]
+ ActiveRecord::Migrator.new(:up, pass_two).migrate
+ refute pass_two[0].went_up
+ assert pass_two[1].went_up
+ assert pass_two.all? { |x| !x.went_down }
+
+ pass_three = [Sensor.new('One', 1),
+ Sensor.new('Two', 2),
+ Sensor.new('Three', 3)]
+
+ ActiveRecord::Migrator.new(:down, pass_three).migrate
+ assert pass_three[0].went_down
+ refute pass_three[1].went_down
+ assert pass_three[2].went_down
+ end
+
+ def test_up_calls_up
+ migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ assert migrations.all? { |m| m.went_up }
+ assert migrations.all? { |m| !m.went_down }
+ assert_equal 2, ActiveRecord::Migrator.current_version
+ end
+
+ def test_down_calls_down
+ test_up_calls_up
+
+ migrations = [Sensor.new(nil, 0), Sensor.new(nil, 1), Sensor.new(nil, 2)]
+ ActiveRecord::Migrator.new(:down, migrations).migrate
+ assert migrations.all? { |m| !m.went_up }
+ assert migrations.all? { |m| m.went_down }
+ assert_equal 0, ActiveRecord::Migrator.current_version
+ end
+
+ def test_current_version
+ ActiveRecord::SchemaMigration.create!(:version => '1000')
+ assert_equal 1000, ActiveRecord::Migrator.current_version
+ end
+
+ def test_migrator_one_up
+ calls, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:up, migrations, 2).migrate
+ assert_equal [[:up, 2]], calls
+ end
+
+ def test_migrator_one_down
+ calls, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations).migrate
+ assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:down, migrations, 1).migrate
+
+ assert_equal [[:down, 3], [:down, 2]], calls
+ end
+
+ def test_migrator_one_up_one_down
+ calls, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal [[:down, 1]], calls
+ end
+
+ def test_migrator_double_up
+ calls, migrations = sensors(3)
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [], calls
+ end
+
+ def test_migrator_double_down
+ calls, migrations = sensors(3)
+
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).run
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:down, migrations, 1).run
+ assert_equal [[:down, 1]], calls
+ calls.clear
+
+ ActiveRecord::Migrator.new(:down, migrations, 1).run
+ assert_equal [], calls
+
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ end
+
+ def test_migrator_verbosity
+ _, migrations = sensors(3)
+
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_not_equal 0, ActiveRecord::Migration.message_count
+
+ ActiveRecord::Migration.message_count = 0
+
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_not_equal 0, ActiveRecord::Migration.message_count
+ ActiveRecord::Migration.message_count = 0
+ end
+
+ def test_migrator_verbosity_off
+ _, migrations = sensors(3)
+
+ ActiveRecord::Migration.message_count = 0
+ ActiveRecord::Migration.verbose = false
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal 0, ActiveRecord::Migration.message_count
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal 0, ActiveRecord::Migration.message_count
+ end
+
+ def test_target_version_zero_should_run_only_once
+ calls, migrations = sensors(3)
+
+ # migrate up to 1
+ ActiveRecord::Migrator.new(:up, migrations, 1).migrate
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ # migrate down to 0
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal [[:down, 1]], calls
+ calls.clear
+
+ # migrate down to 0 again
+ ActiveRecord::Migrator.new(:down, migrations, 0).migrate
+ assert_equal [], calls
+ end
+
+ def test_migrator_going_down_due_to_version_target
+ calls, migrator = migrator_class(3)
+
+ migrator.up("valid", 1)
+ assert_equal [[:up, 1]], calls
+ calls.clear
+
+ migrator.migrate("valid", 0)
+ assert_equal [[:down, 1]], calls
+ calls.clear
+
+ migrator.migrate("valid")
+ assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls
+ end
+
+ def test_migrator_rollback
+ _, migrator = migrator_class(3)
+
+ migrator.migrate("valid")
+ assert_equal(3, ActiveRecord::Migrator.current_version)
+
+ migrator.rollback("valid")
+ assert_equal(2, ActiveRecord::Migrator.current_version)
+
+ migrator.rollback("valid")
+ assert_equal(1, ActiveRecord::Migrator.current_version)
+
+ migrator.rollback("valid")
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+
+ migrator.rollback("valid")
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ end
+
+ def test_migrator_db_has_no_schema_migrations_table
+ _, migrator = migrator_class(3)
+
+ ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations")
+ refute ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ migrator.migrate("valid", 1)
+ assert ActiveRecord::Base.connection.table_exists?('schema_migrations')
+ end
+
+ def test_migrator_forward
+ _, migrator = migrator_class(3)
+ migrator.migrate("/valid", 1)
+ assert_equal(1, ActiveRecord::Migrator.current_version)
+
+ migrator.forward("/valid", 2)
+ assert_equal(3, ActiveRecord::Migrator.current_version)
+
+ migrator.forward("/valid")
+ assert_equal(3, ActiveRecord::Migrator.current_version)
+ end
+
+ def test_only_loads_pending_migrations
+ # migrate up to 1
+ ActiveRecord::SchemaMigration.create!(:version => '1')
+
+ calls, migrator = migrator_class(3)
+ migrator.migrate("valid", nil)
+
+ assert_equal [[:up, 2], [:up, 3]], calls
+ end
+
+ def test_get_all_versions
+ _, migrator = migrator_class(3)
+
+ migrator.migrate("valid")
+ assert_equal([1,2,3], ActiveRecord::Migrator.get_all_versions)
+
+ migrator.rollback("valid")
+ assert_equal([1,2], ActiveRecord::Migrator.get_all_versions)
+
+ migrator.rollback("valid")
+ assert_equal([1], ActiveRecord::Migrator.get_all_versions)
+
+ migrator.rollback("valid")
+ assert_equal([], ActiveRecord::Migrator.get_all_versions)
+ end
+
+ private
+ def m(name, version, &block)
+ x = Sensor.new name, version
+ x.extend(Module.new {
+ define_method(:up) { block.call(:up, x); super() }
+ define_method(:down) { block.call(:down, x); super() }
+ }) if block_given?
+ end
+
+ def sensors(count)
+ calls = []
+ migrations = count.times.map { |i|
+ m(nil, i + 1) { |c,migration|
+ calls << [c, migration.version]
+ }
+ }
+ [calls, migrations]
+ end
+
+ def migrator_class(count)
+ calls, migrations = sensors(count)
+
+ migrator = Class.new(Migrator).extend(Module.new {
+ define_method(:migrations) { |paths|
+ migrations
+ }
+ })
+ [calls, migrator]
+ end
+ end
+end
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index a2041af16a..a03c4f552e 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -27,19 +27,19 @@ class ModulesTest < ActiveRecord::TestCase
end
def test_module_spanning_associations
- firm = MyApplication::Business::Firm.find(:first)
+ firm = MyApplication::Business::Firm.first
assert !firm.clients.empty?, "Firm should have clients"
assert_nil firm.class.table_name.match('::'), "Firm shouldn't have the module appear in its table name"
end
def test_module_spanning_has_and_belongs_to_many_associations
- project = MyApplication::Business::Project.find(:first)
+ project = MyApplication::Business::Project.first
project.developers << MyApplication::Business::Developer.create("name" => "John")
assert_equal "John", project.developers.last.name
end
def test_associations_spanning_cross_modules
- account = MyApplication::Billing::Account.find(:first, :order => 'id')
+ account = MyApplication::Billing::Account.scoped(:order => 'id').first
assert_kind_of MyApplication::Business::Firm, account.firm
assert_kind_of MyApplication::Billing::Firm, account.qualified_billing_firm
assert_kind_of MyApplication::Billing::Firm, account.unqualified_billing_firm
@@ -48,7 +48,7 @@ class ModulesTest < ActiveRecord::TestCase
end
def test_find_account_and_include_company
- account = MyApplication::Billing::Account.find(1, :include => :firm)
+ account = MyApplication::Billing::Account.scoped(:includes => :firm).find(1)
assert_kind_of MyApplication::Business::Firm, account.firm
end
@@ -72,8 +72,8 @@ class ModulesTest < ActiveRecord::TestCase
clients = []
assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
- clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL')
- clients << MyApplication::Business::Client.find(3, :include => {:firm => :account})
+ clients << MyApplication::Business::Client.references(:accounts).scoped(:includes => {:firm => :account}, :where => 'accounts.id IS NOT NULL').find(3)
+ clients << MyApplication::Business::Client.scoped(:includes => {:firm => :account}).find(3)
end
clients.each do |client|
@@ -123,7 +123,7 @@ class ModulesTest < ActiveRecord::TestCase
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = true
- collection = Shop::Collection.find(:first)
+ collection = Shop::Collection.first
assert !collection.products.empty?, "Collection should have products"
assert_nothing_raised { collection.destroy }
ensure
@@ -134,7 +134,7 @@ class ModulesTest < ActiveRecord::TestCase
old = ActiveRecord::Base.store_full_sti_class
ActiveRecord::Base.store_full_sti_class = true
- product = Shop::Product.find(:first)
+ product = Shop::Product.first
assert !product.variants.empty?, "Product should have variants"
assert_nothing_raised { product.destroy }
ensure
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index bd51388e05..06d6596725 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -10,6 +10,7 @@ class MultipleDbTest < ActiveRecord::TestCase
def setup
@courses = create_fixtures("courses") { Course.retrieve_connection }
+ @colleges = create_fixtures("colleges") { College.retrieve_connection }
@entrants = create_fixtures("entrants")
end
@@ -85,7 +86,25 @@ class MultipleDbTest < ActiveRecord::TestCase
end
def test_arel_table_engines
+ assert_not_equal Entrant.arel_engine, Bird.arel_engine
assert_not_equal Entrant.arel_engine, Course.arel_engine
- assert_equal Entrant.arel_engine, Bird.arel_engine
+ end
+
+ def test_connection
+ assert_equal Entrant.arel_engine.connection, Bird.arel_engine.connection
+ assert_not_equal Entrant.arel_engine.connection, Course.arel_engine.connection
+ end
+
+ unless in_memory_db?
+ def test_associations_should_work_when_model_has_no_connection
+ begin
+ ActiveRecord::Model.remove_connection
+ assert_nothing_raised ActiveRecord::ConnectionNotEstablished do
+ College.first.courses.first
+ end
+ ensure
+ ActiveRecord::Model.establish_connection 'arunit'
+ end
+ end
end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 4a09a87322..bf825c002a 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -1,5 +1,4 @@
require "cases/helper"
-require 'active_support/core_ext/array/random_access'
require 'models/post'
require 'models/topic'
require 'models/comment'
@@ -11,12 +10,12 @@ class NamedScopeTest < ActiveRecord::TestCase
fixtures :posts, :authors, :topics, :comments, :author_addresses
def test_implements_enumerable
- assert !Topic.find(:all).empty?
+ assert !Topic.all.empty?
- assert_equal Topic.find(:all), Topic.base
- assert_equal Topic.find(:all), Topic.base.to_a
- assert_equal Topic.find(:first), Topic.base.first
- assert_equal Topic.find(:all), Topic.base.map { |i| i }
+ assert_equal Topic.all, Topic.base
+ assert_equal Topic.all, Topic.base.to_a
+ assert_equal Topic.first, Topic.base.first
+ assert_equal Topic.all, Topic.base.map { |i| i }
end
def test_found_items_are_cached
@@ -39,10 +38,10 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_delegates_finds_and_calculations_to_the_base_class
- assert !Topic.find(:all).empty?
+ assert !Topic.all.empty?
- assert_equal Topic.find(:all), Topic.base.find(:all)
- assert_equal Topic.find(:first), Topic.base.find(:first)
+ assert_equal Topic.all, Topic.base.all
+ assert_equal Topic.first, Topic.base.first
assert_equal Topic.count, Topic.base.count
assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count)
end
@@ -59,10 +58,10 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified
- assert !Topic.find(:all, :conditions => {:approved => true}).empty?
+ assert !Topic.scoped(:where => {:approved => true}).all.empty?
- assert_equal Topic.find(:all, :conditions => {:approved => true}), Topic.approved
- assert_equal Topic.count(:conditions => {:approved => true}), Topic.approved.count
+ assert_equal Topic.scoped(:where => {:approved => true}).all, Topic.approved
+ assert_equal Topic.where(:approved => true).count, Topic.approved.count
end
def test_scopes_with_string_name_can_be_composed
@@ -71,13 +70,9 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal Topic.replied.approved, Topic.replied.approved_as_string
end
- def test_scopes_can_be_specified_with_deep_hash_conditions
- assert_equal Topic.replied.approved, Topic.replied.approved_as_hash_condition
- end
-
def test_scopes_are_composable
- assert_equal((approved = Topic.find(:all, :conditions => {:approved => true})), Topic.approved)
- assert_equal((replied = Topic.find(:all, :conditions => 'replies_count > 0')), Topic.replied)
+ assert_equal((approved = Topic.scoped(:where => {:approved => true}).all), Topic.approved)
+ assert_equal((replied = Topic.scoped(:where => 'replies_count > 0').all), Topic.replied)
assert !(approved == replied)
assert !(approved & replied).empty?
@@ -85,8 +80,8 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_procedural_scopes
- topics_written_before_the_third = Topic.find(:all, :conditions => ['written_on < ?', topics(:third).written_on])
- topics_written_before_the_second = Topic.find(:all, :conditions => ['written_on < ?', topics(:second).written_on])
+ topics_written_before_the_third = Topic.where('written_on < ?', topics(:third).written_on)
+ topics_written_before_the_second = Topic.where('written_on < ?', topics(:second).written_on)
assert_not_equal topics_written_before_the_second, topics_written_before_the_third
assert_equal topics_written_before_the_third, Topic.written_before(topics(:third).written_on)
@@ -94,46 +89,17 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_procedural_scopes_returning_nil
- all_topics = Topic.find(:all)
+ all_topics = Topic.all
assert_equal all_topics, Topic.written_before(nil)
end
- def test_scopes_with_joins
- address = author_addresses(:david_address)
- posts_with_authors_at_address = Post.find(
- :all, :joins => 'JOIN authors ON authors.id = posts.author_id',
- :conditions => [ 'authors.author_address_id = ?', address.id ]
- )
- assert_equal posts_with_authors_at_address, Post.with_authors_at_address(address)
- end
-
- def test_scopes_with_joins_respects_custom_select
- address = author_addresses(:david_address)
- posts_with_authors_at_address_titles = Post.find(:all,
- :select => 'title',
- :joins => 'JOIN authors ON authors.id = posts.author_id',
- :conditions => [ 'authors.author_address_id = ?', address.id ]
- )
- assert_equal posts_with_authors_at_address_titles.map(&:title), Post.with_authors_at_address(address).find(:all, :select => 'title').map(&:title)
- end
-
def test_scope_with_object
objects = Topic.with_object
assert_operator objects.length, :>, 0
assert objects.all?(&:approved?), 'all objects should be approved'
end
- def test_extensions
- assert_equal 1, Topic.anonymous_extension.one
- assert_equal 2, Topic.named_extension.two
- end
-
- def test_multiple_extensions
- assert_equal 2, Topic.multiple_extensions.extension_two
- assert_equal 1, Topic.multiple_extensions.extension_one
- end
-
def test_has_many_associations_have_access_to_scopes
assert_not_equal Post.containing_the_letter_a, authors(:david).posts
assert !Post.containing_the_letter_a.empty?
@@ -164,20 +130,16 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_active_records_have_scope_named__all__
- assert !Topic.find(:all).empty?
+ assert !Topic.all.empty?
- assert_equal Topic.find(:all), Topic.base
+ assert_equal Topic.all, Topic.base
end
def test_active_records_have_scope_named__scoped__
- assert !Topic.find(:all, scope = {:conditions => "content LIKE '%Have%'"}).empty?
+ scope = Topic.where("content LIKE '%Have%'")
+ assert !scope.empty?
- assert_equal Topic.find(:all, scope), Topic.scoped(scope)
- end
-
- def test_first_and_last_should_support_find_options
- assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title')
- assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title')
+ assert_equal scope, Topic.scoped(where: "content LIKE '%Have%'")
end
def test_first_and_last_should_allow_integers_for_limit
@@ -194,15 +156,6 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
- def test_first_and_last_find_options_should_use_query_when_results_are_loaded
- topics = Topic.base
- topics.reload # force load
- assert_queries(2) do
- topics.first(:order => 'title')
- topics.last(:order => 'title')
- end
- end
-
def test_empty_should_not_load_results
topics = Topic.base
assert_queries(2) do
@@ -265,9 +218,9 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_many_should_return_false_if_none_or_one
- topics = Topic.base.scoped(:conditions => {:id => 0})
+ topics = Topic.base.where(:id => 0)
assert !topics.many?
- topics = Topic.base.scoped(:conditions => {:id => 1})
+ topics = Topic.base.where(:id => 1)
assert !topics.many?
end
@@ -319,7 +272,7 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_should_use_where_in_query_for_scope
- assert_equal Developer.find_all_by_name('Jamis').to_set, Developer.find_all_by_id(Developer.jamises).to_set
+ assert_equal Developer.where(name: 'Jamis').to_set, Developer.where(id: Developer.jamises).to_set
end
def test_size_should_use_count_when_results_are_not_loaded
@@ -337,10 +290,15 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
+ def test_should_not_duplicates_where_values
+ where_values = Topic.where("1=1").scope_with_lambda.where_values
+ assert_equal ["1=1"], where_values
+ end
+
def test_chaining_with_duplicate_joins
join = "INNER JOIN comments ON comments.post_id = posts.id"
post = Post.find(1)
- assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size
+ assert_equal post.comments.size, Post.joins(join).joins(join).where("posts.id = #{post.id}").size
end
def test_chaining_should_use_latest_conditions_when_creating
@@ -389,25 +347,6 @@ class NamedScopeTest < ActiveRecord::TestCase
end
end
- def test_scopes_with_reserved_names
- class << Topic
- def public_method; end
- public :public_method
-
- def protected_method; end
- protected :protected_method
-
- def private_method; end
- private :private_method
- end
-
- [:public_method, :protected_method, :private_method].each do |reserved_method|
- assert Topic.respond_to?(reserved_method, true)
- ActiveRecord::Base.logger.expects(:warn)
- Topic.scope(reserved_method)
- end
- end
-
def test_scopes_on_relations
# Topic.replied
approved_topics = Topic.scoped.approved.order('id DESC')
@@ -479,42 +418,24 @@ class NamedScopeTest < ActiveRecord::TestCase
require "models/without_table"
end
end
-end
-
-class DynamicScopeMatchTest < ActiveRecord::TestCase
- def test_scoped_by_no_match
- assert_nil ActiveRecord::DynamicScopeMatch.match("not_scoped_at_all")
- end
-
- def test_scoped_by
- match = ActiveRecord::DynamicScopeMatch.match("scoped_by_age_and_sex_and_location")
- assert_not_nil match
- assert match.scope?
- assert_equal %w(age sex location), match.attribute_names
- end
-end
-class DynamicScopeTest < ActiveRecord::TestCase
- fixtures :posts
+ def test_eager_scopes_are_deprecated
+ klass = Class.new(ActiveRecord::Base)
+ klass.table_name = 'posts'
- def setup
- @test_klass = Class.new(Post) do
- def self.name; "Post"; end
+ assert_deprecated do
+ klass.scope :welcome_2, klass.where(:id => posts(:welcome).id)
end
+ assert_equal [posts(:welcome).title], klass.welcome_2.map(&:title)
end
- def test_dynamic_scope
- assert_equal @test_klass.scoped_by_author_id(1).find(1), @test_klass.find(1)
- assert_equal @test_klass.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, @test_klass.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
- end
-
- def test_dynamic_scope_should_create_methods_after_hitting_method_missing
- assert_blank @test_klass.methods.grep(/scoped_by_type/)
- @test_klass.scoped_by_type(nil)
- assert_present @test_klass.methods.grep(/scoped_by_type/)
- end
+ def test_eager_default_scope_relations_are_deprecated
+ klass = Class.new(ActiveRecord::Base)
+ klass.table_name = 'posts'
- def test_dynamic_scope_with_less_number_of_arguments
- assert_raise(ArgumentError){ @test_klass.scoped_by_author_id_and_title(1) }
+ assert_deprecated do
+ klass.send(:default_scope, klass.where(:id => posts(:welcome).id))
+ end
+ assert_equal [posts(:welcome).title], klass.all.map(&:title)
end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 2ae9cb4888..0559bbbe9a 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -172,6 +172,19 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
man.interests_attributes = [{:id => interest.id, :topic => 'gardening'}]
assert_equal man.interests.first.topic, man.interests[0].topic
end
+
+ def test_allows_class_to_override_setter_and_call_super
+ mean_pirate_class = Class.new(Pirate) do
+ accepts_nested_attributes_for :parrot
+ def parrot_attributes=(attrs)
+ super(attrs.merge(:color => "blue"))
+ end
+ end
+ mean_pirate = mean_pirate_class.new
+ mean_pirate.parrot_attributes = { :name => "James" }
+ assert_equal "James", mean_pirate.parrot.name
+ assert_equal "blue", mean_pirate.parrot.color
+ end
end
class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
@@ -663,7 +676,7 @@ module NestedAttributesOnACollectionAssociationTests
end
def test_should_sort_the_hash_by_the_keys_before_building_new_associated_models
- attributes = ActiveSupport::OrderedHash.new
+ attributes = {}
attributes['123726353'] = { :name => 'Grace OMalley' }
attributes['2'] = { :name => 'Privateers Greed' } # 2 is lower then 123726353
@pirate.send(association_setter, attributes)
@@ -673,7 +686,7 @@ module NestedAttributesOnACollectionAssociationTests
def test_should_raise_an_argument_error_if_something_else_than_a_hash_is_passed
assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) }
- assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, ActiveSupport::OrderedHash.new) }
+ assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, Hash.new) }
assert_raise_with_message ArgumentError, 'Hash or Array expected, got String ("foo")' do
@pirate.send(association_setter, "foo")
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index adfd8e83a1..0933a4ff3d 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -13,12 +13,14 @@ require 'models/warehouse_thing'
require 'models/parrot'
require 'models/minivan'
require 'models/person'
+require 'models/pet'
+require 'models/toy'
require 'rexml/document'
require 'active_support/core_ext/exception'
class PersistencesTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans, :pets, :toys
# Oracle UPDATE does not support ORDER BY
unless current_adapter?(:OracleAdapter)
@@ -53,7 +55,7 @@ class PersistencesTest < ActiveRecord::TestCase
author = authors(:david)
assert_nothing_raised do
assert_equal 1, author.posts_sorted_by_id_limited.size
- assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size
+ assert_equal 2, author.posts_sorted_by_id_limited.scoped(:limit => 2).all.size
assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ])
assert_equal "bulk update!", posts(:welcome).body
assert_not_equal "bulk update!", posts(:thinking).body
@@ -76,10 +78,20 @@ class PersistencesTest < ActiveRecord::TestCase
assert_equal Topic.count, Topic.delete_all
end
- def test_update_by_condition
- Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
- assert_equal "Have a nice day", Topic.find(1).content
- assert_equal "bulk updated!", Topic.find(2).content
+ def test_delete_all_with_joins_and_where_part_is_hash
+ where_args = {:toys => {:name => 'Bone'}}
+ count = Pet.joins(:toys).where(where_args).count
+
+ assert_equal count, 1
+ assert_equal count, Pet.joins(:toys).where(where_args).delete_all
+ end
+
+ def test_delete_all_with_joins_and_where_part_is_not_hash
+ where_args = ['toys.name = ?', 'Bone']
+ count = Pet.joins(:toys).where(where_args).count
+
+ assert_equal count, 1
+ assert_equal count, Pet.joins(:toys).where(where_args).delete_all
end
def test_increment_attribute
@@ -108,7 +120,7 @@ class PersistencesTest < ActiveRecord::TestCase
def test_destroy_all
conditions = "author_name = 'Mary'"
- topics_by_mary = Topic.all(:conditions => conditions, :order => 'id')
+ topics_by_mary = Topic.scoped(:where => conditions, :order => 'id').to_a
assert ! topics_by_mary.empty?
assert_difference('Topic.count', -topics_by_mary.size) do
@@ -119,7 +131,7 @@ class PersistencesTest < ActiveRecord::TestCase
end
def test_destroy_many
- clients = Client.find([2, 3], :order => 'id')
+ clients = Client.scoped(:order => 'id').find([2, 3])
assert_difference('Client.count', -2) do
destroyed = Client.destroy([2, 3]).sort_by(&:id)
@@ -320,7 +332,7 @@ class PersistencesTest < ActiveRecord::TestCase
end
def test_update_all_with_non_standard_table_name
- assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1])
+ assert_equal 1, WarehouseThing.where(id: 1).update_all(['value = ?', 0])
assert_equal 0, WarehouseThing.find(1).value
end
@@ -393,7 +405,6 @@ class PersistencesTest < ActiveRecord::TestCase
def test_update_attribute_with_one_updated
t = Topic.first
- title = t.title
t.update_attribute(:title, 'super_title')
assert_equal 'super_title', t.title
assert !t.changed?, "topic should not have changed"
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index 434b8a677a..fba3006ebe 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -7,17 +7,17 @@ class PooledConnectionsTest < ActiveRecord::TestCase
def setup
@per_test_teardown = []
- @connection = ActiveRecord::Base.remove_connection
+ @connection = ActiveRecord::Model.remove_connection
end
def teardown
- ActiveRecord::Base.clear_all_connections!
- ActiveRecord::Base.establish_connection(@connection)
+ ActiveRecord::Model.clear_all_connections!
+ ActiveRecord::Model.establish_connection(@connection)
@per_test_teardown.each {|td| td.call }
end
def checkout_connections
- ActiveRecord::Base.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3}))
+ ActiveRecord::Model.establish_connection(@connection.merge({:pool => 2, :wait_timeout => 0.3}))
@connections = []
@timed_out = 0
@@ -33,24 +33,16 @@ class PooledConnectionsTest < ActiveRecord::TestCase
end
# Will deadlock due to lack of Monitor timeouts in 1.9
- if RUBY_VERSION < '1.9'
- def test_pooled_connection_checkout
- checkout_connections
- assert_equal 2, @connections.length
- assert_equal 2, @timed_out
- end
- end
-
def checkout_checkin_connections(pool_size, threads)
- ActiveRecord::Base.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5}))
+ ActiveRecord::Model.establish_connection(@connection.merge({:pool => pool_size, :wait_timeout => 0.5}))
@connection_count = 0
@timed_out = 0
threads.times do
Thread.new do
begin
- conn = ActiveRecord::Base.connection_pool.checkout
+ conn = ActiveRecord::Model.connection_pool.checkout
sleep 0.1
- ActiveRecord::Base.connection_pool.checkin conn
+ ActiveRecord::Model.connection_pool.checkin conn
@connection_count += 1
rescue ActiveRecord::ConnectionTimeoutError
@timed_out += 1
@@ -63,85 +55,13 @@ class PooledConnectionsTest < ActiveRecord::TestCase
checkout_checkin_connections 1, 2
assert_equal 2, @connection_count
assert_equal 0, @timed_out
- assert_equal 1, ActiveRecord::Base.connection_pool.connections.size
+ assert_equal 1, ActiveRecord::Model.connection_pool.connections.size
end
- def test_pooled_connection_checkin_two
- checkout_checkin_connections 2, 3
- assert_equal 3, @connection_count
- assert_equal 0, @timed_out
- assert_equal 1, ActiveRecord::Base.connection_pool.connections.size
- end
-
- def test_pooled_connection_checkout_existing_first
- ActiveRecord::Base.establish_connection(@connection.merge({:pool => 1}))
- conn_pool = ActiveRecord::Base.connection_pool
- conn = conn_pool.checkout
- conn_pool.checkin(conn)
- conn = conn_pool.checkout
- assert ActiveRecord::ConnectionAdapters::AbstractAdapter === conn
- conn_pool.checkin(conn)
- end
-
- def test_not_connected_defined_connection_returns_false
- ActiveRecord::Base.establish_connection(@connection)
- assert ! ActiveRecord::Base.connected?
- end
-
- def test_undefined_connection_returns_false
- old_handler = ActiveRecord::Base.connection_handler
- ActiveRecord::Base.connection_handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
- assert ! ActiveRecord::Base.connected?
- ensure
- ActiveRecord::Base.connection_handler = old_handler
- end
-
- def test_connection_config
- ActiveRecord::Base.establish_connection(@connection)
- assert_equal @connection, ActiveRecord::Base.connection_config
- end
-
- def test_with_connection_nesting_safety
- ActiveRecord::Base.establish_connection(@connection.merge({:pool => 1, :wait_timeout => 0.1}))
-
- before_count = Project.count
-
- add_record('one')
-
- ActiveRecord::Base.connection.transaction do
- add_record('two')
- # Have another thread try to screw up the transaction
- Thread.new do
- ActiveRecord::Base.connection.rollback_db_transaction
- ActiveRecord::Base.connection_pool.release_connection
- end
- add_record('three')
- end
-
- after_count = Project.count
- assert_equal 3, after_count - before_count
- end
-
- def test_connection_pool_callbacks
- checked_out, checked_in = false, false
- ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
- set_callback(:checkout, :after) { checked_out = true }
- set_callback(:checkin, :before) { checked_in = true }
- end
- @per_test_teardown << proc do
- ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
- reset_callbacks :checkout
- reset_callbacks :checkin
- end
- end
- checkout_checkin_connections 1, 1
- assert checked_out
- assert checked_in
- end
private
def add_record(name)
- ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name }
+ ActiveRecord::Model.connection_pool.with_connection { Project.create! :name => name }
end
end unless current_adapter?(:FrontBase) || in_memory_db?
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 4bb5752096..e6e50a4cd4 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -23,6 +23,11 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_equal keyboard.to_key, [keyboard.id]
end
+ def test_read_attribute_with_custom_primary_key
+ keyboard = Keyboard.create!
+ assert_equal keyboard.key_number, keyboard.read_attribute(:id)
+ end
+
def test_to_key_with_primary_key_after_destroy
topic = Topic.find(1)
topic.destroy
@@ -142,8 +147,38 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_equal k.connection.quote_column_name("id"), k.quoted_primary_key
k.primary_key = "foo"
assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key
- k.set_primary_key "bar"
- assert_equal k.connection.quote_column_name("bar"), k.quoted_primary_key
+ end
+
+ def test_two_models_with_same_table_but_different_primary_key
+ k1 = Class.new(ActiveRecord::Base)
+ k1.table_name = 'posts'
+ k1.primary_key = 'id'
+
+ k2 = Class.new(ActiveRecord::Base)
+ k2.table_name = 'posts'
+ k2.primary_key = 'title'
+
+ assert k1.columns.find { |c| c.name == 'id' }.primary
+ assert !k1.columns.find { |c| c.name == 'title' }.primary
+ assert k1.columns_hash['id'].primary
+ assert !k1.columns_hash['title'].primary
+
+ assert !k2.columns.find { |c| c.name == 'id' }.primary
+ assert k2.columns.find { |c| c.name == 'title' }.primary
+ assert !k2.columns_hash['id'].primary
+ assert k2.columns_hash['title'].primary
+ end
+
+ def test_models_with_same_table_have_different_columns
+ k1 = Class.new(ActiveRecord::Base)
+ k1.table_name = 'posts'
+
+ k2 = Class.new(ActiveRecord::Base)
+ k2.table_name = 'posts'
+
+ k1.columns.zip(k2.columns).each do |col1, col2|
+ assert !col1.equal?(col2)
+ end
end
end
@@ -153,16 +188,31 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase
def test_set_primary_key_with_no_connection
return skip("disconnect wipes in-memory db") if in_memory_db?
- connection = ActiveRecord::Base.remove_connection
+ connection = ActiveRecord::Model.remove_connection
- model = Class.new(ActiveRecord::Base) do
- set_primary_key 'foo'
- end
+ model = Class.new(ActiveRecord::Base)
+ model.primary_key = 'foo'
assert_equal 'foo', model.primary_key
- ActiveRecord::Base.establish_connection(connection)
+ ActiveRecord::Model.establish_connection(connection)
assert_equal 'foo', model.primary_key
end
end
+
+if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
+ class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def test_primaery_key_method_with_ansi_quotes
+ con = ActiveRecord::Base.connection
+ con.execute("SET SESSION sql_mode='ANSI_QUOTES'")
+ assert_equal "id", con.primary_key("topics")
+ ensure
+ con.reconnect!
+ end
+
+ end
+end
+
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index b2429d631f..a712e5f689 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -3,7 +3,7 @@ require 'models/topic'
require 'models/task'
require 'models/category'
require 'models/post'
-
+require 'rack'
class QueryCacheTest < ActiveRecord::TestCase
fixtures :tasks, :topics, :categories, :posts, :categories_posts
@@ -43,6 +43,7 @@ class QueryCacheTest < ActiveRecord::TestCase
called = false
mw = ActiveRecord::QueryCache.new lambda { |env|
called = true
+ [200, {}, nil]
}
mw.call({})
assert called, 'middleware should delegate'
@@ -53,6 +54,7 @@ class QueryCacheTest < ActiveRecord::TestCase
Task.find 1
Task.find 1
assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ [200, {}, nil]
}
mw.call({})
end
@@ -62,6 +64,7 @@ class QueryCacheTest < ActiveRecord::TestCase
mw = ActiveRecord::QueryCache.new lambda { |env|
assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
+ [200, {}, nil]
}
mw.call({})
end
@@ -83,7 +86,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_cache_off_after_close
- mw = ActiveRecord::QueryCache.new lambda { |env| }
+ mw = ActiveRecord::QueryCache.new lambda { |env| [200, {}, nil] }
body = mw.call({}).last
assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled'
@@ -93,7 +96,8 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_clear_after_close
mw = ActiveRecord::QueryCache.new lambda { |env|
- Post.find(:first)
+ Post.first
+ [200, {}, nil]
}
body = mw.call({}).last
@@ -103,7 +107,7 @@ class QueryCacheTest < ActiveRecord::TestCase
end
def test_find_queries
- assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) }
+ assert_queries(2) { Task.find(1); Task.find(1) }
end
def test_find_queries_with_cache
@@ -170,6 +174,18 @@ end
class QueryCacheExpiryTest < ActiveRecord::TestCase
fixtures :tasks, :posts, :categories, :categories_posts
+ def test_cache_gets_cleared_after_migration
+ # warm the cache
+ Post.find(1)
+
+ # change the column definition
+ Post.connection.change_column :posts, :title, :string, :limit => 80
+ assert_nothing_raised { Post.find(1) }
+
+ # restore the old definition
+ Post.connection.change_column :posts, :title, :string
+ end
+
def test_find
Task.connection.expects(:clear_query_cache).times(1)
@@ -217,8 +233,8 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
def test_cache_is_expired_by_habtm_update
ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
ActiveRecord::Base.cache do
- c = Category.find(:first)
- p = Post.find(:first)
+ c = Category.first
+ p = Post.first
p.categories << c
end
end
@@ -232,14 +248,3 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase
end
end
end
-
-class QueryCacheBodyProxyTest < ActiveRecord::TestCase
-
- test "is polite to it's body and responds to it" do
- body = Class.new(String) { def to_path; "/path"; end }.new
- proxy = ActiveRecord::QueryCache::BodyProxy.new(nil, body, ActiveRecord::Base.connection_id)
- assert proxy.respond_to?(:to_path)
- assert_equal proxy.to_path, "/path"
- end
-
-end
diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb
index e21109baae..df0399f548 100644
--- a/activerecord/test/cases/readonly_test.rb
+++ b/activerecord/test/cases/readonly_test.rb
@@ -23,11 +23,12 @@ class ReadOnlyTest < ActiveRecord::TestCase
end
assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save }
assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! }
+ assert_raise(ActiveRecord::ReadOnlyRecord) { dev.destroy }
end
def test_find_with_readonly_option
- Developer.find(:all).each { |d| assert !d.readonly? }
+ Developer.all.each { |d| assert !d.readonly? }
Developer.readonly(false).each { |d| assert !d.readonly? }
Developer.readonly(true).each { |d| assert d.readonly? }
Developer.readonly.each { |d| assert d.readonly? }
@@ -48,7 +49,7 @@ class ReadOnlyTest < ActiveRecord::TestCase
post = Post.find(1)
assert !post.comments.empty?
assert !post.comments.any?(&:readonly?)
- assert !post.comments.find(:all).any?(&:readonly?)
+ assert !post.comments.all.any?(&:readonly?)
assert post.comments.readonly(true).all?(&:readonly?)
end
@@ -70,13 +71,13 @@ class ReadOnlyTest < ActiveRecord::TestCase
end
def test_readonly_scoping
- Post.send(:with_scope, :find => { :conditions => '1=1' }) do
+ Post.where('1=1').scoped do
assert !Post.find(1).readonly?
assert Post.readonly(true).find(1).readonly?
assert !Post.readonly(false).find(1).readonly?
end
- Post.send(:with_scope, :find => { :joins => ' ' }) do
+ Post.joins(' ').scoped do
assert !Post.find(1).readonly?
assert Post.readonly.find(1).readonly?
assert !Post.readonly(false).find(1).readonly?
@@ -85,14 +86,14 @@ class ReadOnlyTest < ActiveRecord::TestCase
# Oracle barfs on this because the join includes unqualified and
# conflicting column names
unless current_adapter?(:OracleAdapter)
- Post.send(:with_scope, :find => { :joins => ', developers' }) do
+ Post.joins(', developers').scoped do
assert Post.find(1).readonly?
assert Post.readonly.find(1).readonly?
assert !Post.readonly(false).find(1).readonly?
end
end
- Post.send(:with_scope, :find => { :readonly => true }) do
+ Post.readonly(true).scoped do
assert Post.find(1).readonly?
assert Post.readonly.find(1).readonly?
assert !Post.readonly(false).find(1).readonly?
diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb
new file mode 100644
index 0000000000..576ab60090
--- /dev/null
+++ b/activerecord/test/cases/reaper_test.rb
@@ -0,0 +1,81 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class ReaperTest < ActiveRecord::TestCase
+ attr_reader :pool
+
+ def setup
+ super
+ @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
+ end
+
+ def teardown
+ super
+ @pool.connections.each(&:close)
+ end
+
+ class FakePool
+ attr_reader :reaped
+
+ def initialize
+ @reaped = false
+ end
+
+ def reap
+ @reaped = true
+ end
+ end
+
+ # A reaper with nil time should never reap connections
+ def test_nil_time
+ fp = FakePool.new
+ assert !fp.reaped
+ reaper = ConnectionPool::Reaper.new(fp, nil)
+ reaper.run
+ assert !fp.reaped
+ end
+
+ def test_some_time
+ fp = FakePool.new
+ assert !fp.reaped
+
+ reaper = ConnectionPool::Reaper.new(fp, 0.0001)
+ reaper.run
+ until fp.reaped
+ Thread.pass
+ end
+ assert fp.reaped
+ end
+
+ def test_pool_has_reaper
+ assert pool.reaper
+ end
+
+ def test_reaping_frequency_configuration
+ spec = ActiveRecord::Base.connection_pool.spec.dup
+ spec.config[:reaping_frequency] = 100
+ pool = ConnectionPool.new spec
+ assert_equal 100, pool.reaper.frequency
+ end
+
+ def test_connection_pool_starts_reaper
+ spec = ActiveRecord::Base.connection_pool.spec.dup
+ spec.config[:reaping_frequency] = 0.0001
+
+ pool = ConnectionPool.new spec
+ pool.timeout = 0
+
+ conn = pool.checkout
+ count = pool.connections.length
+
+ conn.extend(Module.new { def active?; false; end; })
+
+ while count == pool.connections.length
+ Thread.pass
+ end
+ assert_equal(count - 1, pool.connections.length)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 69e9fc8d61..7dd5698dcf 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -36,25 +36,25 @@ class ReflectionTest < ActiveRecord::TestCase
def test_read_attribute_names
assert_equal(
- %w( id title author_name author_email_address bonus_time written_on last_read content group approved replies_count parent_id parent_title type created_at updated_at ).sort,
+ %w( id title author_name author_email_address bonus_time written_on last_read content important group approved replies_count parent_id parent_title type created_at updated_at ).sort,
@first.attribute_names.sort
)
end
def test_columns
- assert_equal 16, Topic.columns.length
+ assert_equal 17, Topic.columns.length
end
def test_columns_are_returned_in_the_order_they_were_declared
column_names = Topic.columns.map { |column| column.name }
- assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type group created_at updated_at), column_names
+ assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content important approved replies_count parent_id parent_title type group created_at updated_at), column_names
end
def test_content_columns
content_columns = Topic.content_columns
content_column_names = content_columns.map {|column| column.name}
- assert_equal 12, content_columns.length
- assert_equal %w(title author_name author_email_address written_on bonus_time last_read content group approved parent_title created_at updated_at).sort, content_column_names.sort
+ assert_equal 13, content_columns.length
+ assert_equal %w(title author_name author_email_address written_on bonus_time last_read content important group approved parent_title created_at updated_at).sort, content_column_names.sort
end
def test_column_string_type_and_limit
@@ -63,7 +63,7 @@ class ReflectionTest < ActiveRecord::TestCase
end
def test_column_null_not_null
- subscriber = Subscriber.find(:first)
+ subscriber = Subscriber.first
assert subscriber.column_for_attribute("name").null
assert !subscriber.column_for_attribute("nick").null
end
@@ -189,8 +189,8 @@ class ReflectionTest < ActiveRecord::TestCase
def test_reflection_of_all_associations
# FIXME these assertions bust a lot
- assert_equal 36, Firm.reflect_on_all_associations.size
- assert_equal 26, Firm.reflect_on_all_associations(:has_many).size
+ assert_equal 39, Firm.reflect_on_all_associations.size
+ assert_equal 29, Firm.reflect_on_all_associations(:has_many).size
assert_equal 10, Firm.reflect_on_all_associations(:has_one).size
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
end
@@ -319,6 +319,16 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "category_id", Post.reflect_on_association(:categorizations).foreign_key.to_s
end
+ def test_through_reflection_conditions_do_not_modify_other_reflections
+ orig_conds = Post.reflect_on_association(:first_blue_tags_2).conditions.inspect
+ Author.reflect_on_association(:misc_post_first_blue_tags_2).conditions
+ assert_equal orig_conds, Post.reflect_on_association(:first_blue_tags_2).conditions.inspect
+ end
+
+ def test_symbol_for_class_name
+ assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass
+ end
+
private
def assert_reflection(klass, association, options)
assert reflection = klass.reflect_on_association(association)
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 1e2093273e..cf367242f2 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -53,8 +53,8 @@ class RelationScopingTest < ActiveRecord::TestCase
end
def test_scoped_find_last_preserves_scope
- lowest_salary = Developer.first :order => "salary ASC"
- highest_salary = Developer.first :order => "salary DESC"
+ lowest_salary = Developer.order("salary ASC").first
+ highest_salary = Developer.order("salary DESC").first
Developer.order("salary").scoping do
assert_equal highest_salary, Developer.last
@@ -84,7 +84,7 @@ class RelationScopingTest < ActiveRecord::TestCase
def test_scope_select_concatenates
Developer.select("id, name").scoping do
- developer = Developer.select('id, salary').where("name = 'David'").first
+ developer = Developer.select('salary').where("name = 'David'").first
assert_equal 80000, developer.salary
assert developer.has_attribute?(:id)
assert developer.has_attribute?(:name)
@@ -106,7 +106,7 @@ class RelationScopingTest < ActiveRecord::TestCase
def test_scoped_find_include
# with the include, will retrieve only developers for the given project
scoped_developers = Developer.includes(:projects).scoping do
- Developer.where('projects.id = 2').all
+ Developer.where('projects.id' => 2).all
end
assert scoped_developers.include?(developers(:david))
assert !scoped_developers.include?(developers(:jamis))
@@ -254,11 +254,6 @@ class HasManyScopingTest< ActiveRecord::TestCase
assert_equal 2, @welcome.comments.search_by_type('Comment').size
end
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Comment.find_all_by_type('Comment').size
- assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
- end
-
def test_nested_scope_finder
Comment.where('1=0').scoping do
assert_equal 0, @welcome.comments.count
@@ -300,12 +295,6 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
assert_equal 'a category...', @welcome.categories.what_are_you
end
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Category.find_all_by_type('SpecialCategory').size
- assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
- assert_equal 2, @welcome.categories.find_all_by_type('Category').size
- end
-
def test_nested_scope_finder
Category.where('1=0').scoping do
assert_equal 0, @welcome.categories.count
@@ -323,8 +312,8 @@ class DefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts
def test_default_scope
- expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ expected = Developer.scoped(:order => 'salary DESC').all.collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.all.collect { |dev| dev.salary }
assert_equal expected, received
end
@@ -362,12 +351,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
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 Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.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 Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort
assert_equal 'Jamis', DeveloperCalledJamis.create!.name
end
@@ -395,23 +384,9 @@ class DefaultScopingTest < ActiveRecord::TestCase
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_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
-
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 }
+ expected = Developer.scoped(:order => 'salary DESC, name DESC').all.collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.by_name.all.collect { |dev| dev.name }
assert_equal expected, received
end
@@ -421,17 +396,15 @@ class DefaultScopingTest < ActiveRecord::TestCase
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
+ def test_order_after_reorder_combines_orders
+ expected = Developer.order('name DESC, id DESC').collect { |dev| [dev.name, dev.id] }
+ received = Developer.order('name ASC').reorder('name DESC').order('id DESC').collect { |dev| [dev.name, dev.id] }
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 }
+ expected = Developer.scoped(:order => 'salary desc').all.collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.scoped(:order => 'salary').all.collect { |dev| dev.salary }
assert_equal expected, received
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index b23ead6feb..89f818a689 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -19,33 +19,12 @@ module ActiveRecord
assert !relation.loaded, 'relation is not loaded'
end
- def test_single_values
- assert_equal [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order].map(&:to_s).sort,
- Relation::SINGLE_VALUE_METHODS.map(&:to_s).sort
- end
-
def test_initialize_single_values
relation = Relation.new :a, :b
- Relation::SINGLE_VALUE_METHODS.each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
- end
-
- def test_association_methods
- assert_equal [:includes, :eager_load, :preload].map(&:to_s).sort,
- Relation::ASSOCIATION_METHODS.map(&:to_s).sort
- end
-
- def test_initialize_association_methods
- relation = Relation.new :a, :b
- Relation::ASSOCIATION_METHODS.each do |method|
- assert_equal [], relation.send("#{method}_values"), method.to_s
- end
- end
-
- def test_multi_value_methods
- assert_equal [:select, :group, :order, :joins, :where, :having, :bind].map(&:to_s).sort,
- Relation::MULTI_VALUE_METHODS.map(&:to_s).sort
+ assert_equal({}, relation.create_with_value)
end
def test_multi_value_initialize
@@ -64,19 +43,19 @@ module ActiveRecord
relation = Relation.new :a, :b
assert_equal({}, relation.where_values_hash)
- relation.where_values << :hello
+ relation.where! :hello
assert_equal({}, relation.where_values_hash)
end
def test_has_values
relation = Relation.new Post, Post.arel_table
- relation.where_values << relation.table[:id].eq(10)
+ relation.where! relation.table[:id].eq(10)
assert_equal({:id => 10}, relation.where_values_hash)
end
def test_values_wrong_table
relation = Relation.new Post, Post.arel_table
- relation.where_values << Comment.arel_table[:id].eq(10)
+ relation.where! Comment.arel_table[:id].eq(10)
assert_equal({}, relation.where_values_hash)
end
@@ -85,7 +64,7 @@ module ActiveRecord
left = relation.table[:id].eq(10)
right = relation.table[:id].eq(10)
combine = left.and right
- relation.where_values << combine
+ relation.where! combine
assert_equal({}, relation.where_values_hash)
end
@@ -108,7 +87,7 @@ module ActiveRecord
def test_create_with_value_with_wheres
relation = Relation.new Post, Post.arel_table
- relation.where_values << relation.table[:id].eq(10)
+ relation.where! relation.table[:id].eq(10)
relation.create_with_value = {:hello => 'world'}
assert_equal({:hello => 'world', :id => 10}, relation.scope_for_create)
end
@@ -118,7 +97,7 @@ module ActiveRecord
relation = Relation.new Post, Post.arel_table
assert_equal({}, relation.scope_for_create)
- relation.where_values << relation.table[:id].eq(10)
+ relation.where! relation.table[:id].eq(10)
assert_equal({}, relation.scope_for_create)
relation.create_with_value = {:hello => 'world'}
@@ -132,8 +111,138 @@ module ActiveRecord
def test_eager_load_values
relation = Relation.new :a, :b
- relation.eager_load_values << :b
+ relation.eager_load! :b
assert relation.eager_loading?
end
+
+ def test_references_values
+ relation = Relation.new :a, :b
+ assert_equal [], relation.references_values
+ relation = relation.references(:foo).references(:omg, :lol)
+ assert_equal ['foo', 'omg', 'lol'], relation.references_values
+ end
+
+ def test_references_values_dont_duplicate
+ relation = Relation.new :a, :b
+ relation = relation.references(:foo).references(:foo)
+ assert_equal ['foo'], relation.references_values
+ end
+
+ test 'merging a hash into a relation' do
+ relation = Relation.new :a, :b
+ relation = relation.merge where: :lol, readonly: true
+
+ assert_equal [:lol], relation.where_values
+ assert_equal true, relation.readonly_value
+ end
+
+ test 'merging an empty hash into a relation' do
+ assert_equal [], Relation.new(:a, :b).merge({}).where_values
+ end
+
+ test 'merging a hash with unknown keys raises' do
+ assert_raises(ArgumentError) { Relation::HashMerger.new(nil, omg: 'lol') }
+ end
+
+ test '#values returns a dup of the values' do
+ relation = Relation.new(:a, :b).where! :foo
+ values = relation.values
+
+ values[:where] = nil
+ assert_not_nil relation.where_values
+ end
+
+ test 'relations can be created with a values hash' do
+ relation = Relation.new(:a, :b, where: [:foo])
+ assert_equal [:foo], relation.where_values
+ end
+
+ test 'merging a single where value' do
+ relation = Relation.new(:a, :b)
+ relation.merge!(where: :foo)
+ assert_equal [:foo], relation.where_values
+ end
+
+ test 'merging a hash interpolates conditions' do
+ klass = stub
+ klass.stubs(:sanitize_sql).with(['foo = ?', 'bar']).returns('foo = bar')
+
+ relation = Relation.new(klass, :b)
+ relation.merge!(where: ['foo = ?', 'bar'])
+ assert_equal ['foo = bar'], relation.where_values
+ end
+ end
+
+ class RelationMutationTest < ActiveSupport::TestCase
+ def relation
+ @relation ||= Relation.new :a, :b
+ end
+
+ (Relation::MULTI_VALUE_METHODS - [:references, :extending]).each do |method|
+ test "##{method}!" do
+ assert relation.public_send("#{method}!", :foo).equal?(relation)
+ assert_equal [:foo], relation.public_send("#{method}_values")
+ end
+ end
+
+ test '#references!' do
+ assert relation.references!(:foo).equal?(relation)
+ assert relation.references_values.include?('foo')
+ end
+
+ test 'extending!' do
+ mod = Module.new
+
+ assert relation.extending!(mod).equal?(relation)
+ assert [mod], relation.extending_values
+ assert relation.is_a?(mod)
+ end
+
+ test 'extending! with empty args' do
+ relation.extending!
+ assert_equal [], relation.extending_values
+ end
+
+ (Relation::SINGLE_VALUE_METHODS - [:from, :lock, :reordering, :reverse_order, :create_with]).each do |method|
+ test "##{method}!" do
+ assert relation.public_send("#{method}!", :foo).equal?(relation)
+ assert_equal :foo, relation.public_send("#{method}_value")
+ end
+ end
+
+ test '#from!' do
+ assert relation.from!('foo').equal?(relation)
+ assert_equal ['foo', nil], relation.from_value
+ end
+
+ test '#lock!' do
+ assert relation.lock!('foo').equal?(relation)
+ assert_equal 'foo', relation.lock_value
+ end
+
+ test '#reorder!' do
+ relation = self.relation.order('foo')
+
+ assert relation.reorder!('bar').equal?(relation)
+ assert_equal ['bar'], relation.order_values
+ assert relation.reordering_value
+ end
+
+ test 'reverse_order!' do
+ assert relation.reverse_order!.equal?(relation)
+ assert relation.reverse_order_value
+ relation.reverse_order!
+ assert !relation.reverse_order_value
+ end
+
+ test 'create_with!' do
+ assert relation.create_with!(foo: 'bar').equal?(relation)
+ assert_equal({foo: 'bar'}, relation.create_with_value)
+ end
+
+ test 'merge!' do
+ assert relation.merge!(where: :foo).equal?(relation)
+ assert_equal [:foo], relation.where_values
+ end
end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 95408a5f29..4a56ae0d23 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -4,11 +4,11 @@ require 'models/tagging'
require 'models/post'
require 'models/topic'
require 'models/comment'
-require 'models/reply'
require 'models/author'
require 'models/comment'
require 'models/entrant'
require 'models/developer'
+require 'models/reply'
require 'models/company'
require 'models/bird'
require 'models/car'
@@ -133,6 +133,13 @@ class RelationTest < ActiveRecord::TestCase
assert topics.loaded?
end
+ def test_finiding_with_subquery
+ relation = Topic.where(:approved => true)
+ assert_equal relation.to_a, Topic.select('*').from(relation).to_a
+ assert_equal relation.to_a, Topic.select('subquery.*').from(relation).to_a
+ assert_equal relation.to_a, Topic.select('a.*').from(relation, :a).to_a
+ end
+
def test_finding_with_conditions
assert_equal ["David"], Author.where(:name => 'David').map(&:name)
assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name)
@@ -184,12 +191,12 @@ class RelationTest < ActiveRecord::TestCase
end
def test_finding_with_complex_order_and_limit
- tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a
+ tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a
assert_equal 1, tags.length
end
def test_finding_with_complex_order
- tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a
+ tags = Tag.includes(:taggings).references(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a
assert_equal 3, tags.length
end
@@ -215,6 +222,52 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [2, 4, 6, 8, 10], even_ids.sort
end
+ def test_none
+ assert_no_queries do
+ assert_equal [], Developer.none
+ assert_equal [], Developer.scoped.none
+ assert Developer.none.is_a?(ActiveRecord::NullRelation)
+ end
+ end
+
+ def test_none_chainable
+ assert_no_queries do
+ assert_equal [], Developer.none.where(:name => 'David')
+ end
+ end
+
+ def test_none_chained_to_methods_firing_queries_straight_to_db
+ assert_no_queries do
+ assert_equal [], Developer.none.pluck(:id) # => uses select_all
+ assert_equal 0, Developer.none.delete_all
+ assert_equal 0, Developer.none.update_all(:name => 'David')
+ assert_equal 0, Developer.none.delete(1)
+ assert_equal false, Developer.none.exists?(1)
+ end
+ end
+
+ def test_null_relation_content_size_methods
+ assert_no_queries do
+ assert_equal 0, Developer.none.size
+ assert_equal 0, Developer.none.count
+ assert_equal true, Developer.none.empty?
+ assert_equal false, Developer.none.any?
+ assert_equal false, Developer.none.many?
+ end
+ end
+
+ def test_null_relation_calculations_methods
+ assert_no_queries do
+ assert_equal 0, Developer.none.count
+ assert_equal nil, Developer.none.calculate(:average, 'salary')
+ end
+ end
+
+ def test_null_relation_metadata_methods
+ assert_equal "", Developer.none.to_sql
+ assert_equal({}, Developer.none.where_values_hash)
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
@@ -236,7 +289,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_find_on_hash_conditions
- assert_equal Topic.find(:all, :conditions => {:approved => false}), Topic.where({ :approved => false }).to_a
+ assert_equal Topic.scoped(:where => {:approved => false}).all, Topic.where({ :approved => false }).to_a
end
def test_joins_with_string_array
@@ -284,7 +337,6 @@ class RelationTest < ActiveRecord::TestCase
end
def test_respond_to_class_methods_and_scopes
- assert DeveloperOrderedBySalary.scoped.respond_to?(:all_ordered_by_name)
assert Topic.scoped.respond_to?(:by_lifo)
end
@@ -310,7 +362,7 @@ class RelationTest < ActiveRecord::TestCase
assert posts.first.comments.first
end
- assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
+ assert_queries(2) do
posts = Post.preload(:comments).order('posts.id')
assert posts.first.comments.first
end
@@ -320,12 +372,12 @@ class RelationTest < ActiveRecord::TestCase
assert posts.first.author
end
- assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
+ assert_queries(2) do
posts = Post.preload(:author).order('posts.id')
assert posts.first.author
end
- assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 3) do
+ assert_queries(3) do
posts = Post.preload(:author, :comments).order('posts.id')
assert posts.first.author
assert posts.first.comments.first
@@ -338,7 +390,7 @@ class RelationTest < ActiveRecord::TestCase
assert posts.first.comments.first
end
- assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) do
+ assert_queries(2) do
posts = Post.scoped.includes(:comments).order('posts.id')
assert posts.first.comments.first
end
@@ -348,7 +400,7 @@ class RelationTest < ActiveRecord::TestCase
assert posts.first.author
end
- assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 3) do
+ assert_queries(3) do
posts = Post.includes(:author, :comments).order('posts.id')
assert posts.first.author
assert posts.first.comments.first
@@ -356,18 +408,18 @@ class RelationTest < ActiveRecord::TestCase
end
def test_default_scope_with_conditions_string
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort
+ assert_equal Developer.where(name: 'David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort
assert_nil DeveloperCalledDavid.create!.name
end
def test_default_scope_with_conditions_hash
- assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort
+ assert_equal Developer.where(name: 'Jamis').map(&:id).sort, DeveloperCalledJamis.scoped.map(&:id).sort
assert_equal 'Jamis', DeveloperCalledJamis.create!.name
end
def test_default_scoping_finder_methods
developers = DeveloperCalledDavid.order('id').map(&:id).sort
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, developers
+ assert_equal Developer.where(name: 'David').map(&:id).sort, developers
end
def test_loading_with_one_association
@@ -391,15 +443,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.find(1).last_comment, post.last_comment
end
- def test_dynamic_find_by_attributes_should_yield_found_object
- david = authors(:david)
- yielded_value = nil
- Author.find_by_name(david.name) do |author|
- yielded_value = author
- end
- assert_equal david, yielded_value
- end
-
def test_dynamic_find_by_attributes
david = authors(:david)
author = Author.preload(:taggings).find_by_id(david.id)
@@ -421,34 +464,6 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) { Author.scoped.find_by_id_and_name!(20, 'invalid') }
end
- def test_dynamic_find_all_by_attributes
- authors = Author.scoped
-
- davids = authors.find_all_by_name('David')
- assert_kind_of Array, davids
- assert_equal [authors(:david)], davids
- end
-
- def test_dynamic_find_or_initialize_by_attributes
- authors = Author.scoped
-
- lifo = authors.find_or_initialize_by_name('Lifo')
- assert_equal "Lifo", lifo.name
- assert !lifo.persisted?
-
- assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David')
- end
-
- def test_dynamic_find_or_create_by_attributes
- authors = Author.scoped
-
- lifo = authors.find_or_create_by_name('Lifo')
- assert_equal "Lifo", lifo.name
- assert lifo.persisted?
-
- assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David')
- end
-
def test_find_id
authors = Author.scoped
@@ -587,6 +602,7 @@ class RelationTest < ActiveRecord::TestCase
assert ! davids.exists?(authors(:mary).id)
assert ! davids.exists?("42")
assert ! davids.exists?(42)
+ assert ! davids.exists?(davids.new)
fake = Author.where(:name => 'fake author')
assert ! fake.exists?
@@ -631,6 +647,10 @@ class RelationTest < ActiveRecord::TestCase
assert davids.loaded?
end
+ def test_delete_all_limit_error
+ assert_raises(ActiveRecord::ActiveRecordError) { Author.limit(10).delete_all }
+ end
+
def test_select_argument_error
assert_raises(ArgumentError) { Developer.select }
end
@@ -660,10 +680,8 @@ class RelationTest < ActiveRecord::TestCase
end
def test_relation_merging_with_preload
- ActiveRecord::IdentityMap.without do
- [Post.scoped.merge(Post.preload(:author)), Post.preload(:author).merge(Post.scoped)].each do |posts|
- assert_queries(2) { assert posts.first.author }
- end
+ [Post.scoped.merge(Post.preload(:author)), Post.preload(:author).merge(Post.scoped)].each do |posts|
+ assert_queries(2) { assert posts.first.author }
end
end
@@ -1004,10 +1022,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.all, all_posts.all
end
- def test_extensions_with_except
- assert_equal 2, Topic.named_extension.order(:author_name).except(:order).two
- end
-
def test_only
relation = Post.where(:author_id => 1).order('id ASC').limit(1)
assert_equal [posts(:welcome)], relation.all
@@ -1019,10 +1033,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.limit(1).all.first, all_posts.first
end
- def test_extensions_with_only
- assert_equal 2, Topic.named_extension.order(:author_name).only(:order).two
- end
-
def test_anonymous_extension
relation = Post.where(:author_id => 1).order('id ASC').extending do
def author
@@ -1044,36 +1054,26 @@ class RelationTest < ActiveRecord::TestCase
assert_equal Post.order(Post.arel_table[:title]).all, Post.order("title").all
end
- def test_order_with_find_with_order
- assert_equal 'zyke', CoolCar.order('name desc').find(:first, :order => 'id').name
- assert_equal 'zyke', FastCar.order('name desc').find(:first, :order => 'id').name
- end
-
def test_default_scope_order_with_scope_order
assert_equal 'zyke', CoolCar.order_using_new_style.limit(1).first.name
- assert_equal 'zyke', CoolCar.order_using_old_style.limit(1).first.name
assert_equal 'zyke', FastCar.order_using_new_style.limit(1).first.name
- assert_equal 'zyke', FastCar.order_using_old_style.limit(1).first.name
end
def test_order_using_scoping
car1 = CoolCar.order('id DESC').scoping do
- CoolCar.find(:first, :order => 'id asc')
+ CoolCar.scoped(:order => 'id asc').first
end
assert_equal 'zyke', car1.name
car2 = FastCar.order('id DESC').scoping do
- FastCar.find(:first, :order => 'id asc')
+ FastCar.scoped(:order => 'id asc').first
end
assert_equal 'zyke', car2.name
end
def test_unscoped_block_style
assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_new_style.limit(1).first.name}
- assert_equal 'honda', CoolCar.unscoped { CoolCar.order_using_old_style.limit(1).first.name}
-
assert_equal 'honda', FastCar.unscoped { FastCar.order_using_new_style.limit(1).first.name}
- assert_equal 'honda', FastCar.unscoped { FastCar.order_using_old_style.limit(1).first.name}
end
def test_intersection_with_array
@@ -1084,10 +1084,6 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [rails_author], relation & [rails_author]
end
- def test_removing_limit_with_options
- assert_not_equal 1, Post.limit(1).all(:limit => nil).count
- end
-
def test_primary_key
assert_equal "id", Post.scoped.primary_key
end
@@ -1104,7 +1100,9 @@ class RelationTest < ActiveRecord::TestCase
)
)
- assert scope.eager_loading?
+ assert_deprecated do
+ assert scope.eager_loading?
+ end
end
def test_ordering_with_extra_spaces
@@ -1148,4 +1146,129 @@ class RelationTest < ActiveRecord::TestCase
assert_equal posts(:thinking), comments(:more_greetings).post
assert_equal posts(:welcome), comments(:greetings).post
end
+
+ def test_uniq
+ tag1 = Tag.create(:name => 'Foo')
+ tag2 = Tag.create(:name => 'Foo')
+
+ query = Tag.select(:name).where(:id => [tag1.id, tag2.id])
+
+ assert_equal ['Foo', 'Foo'], query.map(&:name)
+ assert_sql(/DISTINCT/) do
+ assert_equal ['Foo'], query.uniq.map(&:name)
+ end
+ assert_sql(/DISTINCT/) do
+ assert_equal ['Foo'], query.uniq(true).map(&:name)
+ end
+ assert_equal ['Foo', 'Foo'], query.uniq(true).uniq(false).map(&:name)
+ end
+
+ def test_references_triggers_eager_loading
+ scope = Post.includes(:comments)
+ assert !scope.eager_loading?
+ assert scope.references(:comments).eager_loading?
+ end
+
+ def test_references_doesnt_trigger_eager_loading_if_reference_not_included
+ scope = Post.references(:comments)
+ assert !scope.eager_loading?
+ end
+
+ def test_automatically_added_where_references
+ scope = Post.where(:comments => { :body => "Bla" })
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.where('comments.body' => 'Bla')
+ assert_equal ['comments'], scope.references_values
+ end
+
+ def test_automatically_added_having_references
+ scope = Post.having(:comments => { :body => "Bla" })
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.having('comments.body' => 'Bla')
+ assert_equal ['comments'], scope.references_values
+ end
+
+ def test_automatically_added_order_references
+ scope = Post.order('comments.body')
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.order('comments.body', 'yaks.body')
+ assert_equal ['comments', 'yaks'], scope.references_values
+
+ # Don't infer yaks, let's not go down that road again...
+ scope = Post.order('comments.body, yaks.body')
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.order('comments.body asc')
+ assert_equal ['comments'], scope.references_values
+
+ scope = Post.order('foo(comments.body)')
+ assert_equal [], scope.references_values
+ end
+
+ def test_presence
+ topics = Topic.scoped
+
+ # the first query is triggered because there are no topics yet.
+ assert_queries(1) { assert topics.present? }
+
+ # checking if there are topics is used before you actually display them,
+ # thus it shouldn't invoke an extra count query.
+ assert_no_queries { assert topics.present? }
+ assert_no_queries { assert !topics.blank? }
+
+ # shows count of topics and loops after loading the query should not trigger extra queries either.
+ assert_no_queries { topics.size }
+ assert_no_queries { topics.length }
+ assert_no_queries { topics.each }
+
+ # count always trigger the COUNT query.
+ assert_queries(1) { topics.count }
+
+ assert topics.loaded?
+ end
+
+ test "find_by with hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by(author_id: 2)
+ end
+
+ test "find_by with non-hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by("author_id = 2")
+ end
+
+ test "find_by with multi-arg conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by('author_id = ?', 2)
+ end
+
+ test "find_by returns nil if the record is missing" do
+ assert_equal nil, Post.scoped.find_by("1 = 0")
+ end
+
+ test "find_by doesn't have implicit ordering" do
+ assert_sql(/^((?!ORDER).)*$/) { Post.find_by(author_id: 2) }
+ end
+
+ test "find_by! with hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by!(author_id: 2)
+ end
+
+ test "find_by! with non-hash conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by!("author_id = 2")
+ end
+
+ test "find_by! with multi-arg conditions returns the first matching record" do
+ assert_equal posts(:eager_other), Post.order(:id).find_by!('author_id = ?', 2)
+ end
+
+ test "find_by! doesn't have implicit ordering" do
+ assert_sql(/^((?!ORDER).)*$/) { Post.find_by!(author_id: 2) }
+ end
+
+ test "find_by! raises RecordNotFound if the record is missing" do
+ assert_raises(ActiveRecord::RecordNotFound) do
+ Post.scoped.find_by!("1 = 0")
+ end
+ end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 71ff727b7f..ab80dd1d6d 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -2,7 +2,13 @@ require "cases/helper"
class SchemaDumperTest < ActiveRecord::TestCase
+ def initialize(*)
+ super
+ ActiveRecord::SchemaMigration.create_table
+ end
+
def setup
+ super
@stream = StringIO.new
end
@@ -13,10 +19,18 @@ class SchemaDumperTest < ActiveRecord::TestCase
@stream.string
end
- if "string".encoding_aware?
- def test_magic_comment
- assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump
+ def test_dump_schema_information_outputs_lexically_ordered_versions
+ versions = %w{ 20100101010101 20100201010101 20100301010101 }
+ versions.reverse.each do |v|
+ ActiveRecord::SchemaMigration.create!(:version => v)
end
+
+ schema_info = ActiveRecord::Base.connection.dump_schema_information
+ assert_match(/20100201010101.*20100301010101/m, schema_info)
+ end
+
+ def test_magic_comment
+ assert_match "# encoding: #{@stream.external_encoding.name}", standard_dump
end
def test_schema_dump
@@ -171,6 +185,15 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition
end
+ def test_schema_dumps_partial_indices
+ index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal 'add_index "companies", ["firm_id", "type"], :name => "company_partial_index", :where => "(rating > 10)"', index_definition
+ else
+ assert_equal 'add_index "companies", ["firm_id", "type"], :name => "company_partial_index"', index_definition
+ end
+ end
+
def test_schema_dump_should_honor_nonstandard_primary_keys
output = standard_dump
match = output.match(%r{create_table "movies"(.*)do})
@@ -213,6 +236,34 @@ class SchemaDumperTest < ActiveRecord::TestCase
end
end
+ def test_schema_dump_includes_inet_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_network_address"} =~ output
+ assert_match %r{t.inet "inet_address"}, output
+ end
+ end
+
+ def test_schema_dump_includes_cidr_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_network_address"} =~ output
+ assert_match %r{t.cidr "cidr_address"}, output
+ end
+ end
+
+ def test_schema_dump_includes_macaddr_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_network_address"} =~ output
+ assert_match %r{t.macaddr "macaddr_address"}, output
+ end
+ end
+
+ def test_schema_dump_includes_hstores_shorthand_definition
+ output = standard_dump
+ if %r{create_table "postgresql_hstores"} =~ output
+ assert_match %r[t.hstore "hash_store", :default => {}], output
+ end
+ end
+
def test_schema_dump_includes_tsvector_shorthand_definition
output = standard_dump
if %r{create_table "postgresql_tsvectors"} =~ output
@@ -238,4 +289,9 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r(:id => false), match[1], "no table id not preserved"
assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved"
end
+
+ def test_schema_dump_keeps_id_false_when_id_is_false_and_unique_not_null_column_added
+ output = standard_dump
+ assert_match %r{create_table "subscribers", :id => false}, output
+ end
end
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index 61b04b3e37..a4c065e667 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -13,7 +13,8 @@ class SerializationTest < ActiveRecord::TestCase
:created_at => Time.utc(2006, 8, 1),
:awesome => false,
:preferences => { :gem => '<strong>ruby</strong>' },
- :alternative_id => nil
+ :alternative_id => nil,
+ :id => nil
}
end
@@ -24,7 +25,7 @@ class SerializationTest < ActiveRecord::TestCase
end
def test_serialize_should_be_reversible
- for format in FORMATS
+ FORMATS.each do |format|
@serialized = Contact.new.send("to_#{format}")
contact = Contact.new.send("from_#{format}", @serialized)
@@ -33,7 +34,7 @@ class SerializationTest < ActiveRecord::TestCase
end
def test_serialize_should_allow_attribute_only_filtering
- for format in FORMATS
+ FORMATS.each do |format|
@serialized = Contact.new(@contact_attributes).send("to_#{format}", :only => [ :age, :name ])
contact = Contact.new.send("from_#{format}", @serialized)
assert_equal @contact_attributes[:name], contact.name, "For #{format}"
@@ -42,7 +43,7 @@ class SerializationTest < ActiveRecord::TestCase
end
def test_serialize_should_allow_attribute_except_filtering
- for format in FORMATS
+ FORMATS.each do |format|
@serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ])
contact = Contact.new.send("from_#{format}", @serialized)
assert_nil contact.name, "For #{format}"
diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb
index 258cee7aba..a3b8ab74d9 100644
--- a/activerecord/test/cases/session_store/session_test.rb
+++ b/activerecord/test/cases/session_store/session_test.rb
@@ -5,11 +5,15 @@ require 'active_record/session_store'
module ActiveRecord
class SessionStore
class SessionTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false unless supports_savepoints? && ActiveRecord::Base.connection.supports_ddl_transactions?
+ self.use_transactional_fixtures = false
+
+ attr_reader :session_klass
def setup
super
+ ActiveRecord::Base.connection.schema_cache.clear!
Session.drop_table! if Session.table_exists?
+ @session_klass = Class.new(Session)
end
def test_data_column_name
@@ -60,8 +64,8 @@ module ActiveRecord
def test_find_by_session_id
Session.create_table!
session_id = "10"
- s = Session.create!(:data => 'world', :session_id => session_id)
- t = Session.find_by_session_id(session_id)
+ s = session_klass.create!(:data => 'world', :session_id => session_id)
+ t = session_klass.find_by_session_id(session_id)
assert_equal s, t
assert_equal s.data, t.data
Session.drop_table!
diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass_test.rb
index 7402b2afd6..6749d4ce98 100644
--- a/activerecord/test/cases/session_store/sql_bypass.rb
+++ b/activerecord/test/cases/session_store/sql_bypass_test.rb
@@ -18,6 +18,11 @@ module ActiveRecord
assert !Session.table_exists?
end
+ def test_new_record?
+ s = SqlBypass.new :data => 'foo', :session_id => 10
+ assert s.new_record?, 'this is a new record!'
+ end
+
def test_persisted?
s = SqlBypass.new :data => 'foo', :session_id => 10
assert !s.persisted?, 'this is a new record!'
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index fb77220875..e1d0f1f799 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -4,14 +4,14 @@ require 'models/admin/user'
class StoreTest < ActiveRecord::TestCase
setup do
- @john = Admin::User.create(:name => 'John Doe', :color => 'black')
+ @john = Admin::User.create(:name => 'John Doe', :color => 'black', :remember_login => true, :height => 'tall', :is_a_good_guy => true)
end
test "reading store attributes through accessors" do
assert_equal 'black', @john.color
assert_nil @john.homepage
end
-
+
test "writing store attributes through accessors" do
@john.color = 'red'
@john.homepage = '37signals.com'
@@ -24,6 +24,54 @@ class StoreTest < ActiveRecord::TestCase
@john.settings[:icecream] = 'graeters'
@john.save
- assert 'graeters', @john.reload.settings[:icecream]
+ assert_equal 'graeters', @john.reload.settings[:icecream]
+ end
+
+ test "updating the store will mark it as changed" do
+ @john.color = 'red'
+ assert @john.settings_changed?
+ end
+
+ test "object initialization with not nullable column" do
+ assert_equal true, @john.remember_login
+ end
+
+ test "writing with not nullable column" do
+ @john.remember_login = false
+ assert_equal false, @john.remember_login
+ end
+
+ test "reading store attributes through accessors encoded with JSON" do
+ assert_equal 'tall', @john.height
+ assert_nil @john.weight
+ end
+
+ test "writing store attributes through accessors encoded with JSON" do
+ @john.height = 'short'
+ @john.weight = 'heavy'
+
+ assert_equal 'short', @john.height
+ assert_equal 'heavy', @john.weight
+ end
+
+ test "accessing attributes not exposed by accessors encoded with JSON" do
+ @john.json_data['somestuff'] = 'somecoolstuff'
+ @john.save
+
+ assert_equal 'somecoolstuff', @john.reload.json_data['somestuff']
+ end
+
+ test "updating the store will mark it as changed encoded with JSON" do
+ @john.height = 'short'
+ assert @john.json_data_changed?
+ end
+
+ test "object initialization with not nullable column encoded with JSON" do
+ assert_equal true, @john.is_a_good_guy
+ end
+
+ test "writing with not nullable column encoded with JSON" do
+ @john.is_a_good_guy = false
+ assert_equal false, @john.is_a_good_guy
end
end
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
new file mode 100644
index 0000000000..94a13d386c
--- /dev/null
+++ b/activerecord/test/cases/test_case.rb
@@ -0,0 +1,10 @@
+require 'active_support/deprecation'
+ActiveSupport::Deprecation.silence do
+ require 'active_record/test_case'
+end
+
+ActiveRecord::TestCase.class_eval do
+ def sqlite3? connection
+ connection.class.name.split('::').last == "SQLite3Adapter"
+ end
+end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 4445a12e1d..447aa29ffe 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -60,6 +60,16 @@ class TimestampTest < ActiveRecord::TestCase
Developer.record_timestamps = true
end
+ def test_saving_when_instance_record_timestamps_is_false_doesnt_update_its_timestamp
+ @developer.record_timestamps = false
+ assert Developer.record_timestamps
+
+ @developer.name = "John Smith"
+ @developer.save!
+
+ assert_equal @previously_updated_at, @developer.updated_at
+ end
+
def test_touching_an_attribute_updates_timestamp
previously_created_at = @developer.created_at
@developer.touch(:created_at)
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 85f222bca2..961ba8d9ba 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -6,7 +6,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
fixtures :topics
class TopicWithCallbacks < ActiveRecord::Base
- set_table_name :topics
+ self.table_name = :topics
after_commit{|record| record.send(:do_after_commit, nil)}
after_commit(:on => :create){|record| record.send(:do_after_commit, :create)}
@@ -252,7 +252,7 @@ class TransactionObserverCallbacksTest < ActiveRecord::TestCase
fixtures :topics
class TopicWithObserverAttached < ActiveRecord::Base
- set_table_name :topics
+ self.table_name = :topics
def history
@history ||= []
end
@@ -287,6 +287,74 @@ class TransactionObserverCallbacksTest < ActiveRecord::TestCase
raise ActiveRecord::Rollback
end
+ assert topic.id.nil?
+ assert !topic.persisted?
assert_equal %w{ after_rollback }, topic.history
end
+
+ class TopicWithManualRollbackObserverAttached < ActiveRecord::Base
+ self.table_name = :topics
+ def history
+ @history ||= []
+ end
+ end
+
+ class TopicWithManualRollbackObserverAttachedObserver < ActiveRecord::Observer
+ def after_save(record)
+ record.history.push "after_save"
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ def test_after_save_called_with_manual_rollback
+ assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer'
+
+ topic = TopicWithManualRollbackObserverAttached.new
+
+ assert !topic.save
+ assert_equal nil, topic.id
+ assert !topic.persisted?
+ assert_equal %w{ after_save }, topic.history
+ end
+ def test_after_save_called_with_manual_rollback_bang
+ assert TopicWithManualRollbackObserverAttachedObserver.instance, 'should have observer'
+
+ topic = TopicWithManualRollbackObserverAttached.new
+
+ topic.save!
+ assert_equal nil, topic.id
+ assert !topic.persisted?
+ assert_equal %w{ after_save }, topic.history
+ end
+end
+
+class SaveFromAfterCommitBlockTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ class TopicWithSaveInCallback < ActiveRecord::Base
+ self.table_name = :topics
+ after_commit :cache_topic, :on => :create
+ after_commit :call_update, :on => :update
+ attr_accessor :cached, :record_updated
+
+ def call_update
+ self.record_updated = true
+ end
+
+ def cache_topic
+ unless cached
+ self.cached = true
+ self.save
+ else
+ self.cached = false
+ end
+ end
+ end
+
+ def test_after_commit_in_save
+ topic = TopicWithSaveInCallback.new()
+ topic.save
+ assert_equal true, topic.cached
+ assert_equal true, topic.record_updated
+ end
end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 110a18772f..203dd054f1 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -563,6 +563,7 @@ if current_adapter?(:PostgreSQLAdapter)
topic.approved = !topic.approved?
topic.save!
end
+ Topic.connection.close
end
end
@@ -598,6 +599,7 @@ if current_adapter?(:PostgreSQLAdapter)
dev = Developer.find(1)
assert_equal original_salary, dev.salary
end
+ Developer.connection.close
end
end
@@ -610,6 +612,7 @@ if current_adapter?(:PostgreSQLAdapter)
assert_equal original_salary, Developer.find(1).salary
end
end
+ Developer.connection.close
end
threads.each { |t| t.join }
diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb
index e82ca3f93d..5a69054445 100644
--- a/activerecord/test/cases/unconnected_test.rb
+++ b/activerecord/test/cases/unconnected_test.rb
@@ -7,13 +7,13 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase
self.use_transactional_fixtures = false
def setup
- @underlying = ActiveRecord::Base.connection
- @specification = ActiveRecord::Base.remove_connection
+ @underlying = ActiveRecord::Model.connection
+ @specification = ActiveRecord::Model.remove_connection
end
def teardown
@underlying = nil
- ActiveRecord::Base.establish_connection(@specification)
+ ActiveRecord::Model.establish_connection(@specification)
load_schema if in_memory_db?
end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index 56e345990f..7ac34bc71e 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -61,6 +61,16 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert r.valid?
end
+ def test_validates_associated_marked_for_destruction
+ Topic.validates_associated(:replies)
+ Reply.validates_presence_of(:content)
+ t = Topic.new
+ t.replies << Reply.new
+ assert t.invalid?
+ t.replies.first.mark_for_destruction
+ assert t.valid?
+ end
+
def test_validates_associated_with_custom_message_using_quotes
Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
Topic.validates_presence_of :content
@@ -76,19 +86,17 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert !r.valid?
assert r.errors[:topic].any?
- r.topic = Topic.find :first
+ r.topic = Topic.first
assert r.valid?
end
def test_validates_size_of_association_utf8
- with_kcode('UTF8') do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
- o = Owner.new('name' => 'あいうえおかきくけこ')
- assert !o.save
- assert o.errors[:pets].any?
- o.pets.build('name' => 'あいうえおかきくけこ')
- assert o.valid?
- end
+ assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ o = Owner.new('name' => 'あいうえおかきくけこ')
+ assert !o.save
+ assert o.errors[:pets].any?
+ o.pets.build('name' => 'あいうえおかきくけこ')
+ assert o.valid?
end
def test_validates_presence_of_belongs_to_association__parent_is_new_record
@@ -110,4 +118,21 @@ class AssociationValidationTest < ActiveRecord::TestCase
end
end
+ def test_validates_associated_models_in_the_same_context
+ Topic.validates_presence_of :title, :on => :custom_context
+ Topic.validates_associated :replies
+ Reply.validates_presence_of :title, :on => :custom_context
+
+ t = Topic.new('title' => '')
+ r = t.replies.new('title' => '')
+
+ assert t.valid?
+ assert !t.valid?(:custom_context)
+
+ t.title = "Longer"
+ assert !t.valid?(:custom_context), "Should NOT be valid if the associated object is not valid in the same context."
+
+ r.title = "Longer"
+ assert t.valid?(:custom_context), "Should be valid if the associated object is not valid in the same context."
+ end
end
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index 628029f8df..a8e513d81f 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -8,6 +8,16 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
I18n.backend = I18n::Backend::Simple.new
end
+ def reset_i18n_load_path
+ @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
+ I18n.load_path.clear
+ I18n.backend = I18n::Backend::Simple.new
+ yield
+ ensure
+ I18n.load_path.replace @old_load_path
+ I18n.backend = @old_backend
+ end
+
# validates_associated: generate_message(attr_name, :invalid, :message => custom_message, :value => value)
def test_generate_message_invalid_with_default_message
assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :value => 'title')
@@ -35,4 +45,13 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message
end
+ test "RecordInvalid exception translation falls back to the :errors namespace" do
+ reset_i18n_load_path do
+ I18n.backend.store_translations 'en', :errors => {:messages => {:record_invalid => 'fallback message'}}
+ topic = Topic.new
+ topic.errors.add(:title, :blank)
+ assert_equal "fallback message", ActiveRecord::RecordInvalid.new(topic).message
+ end
+ end
+
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 0f1b3667cc..c173ee9a15 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -45,6 +45,18 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t2.save, "Should now save t2 as unique"
end
+ def test_validates_uniqueness_with_nil_value
+ Topic.validates_uniqueness_of(:title)
+
+ t = Topic.new("title" => nil)
+ assert t.save, "Should save t as unique"
+
+ t2 = Topic.new("title" => nil)
+ assert !t2.valid?, "Shouldn't be valid"
+ assert !t2.save, "Shouldn't save t2 as unique"
+ assert_equal ["has already been taken"], t2.errors[:title]
+ end
+
def test_validates_uniqueness_with_validates
Topic.validates :title, :uniqueness => true
Topic.create!('title' => 'abc')
@@ -80,6 +92,30 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert r3.valid?, "Saving r3"
end
+ def test_validate_uniqueness_with_object_scope
+ Reply.validates_uniqueness_of(:content, :scope => :topic)
+
+ t = Topic.create("title" => "I'm unique!")
+
+ r1 = t.replies.create "title" => "r1", "content" => "hello world"
+ assert r1.valid?, "Saving r1"
+
+ r2 = t.replies.create "title" => "r2", "content" => "hello world"
+ assert !r2.valid?, "Saving r2 first time"
+ end
+
+ def test_validate_uniqueness_with_object_arg
+ Reply.validates_uniqueness_of(:topic)
+
+ t = Topic.create("title" => "I'm unique!")
+
+ r1 = t.replies.create "title" => "r1", "content" => "hello world"
+ assert r1.valid?, "Saving r1"
+
+ r2 = t.replies.create "title" => "r2", "content" => "hello world"
+ assert !r2.valid?, "Saving r2 first time"
+ end
+
def test_validate_uniqueness_scoped_to_defining_class
t = Topic.create("title" => "What, me worry?")
@@ -149,16 +185,14 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert t2.valid?, "should validate with nil"
assert t2.save, "should save with nil"
- with_kcode('UTF8') do
- t_utf8 = Topic.new("title" => "Я тоже уникальный!")
- assert t_utf8.save, "Should save t_utf8 as unique"
+ t_utf8 = Topic.new("title" => "Я тоже уникальный!")
+ assert t_utf8.save, "Should save t_utf8 as unique"
- # If database hasn't UTF-8 character set, this test fails
- if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!"
- t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!")
- assert !t2_utf8.valid?, "Shouldn't be valid"
- assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique"
- end
+ # If database hasn't UTF-8 character set, this test fails
+ if Topic.scoped(:select => 'LOWER(title) AS title').find(t_utf8).title == "я тоже уникальный!"
+ t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!")
+ assert !t2_utf8.valid?, "Shouldn't be valid"
+ assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique"
end
end
@@ -227,10 +261,10 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert i1.errors[:value].any?, "Should not be empty"
end
- def test_validates_uniqueness_inside_with_scope
+ def test_validates_uniqueness_inside_scoping
Topic.validates_uniqueness_of(:title)
- Topic.send(:with_scope, :find => { :conditions => { :author_name => "David" } }) do
+ Topic.where(:author_name => "David").scoping do
t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary")
assert t1.save
t2 = Topic.new("title" => "I'm unique!", "author_name" => "David")
@@ -256,13 +290,11 @@ class UniquenessValidationTest < ActiveRecord::TestCase
end
def test_validate_uniqueness_with_limit_and_utf8
- with_kcode('UTF8') do
- # Event.title is limited to 5 characters
- e1 = Event.create(:title => "一二三四五")
- assert e1.valid?, "Could not create an event with a unique, 5 character title"
- e2 = Event.create(:title => "一二三四五六七八")
- assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
- end
+ # Event.title is limited to 5 characters
+ e1 = Event.create(:title => "一二三四五")
+ assert e1.valid?, "Could not create an event with a unique, 5 character title"
+ e2 = Event.create(:title => "一二三四五六七八")
+ assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique"
end
def test_validate_straight_inheritance_uniqueness
@@ -293,4 +325,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert w6.errors[:city].any?, "Should have errors for city"
assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city"
end
+
+ def test_validate_uniqueness_with_conditions
+ Topic.validates_uniqueness_of(:title, :conditions => Topic.where('approved = ?', true))
+ Topic.create("title" => "I'm a topic", "approved" => true)
+ Topic.create("title" => "I'm an unapproved topic", "approved" => false)
+
+ t3 = Topic.new("title" => "I'm a topic", "approved" => true)
+ assert !t3.valid?, "t3 shouldn't be valid"
+
+ t4 = Topic.new("title" => "I'm an unapproved topic", "approved" => false)
+ assert t4.valid?, "t4 should be valid"
+ end
end
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index c3e494866b..b11b330374 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -8,7 +8,7 @@ require 'models/parrot'
require 'models/company'
class ProtectedPerson < ActiveRecord::Base
- set_table_name 'people'
+ self.table_name = 'people'
attr_accessor :addon
attr_protected :first_name
end
@@ -93,30 +93,6 @@ class ValidationsTest < ActiveRecord::TestCase
end
end
- def test_scoped_create_without_attributes
- WrongReply.send(:with_scope, :create => {}) do
- assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! }
- end
- end
-
- def test_create_with_exceptions_using_scope_for_protected_attributes
- assert_nothing_raised do
- ProtectedPerson.send(:with_scope, :create => { :first_name => "Mary" } ) do
- person = ProtectedPerson.create! :addon => "Addon"
- assert_equal person.first_name, "Mary", "scope should ignore attr_protected"
- end
- end
- end
-
- def test_create_with_exceptions_using_scope_and_empty_attributes
- assert_nothing_raised do
- ProtectedPerson.send(:with_scope, :create => { :first_name => "Mary" } ) do
- person = ProtectedPerson.create!
- assert_equal person.first_name, "Mary", "should be ok when no attributes are passed to create!"
- end
- end
- end
-
def test_create_without_validation
reply = WrongReply.new
assert !reply.save
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 0b54c309d1..302913e095 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -1,14 +1,20 @@
-require "cases/helper"
+require 'cases/helper'
require 'models/topic'
class YamlSerializationTest < ActiveRecord::TestCase
fixtures :topics
def test_to_yaml_with_time_with_zone_should_not_raise_exception
+ tz = Time.zone
Time.zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
ActiveRecord::Base.time_zone_aware_attributes = true
+
topic = Topic.new(:written_on => DateTime.now)
assert_nothing_raised { topic.to_yaml }
+
+ ensure
+ Time.zone = tz
+ ActiveRecord::Base.time_zone_aware_attributes = false
end
def test_roundtrip
@@ -18,6 +24,11 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal topic, t
end
+ def test_roundtrip_serialized_column
+ topic = Topic.new(:content => {:omg=>:lol})
+ assert_equal({:omg=>:lol}, YAML.load(YAML.dump(topic)).content)
+ end
+
def test_encode_with_coder
topic = Topic.first
coder = {}
@@ -25,22 +36,17 @@ class YamlSerializationTest < ActiveRecord::TestCase
assert_equal({'attributes' => topic.attributes}, coder)
end
- begin
- require 'psych'
-
- def test_psych_roundtrip
- topic = Topic.first
- assert topic
- t = Psych.load Psych.dump topic
- assert_equal topic, t
- end
-
- def test_psych_roundtrip_new_object
- topic = Topic.new
- assert topic
- t = Psych.load Psych.dump topic
- assert_equal topic.attributes, t.attributes
- end
- rescue LoadError
+ def test_psych_roundtrip
+ topic = Topic.first
+ assert topic
+ t = Psych.load Psych.dump topic
+ assert_equal topic, t
+ end
+
+ def test_psych_roundtrip_new_object
+ topic = Topic.new
+ assert topic
+ t = Psych.load Psych.dump topic
+ assert_equal topic.attributes, t.attributes
end
end
diff --git a/activerecord/test/fixtures/admin/randomly_named_a9.yml b/activerecord/test/fixtures/admin/randomly_named_a9.yml
new file mode 100644
index 0000000000..bc51c83112
--- /dev/null
+++ b/activerecord/test/fixtures/admin/randomly_named_a9.yml
@@ -0,0 +1,7 @@
+first_instance:
+ some_attribute: AAA
+ another_attribute: 000
+
+second_instance:
+ some_attribute: BBB
+ another_attribute: 999
diff --git a/activerecord/test/fixtures/admin/randomly_named_b0.yml b/activerecord/test/fixtures/admin/randomly_named_b0.yml
new file mode 100644
index 0000000000..bc51c83112
--- /dev/null
+++ b/activerecord/test/fixtures/admin/randomly_named_b0.yml
@@ -0,0 +1,7 @@
+first_instance:
+ some_attribute: AAA
+ another_attribute: 000
+
+second_instance:
+ some_attribute: BBB
+ another_attribute: 999
diff --git a/activerecord/test/fixtures/colleges.yml b/activerecord/test/fixtures/colleges.yml
new file mode 100644
index 0000000000..27591e0c2c
--- /dev/null
+++ b/activerecord/test/fixtures/colleges.yml
@@ -0,0 +1,3 @@
+FIU:
+ id: 1
+ name: Florida International University
diff --git a/activerecord/test/fixtures/courses.yml b/activerecord/test/fixtures/courses.yml
index 5ee1916003..de3a4a97e5 100644
--- a/activerecord/test/fixtures/courses.yml
+++ b/activerecord/test/fixtures/courses.yml
@@ -1,6 +1,7 @@
ruby:
id: 1
name: Ruby Development
+ college: FIU
java:
id: 2
diff --git a/activerecord/test/fixtures/developers.yml b/activerecord/test/fixtures/developers.yml
index 308bf75de2..3656564f63 100644
--- a/activerecord/test/fixtures/developers.yml
+++ b/activerecord/test/fixtures/developers.yml
@@ -8,7 +8,7 @@ jamis:
name: Jamis
salary: 150000
-<% for digit in 3..10 %>
+<% (3..10).each do |digit| %>
dev_<%= digit %>:
id: <%= digit %>
name: fixture_<%= digit %>
diff --git a/activerecord/test/fixtures/dog_lovers.yml b/activerecord/test/fixtures/dog_lovers.yml
new file mode 100644
index 0000000000..d3e5e4a1aa
--- /dev/null
+++ b/activerecord/test/fixtures/dog_lovers.yml
@@ -0,0 +1,4 @@
+david:
+ id: 1
+ bred_dogs_count: 0
+ trained_dogs_count: 1
diff --git a/activerecord/test/fixtures/dogs.yml b/activerecord/test/fixtures/dogs.yml
new file mode 100644
index 0000000000..16d19be2c5
--- /dev/null
+++ b/activerecord/test/fixtures/dogs.yml
@@ -0,0 +1,3 @@
+sophie:
+ id: 1
+ trainer_id: 1
diff --git a/activerecord/test/fixtures/other_topics.yml b/activerecord/test/fixtures/other_topics.yml
new file mode 100644
index 0000000000..93f48aedc4
--- /dev/null
+++ b/activerecord/test/fixtures/other_topics.yml
@@ -0,0 +1,42 @@
+first:
+ id: 1
+ title: The First Topic
+ author_name: David
+ author_email_address: david@loudthinking.com
+ written_on: 2003-07-16t15:28:11.2233+01:00
+ last_read: 2004-04-15
+ bonus_time: 2005-01-30t15:28:00.00+01:00
+ content: Have a nice day
+ approved: false
+ replies_count: 1
+
+second:
+ id: 2
+ title: The Second Topic of the day
+ author_name: Mary
+ written_on: 2004-07-15t15:28:00.0099+01:00
+ content: Have a nice day
+ approved: true
+ replies_count: 0
+ parent_id: 1
+ type: Reply
+
+third:
+ id: 3
+ title: The Third Topic of the day
+ author_name: Carl
+ written_on: 2005-07-15t15:28:00.0099+01:00
+ content: I'm a troll
+ approved: true
+ replies_count: 1
+
+fourth:
+ id: 4
+ title: The Fourth Topic of the day
+ author_name: Carl
+ written_on: 2006-07-15t15:28:00.0099+01:00
+ content: Why not?
+ approved: true
+ type: Reply
+ parent_id: 3
+
diff --git a/activerecord/test/fixtures/peoples_treasures.yml b/activerecord/test/fixtures/peoples_treasures.yml
new file mode 100644
index 0000000000..a72b190d0c
--- /dev/null
+++ b/activerecord/test/fixtures/peoples_treasures.yml
@@ -0,0 +1,3 @@
+michael_diamond:
+ rich_person_id: <%= ActiveRecord::Fixtures.identify(:michael) %>
+ treasure_id: <%= ActiveRecord::Fixtures.identify(:diamond) %>
diff --git a/activerecord/test/fixtures/randomly_named_a9.yml b/activerecord/test/fixtures/randomly_named_a9.yml
new file mode 100644
index 0000000000..bc51c83112
--- /dev/null
+++ b/activerecord/test/fixtures/randomly_named_a9.yml
@@ -0,0 +1,7 @@
+first_instance:
+ some_attribute: AAA
+ another_attribute: 000
+
+second_instance:
+ some_attribute: BBB
+ another_attribute: 999
diff --git a/activerecord/test/fixtures/teapots.yml b/activerecord/test/fixtures/teapots.yml
new file mode 100644
index 0000000000..ff515beb45
--- /dev/null
+++ b/activerecord/test/fixtures/teapots.yml
@@ -0,0 +1,3 @@
+bob:
+ id: 1
+ name: Bob
diff --git a/activerecord/test/migrations/broken/100_migration_that_raises_exception.rb b/activerecord/test/migrations/broken/100_migration_that_raises_exception.rb
deleted file mode 100644
index ffb224dad9..0000000000
--- a/activerecord/test/migrations/broken/100_migration_that_raises_exception.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class MigrationThatRaisesException < ActiveRecord::Migration
- def self.up
- add_column "people", "last_name", :string
- raise 'Something broke'
- end
-
- def self.down
- remove_column "people", "last_name"
- end
-end
diff --git a/activerecord/test/migrations/duplicate/3_foo.rb b/activerecord/test/migrations/duplicate/3_foo.rb
deleted file mode 100644
index 916fe580fa..0000000000
--- a/activerecord/test/migrations/duplicate/3_foo.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Foo < ActiveRecord::Migration
- def self.up
- end
-
- def self.down
- end
-end \ No newline at end of file
diff --git a/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb b/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb
deleted file mode 100644
index 5fe5089e18..0000000000
--- a/activerecord/test/migrations/duplicate_names/20080507052938_chunky.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Chunky < ActiveRecord::Migration
- def self.up
- end
-
- def self.down
- end
-end
diff --git a/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb b/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb
deleted file mode 100644
index 5fe5089e18..0000000000
--- a/activerecord/test/migrations/duplicate_names/20080507053028_chunky.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Chunky < ActiveRecord::Migration
- def self.up
- end
-
- def self.down
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb b/activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb
deleted file mode 100644
index bf912fbfc8..0000000000
--- a/activerecord/test/migrations/interleaved/pass_1/3_interleaved_innocent_jointable.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class InterleavedInnocentJointable < ActiveRecord::Migration
- def self.up
- create_table("people_reminders", :id => false) do |t|
- t.column :reminder_id, :integer
- t.column :person_id, :integer
- end
- end
-
- def self.down
- drop_table "people_reminders"
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb b/activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb
deleted file mode 100644
index c6c94213a0..0000000000
--- a/activerecord/test/migrations/interleaved/pass_2/1_interleaved_people_have_last_names.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class InterleavedPeopleHaveLastNames < ActiveRecord::Migration
- def self.up
- add_column "people", "last_name", :string
- end
-
- def self.down
- remove_column "people", "last_name"
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb b/activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb
deleted file mode 100644
index bf912fbfc8..0000000000
--- a/activerecord/test/migrations/interleaved/pass_2/3_interleaved_innocent_jointable.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class InterleavedInnocentJointable < ActiveRecord::Migration
- def self.up
- create_table("people_reminders", :id => false) do |t|
- t.column :reminder_id, :integer
- t.column :person_id, :integer
- end
- end
-
- def self.down
- drop_table "people_reminders"
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb b/activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb
deleted file mode 100644
index c6c94213a0..0000000000
--- a/activerecord/test/migrations/interleaved/pass_3/1_interleaved_people_have_last_names.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class InterleavedPeopleHaveLastNames < ActiveRecord::Migration
- def self.up
- add_column "people", "last_name", :string
- end
-
- def self.down
- remove_column "people", "last_name"
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb b/activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb
deleted file mode 100644
index 6849995f5e..0000000000
--- a/activerecord/test/migrations/interleaved/pass_3/2_interleaved_i_raise_on_down.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class InterleavedIRaiseOnDown < ActiveRecord::Migration
- def self.up
- end
-
- def self.down
- raise
- end
-end
diff --git a/activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb b/activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb
deleted file mode 100644
index bf912fbfc8..0000000000
--- a/activerecord/test/migrations/interleaved/pass_3/3_interleaved_innocent_jointable.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class InterleavedInnocentJointable < ActiveRecord::Migration
- def self.up
- create_table("people_reminders", :id => false) do |t|
- t.column :reminder_id, :integer
- t.column :person_id, :integer
- end
- end
-
- def self.down
- drop_table "people_reminders"
- end
-end
diff --git a/activerecord/test/migrations/rename/1_we_need_things.rb b/activerecord/test/migrations/rename/1_we_need_things.rb
new file mode 100644
index 0000000000..cdbe0b1679
--- /dev/null
+++ b/activerecord/test/migrations/rename/1_we_need_things.rb
@@ -0,0 +1,11 @@
+class WeNeedThings < ActiveRecord::Migration
+ def self.up
+ create_table("things") do |t|
+ t.column :content, :text
+ end
+ end
+
+ def self.down
+ drop_table "things"
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/migrations/rename/2_rename_things.rb b/activerecord/test/migrations/rename/2_rename_things.rb
new file mode 100644
index 0000000000..d441b71fc9
--- /dev/null
+++ b/activerecord/test/migrations/rename/2_rename_things.rb
@@ -0,0 +1,9 @@
+class RenameThings < ActiveRecord::Migration
+ def self.up
+ rename_table "things", "awesome_things"
+ end
+
+ def self.down
+ rename_table "awesome_things", "things"
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
new file mode 100644
index 0000000000..e438cf5999
--- /dev/null
+++ b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb
@@ -0,0 +1,9 @@
+class PeopleHaveLastNames < ActiveRecord::Migration
+ def self.up
+ add_column "people", "hobbies", :string
+ end
+
+ def self.down
+ remove_column "people", "hobbies"
+ end
+end
diff --git a/activerecord/test/migrations/duplicate/1_people_have_last_names.rb b/activerecord/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb
index 81af5fef5e..06cb911117 100644
--- a/activerecord/test/migrations/duplicate/1_people_have_last_names.rb
+++ b/activerecord/test/migrations/valid_with_subdirectories/1_valid_people_have_last_names.rb
@@ -1,4 +1,4 @@
-class PeopleHaveLastNames < ActiveRecord::Migration
+class ValidPeopleHaveLastNames < ActiveRecord::Migration
def self.up
add_column "people", "last_name", :string
end
@@ -6,4 +6,4 @@ class PeopleHaveLastNames < ActiveRecord::Migration
def self.down
remove_column "people", "last_name"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/migrations/duplicate/2_we_need_reminders.rb b/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
index d5e71ce8ef..d5e71ce8ef 100644
--- a/activerecord/test/migrations/duplicate/2_we_need_reminders.rb
+++ b/activerecord/test/migrations/valid_with_subdirectories/sub/2_we_need_reminders.rb
diff --git a/activerecord/test/migrations/duplicate/3_innocent_jointable.rb b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
index 21c9ca5328..21c9ca5328 100644
--- a/activerecord/test/migrations/duplicate/3_innocent_jointable.rb
+++ b/activerecord/test/migrations/valid_with_subdirectories/sub1/3_innocent_jointable.rb
diff --git a/activerecord/test/models/admin/randomly_named_c1.rb b/activerecord/test/models/admin/randomly_named_c1.rb
new file mode 100644
index 0000000000..2f81d5b831
--- /dev/null
+++ b/activerecord/test/models/admin/randomly_named_c1.rb
@@ -0,0 +1,3 @@
+class Admin::ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
+ self.table_name = :randomly_named_table
+end
diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb
index c12c88e195..ad30039304 100644
--- a/activerecord/test/models/admin/user.rb
+++ b/activerecord/test/models/admin/user.rb
@@ -1,4 +1,7 @@
class Admin::User < ActiveRecord::Base
belongs_to :account
store :settings, :accessors => [ :color, :homepage ]
+ store :preferences, :accessors => [ :remember_login ]
+ store :json_data, :accessors => [ :height, :weight ], :coder => JSON
+ store :json_data_empty, :accessors => [ :is_a_good_guy ], :coder => JSON
end
diff --git a/activerecord/test/models/arunit2_model.rb b/activerecord/test/models/arunit2_model.rb
new file mode 100644
index 0000000000..04b8b15d3d
--- /dev/null
+++ b/activerecord/test/models/arunit2_model.rb
@@ -0,0 +1,3 @@
+class ARUnit2Model < ActiveRecord::Base
+ self.abstract_class = true
+end
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 23db5650d4..14444a0092 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -49,12 +49,12 @@ class Author < ActiveRecord::Base
has_many :sti_post_comments, :through => :sti_posts, :source => :comments
has_many :special_nonexistant_posts, :class_name => "SpecialPost", :conditions => "posts.body = 'nonexistant'"
- has_many :special_nonexistant_post_comments, :through => :special_nonexistant_posts, :source => :comments, :conditions => "comments.post_id = 0"
+ has_many :special_nonexistant_post_comments, :through => :special_nonexistant_posts, :source => :comments, :conditions => { 'comments.post_id' => 0 }
has_many :nonexistant_comments, :through => :posts
has_many :hello_posts, :class_name => "Post", :conditions => "posts.body = 'hello'"
has_many :hello_post_comments, :through => :hello_posts, :source => :comments
- has_many :posts_with_no_comments, :class_name => 'Post', :conditions => 'comments.id is null', :include => :comments
+ has_many :posts_with_no_comments, :class_name => 'Post', :conditions => { 'comments.id' => nil }, :include => :comments
has_many :hello_posts_with_hash_conditions, :class_name => "Post",
:conditions => {:body => 'hello'}
@@ -128,7 +128,6 @@ class Author < ActiveRecord::Base
belongs_to :author_address, :dependent => :destroy
belongs_to :author_address_extra, :dependent => :delete, :class_name => "AuthorAddress"
- has_many :post_categories, :through => :posts, :source => :categories
has_many :category_post_comments, :through => :categories, :source => :post_comments
has_many :misc_posts, :class_name => 'Post',
@@ -141,8 +140,8 @@ class Author < ActiveRecord::Base
has_many :posts_with_default_include, :class_name => 'PostWithDefaultInclude'
has_many :comments_on_posts_with_default_include, :through => :posts_with_default_include, :source => :comments
- scope :relation_include_posts, includes(:posts)
- scope :relation_include_tags, includes(:tags)
+ scope :relation_include_posts, -> { includes(:posts) }
+ scope :relation_include_tags, -> { includes(:tags) }
attr_accessor :post_log
after_initialize :set_post_log
diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb
index e61d48e6a5..dff099c1fb 100644
--- a/activerecord/test/models/bird.rb
+++ b/activerecord/test/models/bird.rb
@@ -1,9 +1,12 @@
class Bird < ActiveRecord::Base
+ belongs_to :pirate
validates_presence_of :name
+ accepts_nested_attributes_for :pirate
+
attr_accessor :cancel_save_from_callback
before_save :cancel_save_callback_method, :if => :cancel_save_from_callback
def cancel_save_callback_method
false
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb
index 888afc7604..640e57555d 100644
--- a/activerecord/test/models/bulb.rb
+++ b/activerecord/test/models/bulb.rb
@@ -1,5 +1,5 @@
class Bulb < ActiveRecord::Base
- default_scope where(:name => 'defaulty')
+ default_scope { where(:name => 'defaulty') }
belongs_to :car
attr_protected :car_id, :frickinawesome
diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb
index b9c2e8ec9a..b4bc0ad5fa 100644
--- a/activerecord/test/models/car.rb
+++ b/activerecord/test/models/car.rb
@@ -9,20 +9,19 @@ class Car < ActiveRecord::Base
has_many :tyres
has_many :engines, :dependent => :destroy
- has_many :wheels, :as => :wheelable
+ has_many :wheels, :as => :wheelable, :dependent => :destroy
- scope :incl_tyres, includes(:tyres)
- scope :incl_engines, includes(:engines)
+ scope :incl_tyres, -> { includes(:tyres) }
+ scope :incl_engines, -> { includes(:engines) }
- scope :order_using_new_style, order('name asc')
- scope :order_using_old_style, :order => 'name asc'
+ scope :order_using_new_style, -> { order('name asc') }
end
class CoolCar < Car
- default_scope :order => 'name desc'
+ default_scope { order('name desc') }
end
class FastCar < Car
- default_scope :order => 'name desc'
+ default_scope { order('name desc') }
end
diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb
index 4bd980e606..6588531de6 100644
--- a/activerecord/test/models/categorization.rb
+++ b/activerecord/test/models/categorization.rb
@@ -12,7 +12,7 @@ end
class SpecialCategorization < ActiveRecord::Base
self.table_name = 'categorizations'
- default_scope where(:special => true)
+ default_scope { where(:special => true) }
belongs_to :author
belongs_to :category
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index 02b85fd38a..ab3139680c 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -27,7 +27,7 @@ class Category < ActiveRecord::Base
has_many :authors, :through => :categorizations
has_many :authors_with_select, :through => :categorizations, :source => :author, :select => 'authors.*, categorizations.post_id'
- scope :general, :conditions => { :name => 'General' }
+ scope :general, -> { where(:name => 'General') }
end
class SpecialCategory < Category
diff --git a/activerecord/test/models/college.rb b/activerecord/test/models/college.rb
new file mode 100644
index 0000000000..c7495d7deb
--- /dev/null
+++ b/activerecord/test/models/college.rb
@@ -0,0 +1,5 @@
+require_dependency 'models/arunit2_model'
+
+class College < ARUnit2Model
+ has_many :courses
+end
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index 88b139d931..3e9f1b0635 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -1,16 +1,16 @@
class Comment < ActiveRecord::Base
scope :limit_by, lambda {|l| limit(l) }
- 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 }
- scope :for_first_author,
- :joins => :post,
- :conditions => { "posts.author_id" => 1 }
- scope :created
+ scope :containing_the_letter_e, -> { where("comments.body LIKE '%e%'") }
+ scope :not_again, -> { where("comments.body NOT LIKE '%again%'") }
+ scope :for_first_post, -> { where(:post_id => 1) }
+ scope :for_first_author, -> { joins(:post).where("posts.author_id" => 1) }
+ scope :created, -> { scoped }
belongs_to :post, :counter_cache => true
has_many :ratings
+ belongs_to :first_post, :foreign_key => :post_id
+
has_many :children, :class_name => 'Comment', :foreign_key => :parent_id
belongs_to :parent, :class_name => 'Comment', :counter_cache => :children_count
@@ -19,13 +19,13 @@ class Comment < ActiveRecord::Base
end
def self.search_by_type(q)
- self.find(:all, :conditions => ["#{QUOTED_TYPE} = ?", q])
+ self.scoped(:where => ["#{QUOTED_TYPE} = ?", q]).all
end
def self.all_as_method
all
end
- scope :all_as_scope, {}
+ scope :all_as_scope, -> { scoped }
end
class SpecialComment < Comment
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index c1f7a4171a..7b993d5a2c 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -4,7 +4,7 @@ end
class Company < AbstractCompany
attr_protected :rating
- set_sequence_name :companies_nonstd_seq
+ self.sequence_name = :companies_nonstd_seq
validates_presence_of :name
@@ -42,8 +42,10 @@ class Firm < Company
:before_remove => :log_before_remove,
:after_remove => :log_after_remove
has_many :unsorted_clients, :class_name => "Client"
+ has_many :unsorted_clients_with_symbol, :class_name => :Client
has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
+ has_many :clients_ordered_by_name, :order => "name", :class_name => "Client"
has_many :unvalidated_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :validate => false
has_many :dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :destroy
has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all
@@ -87,6 +89,8 @@ class Firm < Company
has_many :accounts
has_many :unautosaved_accounts, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false
+ has_many :association_with_references, :class_name => 'Client', :references => :foo
+
def log
@log ||= []
end
@@ -194,6 +198,11 @@ class Account < ActiveRecord::Base
@destroyed_account_ids ||= Hash.new { |h,k| h[k] = [] }
end
+ # Test private kernel method through collection proxy using has_many.
+ def self.open
+ where('firm_name = ?', '37signals')
+ end
+
before_destroy do |account|
if account.firm
Account.destroyed_account_ids[account.firm.id] << account.id
diff --git a/activerecord/test/models/country.rb b/activerecord/test/models/country.rb
index 15e3a1de0b..7db9a4e731 100644
--- a/activerecord/test/models/country.rb
+++ b/activerecord/test/models/country.rb
@@ -1,6 +1,6 @@
class Country < ActiveRecord::Base
- set_primary_key :country_id
+ self.primary_key = :country_id
has_and_belongs_to_many :treaties
diff --git a/activerecord/test/models/course.rb b/activerecord/test/models/course.rb
index 8a40fa740d..f3d0e05ff7 100644
--- a/activerecord/test/models/course.rb
+++ b/activerecord/test/models/course.rb
@@ -1,3 +1,6 @@
-class Course < ActiveRecord::Base
+require_dependency 'models/arunit2_model'
+
+class Course < ARUnit2Model
+ belongs_to :college
has_many :entrants
end
diff --git a/activerecord/test/models/dashboard.rb b/activerecord/test/models/dashboard.rb
index a8a25834b1..1b3b54545f 100644
--- a/activerecord/test/models/dashboard.rb
+++ b/activerecord/test/models/dashboard.rb
@@ -1,3 +1,3 @@
class Dashboard < ActiveRecord::Base
- set_primary_key :dashboard_id
-end \ No newline at end of file
+ self.primary_key = :dashboard_id
+end
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index 4dc9fff9fd..83482f4d07 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -2,20 +2,20 @@ require 'ostruct'
module DeveloperProjectsAssociationExtension
def find_most_recent
- find(:first, :order => "id DESC")
+ scoped(:order => "id DESC").first
end
end
module DeveloperProjectsAssociationExtension2
def find_least_recent
- find(:first, :order => "id ASC")
+ scoped(:order => "id ASC").first
end
end
class Developer < ActiveRecord::Base
has_and_belongs_to_many :projects do
def find_most_recent
- find(:first, :order => "id DESC")
+ scoped(:order => "id DESC").first
end
end
@@ -37,7 +37,7 @@ class Developer < ActiveRecord::Base
:association_foreign_key => "project_id",
:extend => DeveloperProjectsAssociationExtension do
def find_least_recent
- find(:first, :order => "id ASC")
+ scoped(:order => "id ASC").first
end
end
@@ -45,7 +45,7 @@ class Developer < ActiveRecord::Base
has_many :audit_logs
- scope :jamises, :conditions => {:name => 'Jamis'}
+ scope :jamises, -> { where(:name => 'Jamis') }
validates_inclusion_of :salary, :in => 50000..200000
validates_length_of :name, :within => 3..20
@@ -57,12 +57,6 @@ class Developer < ActiveRecord::Base
def log=(message)
audit_logs.build :message => message
end
-
- def self.all_johns
- self.with_exclusive_scope :find => where(:name => 'John') do
- self.all
- end
- end
end
class AuditLog < ActiveRecord::Base
@@ -88,31 +82,25 @@ end
class DeveloperWithSelect < ActiveRecord::Base
self.table_name = 'developers'
- default_scope select('name')
+ default_scope { select('name') }
end
class DeveloperWithIncludes < ActiveRecord::Base
self.table_name = 'developers'
has_many :audit_logs, :foreign_key => :developer_id
- default_scope includes(:audit_logs)
+ default_scope { includes(:audit_logs) }
end
class DeveloperOrderedBySalary < ActiveRecord::Base
self.table_name = 'developers'
- default_scope :order => 'salary DESC'
-
- scope :by_name, order('name DESC')
+ default_scope { order('salary DESC') }
- def self.all_ordered_by_name
- with_scope(:find => { :order => 'name DESC' }) do
- find(:all)
- end
- end
+ scope :by_name, -> { order('name DESC') }
end
class DeveloperCalledDavid < ActiveRecord::Base
self.table_name = 'developers'
- default_scope where("name = 'David'")
+ default_scope { where("name = 'David'") }
end
class LazyLambdaDeveloperCalledDavid < ActiveRecord::Base
@@ -140,7 +128,7 @@ end
class ClassMethodReferencingScopeDeveloperCalledDavid < ActiveRecord::Base
self.table_name = 'developers'
- scope :david, where(:name => 'David')
+ scope :david, -> { where(:name => 'David') }
def self.default_scope
david
@@ -149,40 +137,40 @@ end
class LazyBlockReferencingScopeDeveloperCalledDavid < ActiveRecord::Base
self.table_name = 'developers'
- scope :david, where(:name => 'David')
+ scope :david, -> { where(:name => 'David') }
default_scope { david }
end
class DeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
- default_scope where(:name => 'Jamis')
- scope :poor, where('salary < 150000')
+ default_scope { where(:name => 'Jamis') }
+ scope :poor, -> { where('salary < 150000') }
end
class PoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
- default_scope where(:name => 'Jamis', :salary => 50000)
+ default_scope -> { where(:name => 'Jamis', :salary => 50000) }
end
class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis
self.table_name = 'developers'
- default_scope where(:salary => 50000)
+ default_scope -> { where(:salary => 50000) }
end
class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
- default_scope where(:name => 'Jamis')
- default_scope where(:salary => 50000)
+ default_scope -> { where(:name => 'Jamis') }
+ default_scope -> { where(:salary => 50000) }
end
module SalaryDefaultScope
extend ActiveSupport::Concern
- included { default_scope where(:salary => 50000) }
+ included { default_scope { where(:salary => 50000) } }
end
class ModuleIncludedPoorDeveloperCalledJamis < DeveloperCalledJamis
@@ -195,7 +183,7 @@ class EagerDeveloperWithDefaultScope < ActiveRecord::Base
self.table_name = 'developers'
has_and_belongs_to_many :projects, :foreign_key => 'developer_id', :join_table => 'developers_projects', :order => 'projects.id'
- default_scope includes(:projects)
+ default_scope { includes(:projects) }
end
class EagerDeveloperWithClassMethodDefaultScope < ActiveRecord::Base
diff --git a/activerecord/test/models/dog.rb b/activerecord/test/models/dog.rb
new file mode 100644
index 0000000000..72b7d33a86
--- /dev/null
+++ b/activerecord/test/models/dog.rb
@@ -0,0 +1,4 @@
+class Dog < ActiveRecord::Base
+ belongs_to :breeder, :class_name => "DogLover", :counter_cache => :bred_dogs_count
+ belongs_to :trainer, :class_name => "DogLover", :counter_cache => :trained_dogs_count
+end
diff --git a/activerecord/test/models/dog_lover.rb b/activerecord/test/models/dog_lover.rb
new file mode 100644
index 0000000000..a33dc575c5
--- /dev/null
+++ b/activerecord/test/models/dog_lover.rb
@@ -0,0 +1,4 @@
+class DogLover < ActiveRecord::Base
+ has_many :trained_dogs, :class_name => "Dog", :foreign_key => :trainer_id
+ has_many :bred_dogs, :class_name => "Dog", :foreign_key => :breeder_id
+end
diff --git a/activerecord/test/models/joke.rb b/activerecord/test/models/joke.rb
index d7f01e59e6..edda4655dc 100644
--- a/activerecord/test/models/joke.rb
+++ b/activerecord/test/models/joke.rb
@@ -1,7 +1,7 @@
class Joke < ActiveRecord::Base
- set_table_name 'funny_jokes'
+ self.table_name = 'funny_jokes'
end
class GoodJoke < ActiveRecord::Base
- set_table_name 'funny_jokes'
+ self.table_name = 'funny_jokes'
end
diff --git a/activerecord/test/models/keyboard.rb b/activerecord/test/models/keyboard.rb
index 32a4a7fad0..39347e274e 100644
--- a/activerecord/test/models/keyboard.rb
+++ b/activerecord/test/models/keyboard.rb
@@ -1,3 +1,3 @@
class Keyboard < ActiveRecord::Base
- set_primary_key 'key_number'
+ self.primary_key = 'key_number'
end
diff --git a/activerecord/test/models/legacy_thing.rb b/activerecord/test/models/legacy_thing.rb
index eaeb642d12..eead181a0e 100644
--- a/activerecord/test/models/legacy_thing.rb
+++ b/activerecord/test/models/legacy_thing.rb
@@ -1,3 +1,3 @@
class LegacyThing < ActiveRecord::Base
- set_locking_column :version
+ self.locking_column = :version
end
diff --git a/activerecord/test/models/liquid.rb b/activerecord/test/models/liquid.rb
index b96c054f6c..3fcd5e4b69 100644
--- a/activerecord/test/models/liquid.rb
+++ b/activerecord/test/models/liquid.rb
@@ -1,5 +1,5 @@
class Liquid < ActiveRecord::Base
- set_table_name :liquid
+ self.table_name = :liquid
has_many :molecules, :uniq => true
end
diff --git a/activerecord/test/models/minivan.rb b/activerecord/test/models/minivan.rb
index 830cdb5796..4fe79720ad 100644
--- a/activerecord/test/models/minivan.rb
+++ b/activerecord/test/models/minivan.rb
@@ -1,5 +1,5 @@
class Minivan < ActiveRecord::Base
- set_primary_key :minivan_id
+ self.primary_key = :minivan_id
belongs_to :speedometer
has_one :dashboard, :through => :speedometer
diff --git a/activerecord/test/models/mixed_case_monkey.rb b/activerecord/test/models/mixed_case_monkey.rb
index 853f2682b3..763baefd91 100644
--- a/activerecord/test/models/mixed_case_monkey.rb
+++ b/activerecord/test/models/mixed_case_monkey.rb
@@ -1,3 +1,3 @@
class MixedCaseMonkey < ActiveRecord::Base
- set_primary_key 'monkeyID'
+ self.primary_key = 'monkeyID'
end
diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb
index 4a4111833f..72e7bade68 100644
--- a/activerecord/test/models/organization.rb
+++ b/activerecord/test/models/organization.rb
@@ -8,5 +8,5 @@ class Organization < ActiveRecord::Base
has_one :author, :primary_key => :name
has_one :author_owned_essay_category, :through => :author, :source => :owned_essay_category
- scope :clubs, { :from => 'clubs' }
+ scope :clubs, -> { from('clubs') }
end
diff --git a/activerecord/test/models/owner.rb b/activerecord/test/models/owner.rb
index 5760b991ec..fea55f4535 100644
--- a/activerecord/test/models/owner.rb
+++ b/activerecord/test/models/owner.rb
@@ -1,5 +1,5 @@
class Owner < ActiveRecord::Base
- set_primary_key :owner_id
+ self.primary_key = :owner_id
has_many :pets
has_many :toys, :through => :pets
end
diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb
index 737ef9131b..c4ee2bd19d 100644
--- a/activerecord/test/models/parrot.rb
+++ b/activerecord/test/models/parrot.rb
@@ -1,5 +1,6 @@
class Parrot < ActiveRecord::Base
- set_inheritance_column :parrot_sti_class
+ self.inheritance_column = :parrot_sti_class
+
has_and_belongs_to_many :pirates
has_and_belongs_to_many :treasures
has_many :loots, :as => :looter
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 967a3625aa..d5c0b351aa 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -1,9 +1,12 @@
class Person < ActiveRecord::Base
has_many :readers
+ has_many :secure_readers
has_one :reader
has_many :posts, :through => :readers
- has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, :conditions => 'comments.id is null'
+ has_many :secure_posts, :through => :secure_readers
+ has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments,
+ :conditions => 'comments.id is null', :references => :comments
has_many :references
has_many :bad_references
@@ -24,8 +27,8 @@ class Person < ActiveRecord::Base
has_many :agents_posts, :through => :agents, :source => :posts
has_many :agents_posts_authors, :through => :agents_posts, :source => :author
- scope :males, :conditions => { :gender => 'M' }
- scope :females, :conditions => { :gender => 'F' }
+ scope :males, -> { where(:gender => 'M') }
+ scope :females, -> { where(:gender => 'F') }
end
class PersonWithDependentDestroyJobs < ActiveRecord::Base
@@ -54,7 +57,7 @@ class LoosePerson < ActiveRecord::Base
self.table_name = 'people'
self.abstract_class = true
- attr_protected :comments
+ attr_protected :comments, :best_friend_id, :best_friend_of_id
attr_protected :as => :admin
has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id
@@ -81,4 +84,10 @@ class TightPerson < ActiveRecord::Base
accepts_nested_attributes_for :best_friend, :best_friend_of, :best_friends
end
-class TightDescendant < TightPerson; end \ No newline at end of file
+class TightDescendant < TightPerson; end
+
+class RichPerson < ActiveRecord::Base
+ self.table_name = 'people'
+
+ has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures'
+end
diff --git a/activerecord/test/models/pet.rb b/activerecord/test/models/pet.rb
index 113826756a..3cd5bceed5 100644
--- a/activerecord/test/models/pet.rb
+++ b/activerecord/test/models/pet.rb
@@ -2,7 +2,7 @@ class Pet < ActiveRecord::Base
attr_accessor :current_user
- set_primary_key :pet_id
+ self.primary_key = :pet_id
belongs_to :owner, :touch => true
has_many :toys
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 198a963cbc..1aaf9a1b82 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -5,15 +5,10 @@ class Post < ActiveRecord::Base
end
end
- scope :containing_the_letter_a, where("body LIKE '%a%'")
- scope :ranked_by_comments, order("comments_count DESC")
+ 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'
- }
- }
belongs_to :author do
def greeting
@@ -24,22 +19,28 @@ class Post < ActiveRecord::Base
belongs_to :author_with_posts, :class_name => "Author", :foreign_key => :author_id, :include => :posts
belongs_to :author_with_address, :class_name => "Author", :foreign_key => :author_id, :include => :author_address
+ def first_comment
+ super.body
+ end
+ has_one :first_comment, :class_name => 'Comment', :order => 'id ASC'
has_one :last_comment, :class_name => 'Comment', :order => 'id desc'
- 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} } }
- }
+ scope :with_special_comments, -> { joins(:comments).where(:comments => {:type => 'SpecialComment'}) }
+ scope :with_very_special_comments, -> { joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) }
+ scope :with_post, ->(post_id) { joins(:comments).where(:comments => { :post_id => post_id }) }
has_many :comments do
def find_most_recent
- find(:first, :order => "id DESC")
+ scoped(:order => "id DESC").first
end
def newest
created.last
end
+
+ def the_association
+ proxy_association
+ end
end
has_many :author_favorites, :through => :author
@@ -63,8 +64,8 @@ class Post < ActiveRecord::Base
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings do
def add_joins_and_select
- find :all, :select => 'tags.*, authors.id as author_id',
- :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
+ scoped(:select => 'tags.*, authors.id as author_id',
+ :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id').all
end
end
@@ -107,8 +108,10 @@ class Post < ActiveRecord::Base
has_many :named_categories, :through => :standard_categorizations
has_many :readers
+ has_many :secure_readers
has_many :readers_with_person, :include => :person, :class_name => "Reader"
has_many :people, :through => :readers
+ has_many :secure_people, :through => :secure_readers
has_many :single_people, :through => :readers
has_many :people_with_callbacks, :source=>:person, :through => :readers,
:before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) },
@@ -161,7 +164,7 @@ end
class FirstPost < ActiveRecord::Base
self.table_name = 'posts'
- default_scope where(:id => 1)
+ default_scope { where(:id => 1) }
has_many :comments, :foreign_key => :post_id
has_one :comment, :foreign_key => :post_id
@@ -169,16 +172,16 @@ end
class PostWithDefaultInclude < ActiveRecord::Base
self.table_name = 'posts'
- default_scope includes(:comments)
+ default_scope { includes(:comments) }
has_many :comments, :foreign_key => :post_id
end
class PostWithDefaultScope < ActiveRecord::Base
self.table_name = 'posts'
- default_scope :order => :title
+ default_scope { order(:title) }
end
class SpecialPostWithDefaultScope < ActiveRecord::Base
self.table_name = 'posts'
- default_scope where(:id => [1, 5,6])
-end \ No newline at end of file
+ default_scope { where(:id => [1, 5,6]) }
+end
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index efe1ce67da..32ce164995 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -32,7 +32,7 @@ class Project < ActiveRecord::Base
def self.all_as_method
all
end
- scope :all_as_scope, {}
+ scope :all_as_scope, -> { scoped }
end
class SpecialProject < Project
diff --git a/activerecord/test/models/randomly_named_c1.rb b/activerecord/test/models/randomly_named_c1.rb
new file mode 100644
index 0000000000..18a86c4989
--- /dev/null
+++ b/activerecord/test/models/randomly_named_c1.rb
@@ -0,0 +1,3 @@
+class ClassNameThatDoesNotFollowCONVENTIONS < ActiveRecord::Base
+ self.table_name = :randomly_named_table
+end
diff --git a/activerecord/test/models/reader.rb b/activerecord/test/models/reader.rb
index 0207a2bd92..59005ac604 100644
--- a/activerecord/test/models/reader.rb
+++ b/activerecord/test/models/reader.rb
@@ -3,3 +3,12 @@ class Reader < ActiveRecord::Base
belongs_to :person, :inverse_of => :readers
belongs_to :single_person, :class_name => 'Person', :foreign_key => :person_id, :inverse_of => :reader
end
+
+class SecureReader < ActiveRecord::Base
+ self.table_name = "readers"
+
+ belongs_to :secure_post, :class_name => "Post", :foreign_key => "post_id"
+ belongs_to :secure_person, :inverse_of => :secure_readers, :class_name => "Person", :foreign_key => "person_id"
+
+ attr_accessible nil
+end
diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb
index c5af0b5d5f..561b431766 100644
--- a/activerecord/test/models/reference.rb
+++ b/activerecord/test/models/reference.rb
@@ -19,5 +19,5 @@ end
class BadReference < ActiveRecord::Base
self.table_name = 'references'
- default_scope where(:favourite => false)
+ default_scope { where(:favourite => false) }
end
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index 6adfe0ae3c..53bc95e5f2 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -1,7 +1,7 @@
require 'models/topic'
class Reply < Topic
- scope :base
+ scope :base, -> { scoped }
belongs_to :topic, :foreign_key => "parent_id", :counter_cache => true
belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count"
diff --git a/activerecord/test/models/speedometer.rb b/activerecord/test/models/speedometer.rb
index 94743eff8e..0a7d38d8ec 100644
--- a/activerecord/test/models/speedometer.rb
+++ b/activerecord/test/models/speedometer.rb
@@ -1,4 +1,4 @@
class Speedometer < ActiveRecord::Base
- set_primary_key :speedometer_id
+ self.primary_key = :speedometer_id
belongs_to :dashboard
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/string_key_object.rb b/activerecord/test/models/string_key_object.rb
index f8d4c6e0e4..f084ec1bdc 100644
--- a/activerecord/test/models/string_key_object.rb
+++ b/activerecord/test/models/string_key_object.rb
@@ -1,3 +1,3 @@
class StringKeyObject < ActiveRecord::Base
- set_primary_key :id
+ self.primary_key = :id
end
diff --git a/activerecord/test/models/subscriber.rb b/activerecord/test/models/subscriber.rb
index 5b78014e6f..76e85a0cd3 100644
--- a/activerecord/test/models/subscriber.rb
+++ b/activerecord/test/models/subscriber.rb
@@ -1,5 +1,5 @@
class Subscriber < ActiveRecord::Base
- set_primary_key 'nick'
+ self.primary_key = 'nick'
has_many :subscriptions
has_many :books, :through => :subscriptions
end
diff --git a/activerecord/test/models/teapot.rb b/activerecord/test/models/teapot.rb
new file mode 100644
index 0000000000..b035b18c1b
--- /dev/null
+++ b/activerecord/test/models/teapot.rb
@@ -0,0 +1,35 @@
+class Teapot
+ # I'm a little teapot,
+ # Short and stout,
+ # Here is my handle
+ # Here is my spout
+ # When I get all steamed up,
+ # Hear me shout,
+ # Tip me over and pour me out!
+ #
+ # HELL YEAH TEAPOT SONG
+
+ include ActiveRecord::Model
+end
+
+class OtherTeapot < Teapot
+end
+
+class OMFGIMATEAPOT
+ def aaahhh
+ "mmm"
+ end
+end
+
+class CoolTeapot < OMFGIMATEAPOT
+ include ActiveRecord::Model
+ self.table_name = "teapots"
+end
+
+class Ceiling
+ include ActiveRecord::Model
+
+ class Teapot
+ include ActiveRecord::Model
+ end
+end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index fe424e61b2..079e403444 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -1,19 +1,20 @@
class Topic < ActiveRecord::Base
- scope :base
+ scope :base, -> { scoped }
scope :written_before, lambda { |time|
if time
- { :conditions => ['written_on < ?', time] }
+ where 'written_on < ?', time
end
}
- scope :approved, :conditions => {:approved => true}
- scope :rejected, :conditions => {:approved => false}
+ scope :approved, -> { where(:approved => true) }
+ scope :rejected, -> { where(:approved => false) }
- scope :by_lifo, :conditions => {:author_name => 'lifo'}
+ scope :scope_with_lambda, lambda { scoped }
- scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}}
- scope 'approved_as_string', :conditions => {:approved => true}
- scope :replied, :conditions => ['replies_count > 0']
- scope :anonymous_extension do
+ scope :by_lifo, -> { where(:author_name => 'lifo') }
+ scope :replied, -> { where 'replies_count > 0' }
+
+ scope 'approved_as_string', -> { where(:approved => true) }
+ scope :anonymous_extension, -> { scoped } do
def one
1
end
@@ -30,18 +31,6 @@ class Topic < ActiveRecord::Base
2
end
end
- module MultipleExtensionOne
- def extension_one
- 1
- end
- end
- module MultipleExtensionTwo
- def extension_two
- 2
- end
- end
- scope :named_extension, :extend => NamedExtension
- scope :multiple_extensions, :extend => [MultipleExtensionTwo, MultipleExtensionOne]
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title"
@@ -78,6 +67,11 @@ class Topic < ActiveRecord::Base
after_initialize :set_email_address
+ class_attribute :after_initialize_called
+ after_initialize do
+ self.class.after_initialize_called = true
+ end
+
def approved=(val)
@custom_approved = val
write_attribute(:approved, val)
@@ -106,6 +100,10 @@ class Topic < ActiveRecord::Base
def after_create_for_transaction; end
end
+class ImportantTopic < Topic
+ serialize :important, Hash
+end
+
module Web
class Topic < ActiveRecord::Base
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id", :class_name => 'Web::Reply'
diff --git a/activerecord/test/models/toy.rb b/activerecord/test/models/toy.rb
index 6c45e99671..ddc7048a56 100644
--- a/activerecord/test/models/toy.rb
+++ b/activerecord/test/models/toy.rb
@@ -1,6 +1,6 @@
class Toy < ActiveRecord::Base
- set_primary_key :toy_id
+ self.primary_key = :toy_id
belongs_to :pet
- scope :with_pet, joins(:pet)
+ scope :with_pet, -> { joins(:pet) }
end
diff --git a/activerecord/test/models/treaty.rb b/activerecord/test/models/treaty.rb
index b46537f0d2..41fd1350f3 100644
--- a/activerecord/test/models/treaty.rb
+++ b/activerecord/test/models/treaty.rb
@@ -1,6 +1,6 @@
class Treaty < ActiveRecord::Base
- set_primary_key :treaty_id
+ self.primary_key = :treaty_id
has_and_belongs_to_many :countries
diff --git a/activerecord/test/models/warehouse_thing.rb b/activerecord/test/models/warehouse_thing.rb
index 6ace1183cc..f20bd1a245 100644
--- a/activerecord/test/models/warehouse_thing.rb
+++ b/activerecord/test/models/warehouse_thing.rb
@@ -1,5 +1,5 @@
class WarehouseThing < ActiveRecord::Base
- set_table_name "warehouse-things"
+ self.table_name = "warehouse-things"
validates_uniqueness_of :value
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb
index 184ab1649e..50c824e4ac 100644
--- a/activerecord/test/models/without_table.rb
+++ b/activerecord/test/models/without_table.rb
@@ -1,3 +1,3 @@
class WithoutTable < ActiveRecord::Base
- default_scope where(:published => true)
+ default_scope -> { where(:published => true) }
end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index ab2c7ccc10..65b6f9f227 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -1,5 +1,5 @@
ActiveRecord::Schema.define do
- create_table :binary_fields, :force => true, :options => 'CHARACTER SET latin1' do |t|
+ create_table :binary_fields, :force => true do |t|
t.binary :tiny_blob, :limit => 255
t.binary :normal_blob, :limit => 65535
t.binary :medium_blob, :limit => 16777215
@@ -32,4 +32,4 @@ CREATE TABLE collation_tests (
) CHARACTER SET utf8 COLLATE utf8_general_ci
SQL
-end \ No newline at end of file
+end
diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb
index a0adfe3752..7d324f98c4 100644
--- a/activerecord/test/schema/mysql_specific_schema.rb
+++ b/activerecord/test/schema/mysql_specific_schema.rb
@@ -1,5 +1,5 @@
ActiveRecord::Schema.define do
- create_table :binary_fields, :force => true, :options => 'CHARACTER SET latin1' do |t|
+ create_table :binary_fields, :force => true do |t|
t.binary :tiny_blob, :limit => 255
t.binary :normal_blob, :limit => 65535
t.binary :medium_blob, :limit => 16777215
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index 5cf9a207f3..e51db50ae3 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -1,8 +1,8 @@
ActiveRecord::Schema.define do
- %w(postgresql_tsvectors postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
- postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones).each do |table_name|
- execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
+ %w(postgresql_tsvectors postgresql_hstores postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings
+ postgresql_oids postgresql_xml_data_type defaults geometrics postgresql_timestamp_with_zones postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
+ execute "DROP TABLE IF EXISTS #{quote_table_name table_name}"
end
execute 'DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE'
@@ -10,6 +10,8 @@ ActiveRecord::Schema.define do
execute "ALTER TABLE companies ALTER COLUMN id SET DEFAULT nextval('companies_nonstd_seq')"
execute 'DROP SEQUENCE IF EXISTS companies_id_seq'
+ execute 'DROP FUNCTION IF EXISTS partitioned_insert_trigger()'
+
%w(accounts_id_seq developers_id_seq projects_id_seq topics_id_seq customers_id_seq orders_id_seq).each do |seq_name|
execute "SELECT setval('#{seq_name}', 100)"
end
@@ -63,6 +65,15 @@ _SQL
);
_SQL
+ if 't' == select_value("select 'hstore'=ANY(select typname from pg_type)")
+ execute <<_SQL
+ CREATE TABLE postgresql_hstores (
+ id SERIAL PRIMARY KEY,
+ hash_store hstore default ''::hstore
+ );
+_SQL
+ end
+
execute <<_SQL
CREATE TABLE postgresql_moneys (
id SERIAL PRIMARY KEY,
@@ -116,6 +127,37 @@ _SQL
);
_SQL
+begin
+ execute <<_SQL
+ CREATE TABLE postgresql_partitioned_table_parent (
+ id SERIAL PRIMARY KEY,
+ number integer
+ );
+ CREATE TABLE postgresql_partitioned_table ( )
+ INHERITS (postgresql_partitioned_table_parent);
+
+ CREATE OR REPLACE FUNCTION partitioned_insert_trigger()
+ RETURNS TRIGGER AS $$
+ BEGIN
+ INSERT INTO postgresql_partitioned_table VALUES (NEW.*);
+ RETURN NULL;
+ END;
+ $$
+ LANGUAGE plpgsql;
+
+ CREATE TRIGGER insert_partitioning_trigger
+ BEFORE INSERT ON postgresql_partitioned_table_parent
+ FOR EACH ROW EXECUTE PROCEDURE partitioned_insert_trigger();
+_SQL
+rescue ActiveRecord::StatementInvalid => e
+ if e.message =~ /language "plpgsql" does not exist/
+ execute "CREATE LANGUAGE 'plpgsql';"
+ retry
+ else
+ raise e
+ end
+end
+
begin
execute <<_SQL
CREATE TABLE postgresql_xml_data_type (
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index bb08f5c181..5bcb9652cd 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -37,7 +37,12 @@ ActiveRecord::Schema.define do
create_table :admin_users, :force => true do |t|
t.string :name
- t.text :settings
+ t.text :settings, :null => true
+ # MySQL does not allow default values for blobs. Fake it out with a
+ # big varchar below.
+ t.string :preferences, :null => false, :default => '', :limit => 1024
+ t.string :json_data, :null => true, :limit => 1024
+ t.string :json_data_empty, :null => false, :default => "", :limit => 1024
t.references :account
end
@@ -75,6 +80,7 @@ ActiveRecord::Schema.define do
create_table :binaries, :force => true do |t|
t.string :name
t.binary :data
+ t.binary :short_data, :limit => 2048
end
create_table :birds, :force => true do |t|
@@ -90,6 +96,7 @@ ActiveRecord::Schema.define do
create_table :booleans, :force => true do |t|
t.boolean :value
+ t.boolean :has_fun, :null => false, :default => false
end
create_table :bulbs, :force => true do |t|
@@ -107,6 +114,7 @@ ActiveRecord::Schema.define do
t.string :name
t.integer :engines_count
t.integer :wheels_count
+ t.column :lock_version, :integer, :null => false, :default => 0
end
create_table :categories, :force => true do |t|
@@ -170,9 +178,11 @@ ActiveRecord::Schema.define do
t.integer :client_of
t.integer :rating, :default => 1
t.integer :account_id
+ t.string :description, :null => false, :default => ""
end
add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index"
+ add_index :companies, [:firm_id, :type], :name => "company_partial_index", :where => "rating > 10"
create_table :computers, :force => true do |t|
t.integer :developer, :null => false
@@ -212,6 +222,16 @@ ActiveRecord::Schema.define do
t.integer :access_level, :default => 1
end
+ create_table :dog_lovers, :force => true do |t|
+ t.integer :trained_dogs_count, :default => 0
+ t.integer :bred_dogs_count, :default => 0
+ end
+
+ create_table :dogs, :force => true do |t|
+ t.integer :trainer_id
+ t.integer :breeder_id
+ end
+
create_table :edges, :force => true, :id => false do |t|
t.column :source_id, :integer, :null => false
t.column :sink_id, :integer, :null => false
@@ -412,6 +432,7 @@ ActiveRecord::Schema.define do
t.string :name
t.column :updated_at, :datetime
t.column :happy_at, :datetime
+ t.column :eats_at, :time
t.string :essay_id
end
@@ -425,6 +446,7 @@ ActiveRecord::Schema.define do
create_table :parrots, :force => true do |t|
t.column :name, :string
+ t.column :color, :string
t.column :parrot_sti_class, :string
t.column :killer_id, :integer
t.column :created_at, :datetime
@@ -455,6 +477,11 @@ ActiveRecord::Schema.define do
t.timestamps
end
+ create_table :peoples_treasures, :id => false, :force => true do |t|
+ t.column :rich_person_id, :integer
+ t.column :treasure_id, :integer
+ end
+
create_table :pets, :primary_key => :pet_id ,:force => true do |t|
t.string :name
t.integer :owner_id, :integer
@@ -505,6 +532,11 @@ ActiveRecord::Schema.define do
t.string :type
end
+ create_table :randomly_named_table, :force => true do |t|
+ t.string :some_attribute
+ t.integer :another_attribute
+ end
+
create_table :ratings, :force => true do |t|
t.integer :comment_id
t.integer :value
@@ -596,6 +628,12 @@ ActiveRecord::Schema.define do
t.datetime :ending
end
+ create_table :teapots, :force => true do |t|
+ t.string :name
+ t.string :type
+ t.timestamps
+ end
+
create_table :topics, :force => true do |t|
t.string :title
t.string :author_name
@@ -607,8 +645,10 @@ ActiveRecord::Schema.define do
# Oracle SELECT WHERE clause which causes many unit test failures
if current_adapter?(:OracleAdapter)
t.string :content, :limit => 4000
+ t.string :important, :limit => 4000
else
t.text :content
+ t.text :important
end
t.boolean :approved, :default => true
t.integer :replies_count, :default => 0
@@ -700,8 +740,6 @@ ActiveRecord::Schema.define do
create_table :countries_treaties, :force => true, :id => false do |t|
t.string :country_id, :null => false
t.string :treaty_id, :null => false
- t.datetime :created_at
- t.datetime :updated_at
end
create_table :liquid, :force => true do |t|
@@ -736,4 +774,9 @@ end
Course.connection.create_table :courses, :force => true do |t|
t.column :name, :string, :null => false
+ t.column :college_id, :integer
+end
+
+College.connection.create_table :colleges, :force => true do |t|
+ t.column :name, :string, :null => false
end
diff --git a/activerecord/test/schema/sqlite_specific_schema.rb b/activerecord/test/schema/sqlite_specific_schema.rb
index ea05b35fe0..e9ddeb32cf 100644
--- a/activerecord/test/schema/sqlite_specific_schema.rb
+++ b/activerecord/test/schema/sqlite_specific_schema.rb
@@ -1,5 +1,5 @@
ActiveRecord::Schema.define do
- # For sqlite 3.1.0+, make a table with a autoincrement column
+ # For sqlite 3.1.0+, make a table with an autoincrement column
if supports_autoincrement?
create_table :table_with_autoincrement, :force => true do |t|
t.column :name, :string
diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb
index a39794fa39..c176316a05 100644
--- a/activerecord/test/support/connection.rb
+++ b/activerecord/test/support/connection.rb
@@ -1,4 +1,5 @@
-require 'logger'
+require 'active_support/logger'
+require_dependency 'models/college'
require_dependency 'models/course'
module ARTest
@@ -11,10 +12,10 @@ module ARTest
end
def self.connect
- puts "Using #{connection_name} with Identity Map #{ActiveRecord::IdentityMap.enabled? ? 'on' : 'off'}"
- ActiveRecord::Base.logger = Logger.new("debug.log")
- ActiveRecord::Base.configurations = connection_config
- ActiveRecord::Base.establish_connection 'arunit'
- Course.establish_connection 'arunit2'
+ puts "Using #{connection_name}"
+ ActiveRecord::Model.logger = ActiveSupport::Logger.new("debug.log")
+ ActiveRecord::Model.configurations = connection_config
+ ActiveRecord::Model.establish_connection 'arunit'
+ ARUnit2Model.establish_connection 'arunit2'
end
end
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG
deleted file mode 100644
index bd496a1263..0000000000
--- a/activeresource/CHANGELOG
+++ /dev/null
@@ -1,337 +0,0 @@
-*Rails 3.2.0 (unreleased)*
-
-* Redirect responses: 303 See Other and 307 Temporary Redirect now behave like
- 301 Moved Permanently and 302 Found. GH #3302.
-
- [Jim Herz]
-
-
-*Rails 3.1.1 (October 7, 2011)*
-
-* No changes.
-
-
-*Rails 3.1.0 (August 30, 2011)*
-
-* The default format has been changed to JSON for all requests. If you want to continue to use XML you will need to set `self.format = :xml` in the class. eg.
-
-class User < ActiveResource::Base
- self.format = :xml
-end
-
-
-*Rails 3.0.7 (April 18, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.6 (April 5, 2011)
-
-* No changes.
-
-
-*Rails 3.0.5 (February 26, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.4 (February 8, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.3 (November 16, 2010)*
-
-* No changes.
-
-
-*Rails 3.0.2 (November 15, 2010)*
-
-* No changes
-
-
-*Rails 3.0.1 (October 15, 2010)*
-
-* No Changes, just a version bump.
-
-
-*Rails 3.0.0 (August 29, 2010)*
-
-* JSON: set Base.include_root_in_json = true to include a root value in the JSON: {"post": {"title": ...}}. Mirrors the Active Record option. [Santiago Pastorino]
-
-* Add support for errors in JSON format. #1956 [Fabien Jakimowicz]
-
-* Recognizes 410 as Resource Gone. #2316 [Jordan Brough, Jatinder Singh]
-
-* More thorough SSL support. #2370 [Roy Nicholson]
-
-* HTTP proxy support. #2133 [Marshall Huss, Sébastien Dabet]
-
-
-*2.3.2 [Final] (March 15, 2009)*
-
-* Nothing new, just included in 2.3.2
-
-
-*2.2.1 [RC2] (November 14th, 2008)*
-
-* Fixed that ActiveResource#post would post an empty string when it shouldn't be posting anything #525 [Paolo Angelini]
-
-
-*2.2.0 [RC1] (October 24th, 2008)*
-
-* Add ActiveResource::Base#to_xml and ActiveResource::Base#to_json. #1011 [Rasik Pandey, Cody Fauser]
-
-* Add ActiveResource::Base.find(:last). [#754 state:resolved] (Adrian Mugnolo)
-
-* Fixed problems with the logger used if the logging string included %'s [#840 state:resolved] (Jamis Buck)
-
-* Fixed Base#exists? to check status code as integer [#299 state:resolved] (Wes Oldenbeuving)
-
-
-*2.1.0 (May 31st, 2008)*
-
-* Fixed response logging to use length instead of the entire thing (seangeo) [#27]
-
-* Fixed that to_param should be used and honored instead of hardcoding the id #11406 [gspiers]
-
-* Improve documentation. [Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert]
-
-* Use HEAD instead of GET in exists? [bscofield]
-
-* Fix small documentation typo. Closes #10670 [Luca Guidi]
-
-* find_or_create_resource_for handles module nesting. #10646 [xavier]
-
-* Allow setting ActiveResource::Base#format before #site. [Rick Olson]
-
-* Support agnostic formats when calling custom methods. Closes #10635 [joerichsen]
-
-* Document custom methods. #10589 [Cheah Chu Yeow]
-
-* Ruby 1.9 compatibility. [Jeremy Kemper]
-
-
-*2.0.2 (December 16th, 2007)*
-
-* Added more specific exceptions for 400, 401, and 403 (all descending from ClientError so existing rescues will work) #10326 [trek]
-
-* Correct empty response handling. #10445 [seangeo]
-
-
-*2.0.1 (December 7th, 2007)*
-
-* Don't cache net/http object so that ActiveResource is more thread-safe. Closes #10142 [kou]
-
-* Update XML documentation examples to include explicit type attributes. Closes #9754 [Josh Susser]
-
-* Added one-off declarations of mock behavior [David Heinemeier Hansson]. Example:
-
- Before:
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.xml", {}, "<person><name>David</name></person>"
- end
-
- Now:
- ActiveResource::HttpMock.respond_to.get "/people/1.xml", {}, "<person><name>David</name></person>"
-
-* Added ActiveResource.format= which defaults to :xml but can also be set to :json [David Heinemeier Hansson]. Example:
-
- class Person < ActiveResource::Base
- self.site = "http://app/"
- self.format = :json
- end
-
- person = Person.find(1) # => GET http://app/people/1.json
- person.name = "David"
- person.save # => PUT http://app/people/1.json {name: "David"}
-
- Person.format = :xml
- person.name = "Mary"
- person.save # => PUT http://app/people/1.json <person><name>Mary</name></person>
-
-* Fix reload error when path prefix is used. #8727 [Ian Warshak]
-
-* Remove ActiveResource::Struct because it hasn't proven very useful. Creating a new ActiveResource::Base subclass is often less code and always clearer. #8612 [Josh Peek]
-
-* Fix query methods on resources. [Cody Fauser]
-
-* pass the prefix_options to the instantiated record when using find without a specific id. Closes #8544 [Eloy Duran]
-
-* Recognize and raise an exception on 405 Method Not Allowed responses. #7692 [Josh Peek]
-
-* Handle string and symbol param keys when splitting params into prefix params and query params.
-
- Comment.find(:all, :params => { :article_id => 5, :page => 2 }) or Comment.find(:all, :params => { 'article_id' => 5, :page => 2 })
-
-* Added find-one with symbol [David Heinemeier Hansson]. Example: Person.find(:one, :from => :leader) # => GET /people/leader.xml
-
-* BACKWARDS INCOMPATIBLE: Changed the finder API to be more extensible with :params and more strict usage of scopes [David Heinemeier Hansson]. Changes:
-
- Person.find(:all, :title => "CEO") ...becomes: Person.find(:all, :params => { :title => "CEO" })
- Person.find(:managers) ...becomes: Person.find(:all, :from => :managers)
- Person.find("/companies/1/manager.xml") ...becomes: Person.find(:one, :from => "/companies/1/manager.xml")
-
-* Add support for setting custom headers per Active Resource model [Rick Olson]
-
- class Project
- headers['X-Token'] = 'foo'
- end
-
- # makes the GET request with the custom X-Token header
- Project.find(:all)
-
-* Added find-by-path options to ActiveResource::Base.find [David Heinemeier Hansson]. Examples:
-
- employees = Person.find(:all, :from => "/companies/1/people.xml") # => GET /companies/1/people.xml
- manager = Person.find("/companies/1/manager.xml") # => GET /companies/1/manager.xml
-
-
-* Added support for using classes from within a single nested module [David Heinemeier Hansson]. Example:
-
- module Highrise
- class Note < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- end
-
- class Comment < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- end
- end
-
- assert_kind_of Highrise::Comment, Note.find(1).comments.first
-
-
-* Added load_attributes_from_response as a way of loading attributes from other responses than just create [David Heinemeier Hansson]
-
- class Highrise::Task < ActiveResource::Base
- def complete
- load_attributes_from_response(post(:complete))
- end
- end
-
- ...will set "done_at" when complete is called.
-
-
-* Added support for calling custom methods #6979 [rwdaigle]
-
- Person.find(:managers) # => GET /people/managers.xml
- Kase.find(1).post(:close) # => POST /kases/1/close.xml
-
-* Remove explicit prefix_options parameter for ActiveResource::Base#initialize. [Rick Olson]
- ActiveResource splits the prefix_options from it automatically.
-
-* Allow ActiveResource::Base.delete with custom prefix. [Rick Olson]
-
-* Add ActiveResource::Base#dup [Rick Olson]
-
-* Fixed constant warning when fetching the same object multiple times [David Heinemeier Hansson]
-
-* Added that saves which get a body response (and not just a 201) will use that response to update themselves [David Heinemeier Hansson]
-
-* Disregard namespaces from the default element name, so Highrise::Person will just try to fetch from "/people", not "/highrise/people" [David Heinemeier Hansson]
-
-* Allow array and hash query parameters. #7756 [Greg Spurrier]
-
-* Loading a resource preserves its prefix_options. #7353 [Ryan Daigle]
-
-* Carry over the convenience of #create from ActiveRecord. Closes #7340. [Ryan Daigle]
-
-* Increase ActiveResource::Base test coverage. Closes #7173, #7174 [Rich Collins]
-
-* Interpret 422 Unprocessable Entity as ResourceInvalid. #7097 [dkubb]
-
-* Mega documentation patches. #7025, #7069 [rwdaigle]
-
-* Base.exists?(id, options) and Base#exists? check whether the resource is found. #6970 [rwdaigle]
-
-* Query string support. [untext, Jeremy Kemper]
- # GET /forums/1/topics.xml?sort=created_at
- Topic.find(:all, :forum_id => 1, :sort => 'created_at')
-
-* Base#==, eql?, and hash methods. == returns true if its argument is identical to self or if it's an instance of the same class, is not new?, and has the same id. eql? is an alias for ==. hash delegates to id. [Jeremy Kemper]
-
-* Allow subclassed resources to share the site info [Rick Olson, Jeremy Kemper]
-d
- class BeastResource < ActiveResource::Base
- self.site = 'http://beast.caboo.se'
- end
-
- class Forum < BeastResource
- # taken from BeastResource
- # self.site = 'http://beast.caboo.se'
- end
-
- class Topic < BeastResource
- self.site += '/forums/:forum_id'
- end
-
-* Fix issues with ActiveResource collection handling. Closes #6291. [bmilekic]
-
-* Use attr_accessor_with_default to dry up attribute initialization. References #6538. [Stuart Halloway]
-
-* Add basic logging support for logging outgoing requests. [Jamis Buck]
-
-* Add Base.delete for deleting resources without having to instantiate them first. [Jamis Buck]
-
-* Make #save behavior mimic AR::Base#save (true on success, false on failure). [Jamis Buck]
-
-* Add Basic HTTP Authentication to ActiveResource (closes #6305). [jonathan]
-
-* Extracted #id_from_response as an entry point for customizing how a created resource gets its own ID.
- By default, it extracts from the Location response header.
-
-* Optimistic locking: raise ActiveResource::ResourceConflict on 409 Conflict response. [Jeremy Kemper]
-
- # Example controller action
- def update
- @person.save!
- rescue ActiveRecord::StaleObjectError
- render :xml => @person.reload.to_xml, :status => '409 Conflict'
- end
-
-* Basic validation support [Rick Olson]
-
- Parses the xml response of ActiveRecord::Errors#to_xml with a similar interface to ActiveRecord::Errors.
-
- render :xml => @person.errors.to_xml, :status => '400 Validation Error'
-
-* Deep hashes are converted into collections of resources. [Jeremy Kemper]
- Person.new :name => 'Bob',
- :address => { :id => 1, :city => 'Portland' },
- :contacts => [{ :id => 1 }, { :id => 2 }]
- Looks for Address and Contact resources and creates them if unavailable.
- So clients can fetch a complex resource in a single request if you e.g.
- render :xml => @person.to_xml(:include => [:address, :contacts])
- in your controller action.
-
-* Major updates [Rick Olson]
-
- * Add full support for find/create/update/destroy
- * Add support for specifying prefixes.
- * Allow overriding of element_name, collection_name, and primary key
- * Provide simpler HTTP mock interface for testing
-
- # rails routing code
- map.resources :posts do |post|
- post.resources :comments
- end
-
- # ActiveResources
- class Post < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000/"
- end
-
- class Comment < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000/posts/:post_id/"
- end
-
- @post = Post.find 5
- @comments = Comment.find :all, :post_id => @post.id
-
- @comment = Comment.new({:body => 'hello world'}, {:post_id => @post.id})
- @comment.save
-
-* Base.site= accepts URIs. 200...400 are valid response codes. PUT and POST request bodies default to ''. [Jeremy Kemper]
-
-* Initial checkin: object-oriented client for restful HTTP resources which follow the Rails convention. [David Heinemeier Hansson]
diff --git a/activeresource/MIT-LICENSE b/activeresource/MIT-LICENSE
deleted file mode 100644
index 216b6e5ba0..0000000000
--- a/activeresource/MIT-LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2006-2011 David Heinemeier Hansson
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/activeresource/README.rdoc b/activeresource/README.rdoc
deleted file mode 100644
index c86289c5fe..0000000000
--- a/activeresource/README.rdoc
+++ /dev/null
@@ -1,187 +0,0 @@
-= Active Resource
-
-Active Resource (ARes) connects business objects and Representational State Transfer (REST)
-web services. It implements object-relational mapping for REST web services to provide transparent
-proxying capabilities between a client (ActiveResource) and a RESTful service (which is provided by Simply RESTful routing
-in ActionController::Resources).
-
-== Philosophy
-
-Active Resource attempts to provide a coherent wrapper object-relational mapping for REST
-web services. It follows the same philosophy as Active Record, in that one of its prime aims
-is to reduce the amount of code needed to map to these resources. This is made possible
-by relying on a number of code- and protocol-based conventions that make it easy for Active Resource
-to infer complex relations and structures. These conventions are outlined in detail in the documentation
-for ActiveResource::Base.
-
-== Overview
-
-Model classes are mapped to remote REST resources by Active Resource much the same way Active Record maps model classes to database
-tables. When a request is made to a remote resource, a REST XML request is generated, transmitted, and the result
-received and serialized into a usable Ruby object.
-
-== Download and installation
-
-The latest version of Active Support can be installed with RubyGems:
-
- % [sudo] gem install activeresource
-
-Source code can be downloaded as part of the Rails project on GitHub
-
-* https://github.com/rails/rails/tree/master/activeresource
-
-=== Configuration and Usage
-
-Putting Active Resource to use is very similar to Active Record. It's as simple as creating a model class
-that inherits from ActiveResource::Base and providing a <tt>site</tt> class variable to it:
-
- class Person < ActiveResource::Base
- self.site = "http://api.people.com:3000"
- end
-
-Now the Person class is REST enabled and can invoke REST services very similarly to how Active Record invokes
-life cycle methods that operate against a persistent store.
-
- # Find a person with id = 1
- ryan = Person.find(1)
- Person.exists?(1) # => true
-
-As you can see, the methods are quite similar to Active Record's methods for dealing with database
-records. But rather than dealing directly with a database record, you're dealing with HTTP resources (which may or may not be database records).
-
-==== Protocol
-
-Active Resource is built on a standard XML format for requesting and submitting resources over HTTP. It mirrors the RESTful routing
-built into Action Controller but will also work with any other REST service that properly implements the protocol.
-REST uses HTTP, but unlike "typical" web applications, it makes use of all the verbs available in the HTTP specification:
-
-* GET requests are used for finding and retrieving resources.
-* POST requests are used to create new resources.
-* PUT requests are used to update existing resources.
-* DELETE requests are used to delete resources.
-
-For more information on how this protocol works with Active Resource, see the ActiveResource::Base documentation;
-for more general information on REST web services, see the article here[http://en.wikipedia.org/wiki/Representational_State_Transfer].
-
-==== Find
-
-Find requests use the GET method and expect the XML form of whatever resource/resources is/are being requested. So,
-for a request for a single element, the XML of that item is expected in response:
-
- # Expects a response of
- #
- # <person><id type="integer">1</id><attribute1>value1</attribute1><attribute2>..</attribute2></person>
- #
- # for GET http://api.people.com:3000/people/1.xml
- #
- ryan = Person.find(1)
-
-The XML document that is received is used to build a new object of type Person, with each
-XML element becoming an attribute on the object.
-
- ryan.is_a? Person # => true
- ryan.attribute1 # => 'value1'
-
-Any complex element (one that contains other elements) becomes its own object:
-
- # With this response:
- #
- # <person><id>1</id><attribute1>value1</attribute1><complex><attribute2>value2</attribute2></complex></person>
- #
- # for GET http://api.people.com:3000/people/1.xml
- #
- ryan = Person.find(1)
- ryan.complex # => <Person::Complex::xxxxx>
- ryan.complex.attribute2 # => 'value2'
-
-Collections can also be requested in a similar fashion
-
- # Expects a response of
- #
- # <people type="array">
- # <person><id type="integer">1</id><first>Ryan</first></person>
- # <person><id type="integer">2</id><first>Jim</first></person>
- # </people>
- #
- # for GET http://api.people.com:3000/people.xml
- #
- people = Person.all
- people.first # => <Person::xxx 'first' => 'Ryan' ...>
- people.last # => <Person::xxx 'first' => 'Jim' ...>
-
-==== Create
-
-Creating a new resource submits the XML form of the resource as the body of the request and expects
-a 'Location' header in the response with the RESTful URL location of the newly created resource. The
-id of the newly created resource is parsed out of the Location response header and automatically set
-as the id of the ARes object.
-
- # <person><first>Ryan</first></person>
- #
- # is submitted as the body on
- #
- # POST http://api.people.com:3000/people.xml
- #
- # when save is called on a new Person object. An empty response is
- # is expected with a 'Location' header value:
- #
- # Response (201): Location: http://api.people.com:3000/people/2
- #
- ryan = Person.new(:first => 'Ryan')
- ryan.new? # => true
- ryan.save # => true
- ryan.new? # => false
- ryan.id # => 2
-
-==== Update
-
-'save' is also used to update an existing resource and follows the same protocol as creating a resource
-with the exception that no response headers are needed -- just an empty response when the update on the
-server side was successful.
-
- # <person><first>Ryan</first></person>
- #
- # is submitted as the body on
- #
- # PUT http://api.people.com:3000/people/1.xml
- #
- # when save is called on an existing Person object. An empty response is
- # is expected with code (204)
- #
- ryan = Person.find(1)
- ryan.first # => 'Ryan'
- ryan.first = 'Rizzle'
- ryan.save # => true
-
-==== Delete
-
-Destruction of a resource can be invoked as a class and instance method of the resource.
-
- # A request is made to
- #
- # DELETE http://api.people.com:3000/people/1.xml
- #
- # for both of these forms. An empty response with
- # is expected with response code (200)
- #
- ryan = Person.find(1)
- ryan.destroy # => true
- ryan.exists? # => false
- Person.delete(2) # => true
- Person.exists?(2) # => false
-
-== License
-
-Active Support is released under the MIT license.
-
-== Support
-
-API documentation is at
-
-* http://api.rubyonrails.org
-
-Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
-
-* https://github.com/rails/rails/issues
-
-You can find more usage information in the ActiveResource::Base documentation.
diff --git a/activeresource/Rakefile b/activeresource/Rakefile
deleted file mode 100755
index b1c18ff189..0000000000
--- a/activeresource/Rakefile
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env rake
-require 'rake/testtask'
-require 'rake/packagetask'
-require 'rubygems/package_task'
-
-desc "Default Task"
-task :default => [ :test ]
-
-# Run the unit tests
-
-Rake::TestTask.new { |t|
- t.libs << "test"
- t.pattern = 'test/**/*_test.rb'
- t.warning = true
-}
-
-namespace :test do
- task :isolated do
- ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
- activesupport_path = "#{File.dirname(__FILE__)}/../activesupport/lib"
- Dir.glob("test/**/*_test.rb").all? do |file|
- sh(ruby, '-w', "-Ilib:test:#{activesupport_path}", file)
- end or raise "Failures"
- end
-end
-
-spec = eval(File.read('activeresource.gemspec'))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
-
-task :lines do
- lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
-
- for file_name in FileList["lib/active_resource/**/*.rb"]
- next if file_name =~ /vendor/
- f = File.open(file_name)
-
- while line = f.gets
- lines += 1
- next if line =~ /^\s*$/
- next if line =~ /^\s*#/
- codelines += 1
- end
- puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
-
- total_lines += lines
- total_codelines += codelines
-
- lines, codelines = 0, 0
- end
-
- puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
-end
-
-
-# Publishing ------------------------------------------------------
-
-desc "Release to gemcutter"
-task :release => :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
diff --git a/activeresource/activeresource.gemspec b/activeresource/activeresource.gemspec
deleted file mode 100644
index a8772ecf8c..0000000000
--- a/activeresource/activeresource.gemspec
+++ /dev/null
@@ -1,24 +0,0 @@
-version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
-
-Gem::Specification.new do |s|
- s.platform = Gem::Platform::RUBY
- s.name = 'activeresource'
- s.version = version
- s.summary = 'REST modeling framework (part of Rails).'
- s.description = 'REST on Rails. Wrap your RESTful web app with Ruby classes and work with them like Active Record models.'
-
- s.required_ruby_version = '>= 1.8.7'
-
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
-
- s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'examples/**/*', 'lib/**/*']
- s.require_path = 'lib'
-
- s.extra_rdoc_files = %w( README.rdoc )
- s.rdoc_options.concat ['--main', 'README.rdoc']
-
- s.add_dependency('activesupport', version)
- s.add_dependency('activemodel', version)
-end
diff --git a/activeresource/examples/performance.rb b/activeresource/examples/performance.rb
deleted file mode 100644
index e4df7a38a4..0000000000
--- a/activeresource/examples/performance.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require 'rubygems'
-require 'active_resource'
-require 'benchmark'
-
-TIMES = (ENV['N'] || 10_000).to_i
-
-# deep nested resource
-attrs = {
- :id => 1,
- :name => 'Luis',
- :age => 21,
- :friends => [
- {
- :name => 'JK',
- :age => 24,
- :colors => ['red', 'green', 'blue'],
- :brothers => [
- {
- :name => 'Mateo',
- :age => 35,
- :children => [{ :name => 'Edith', :age => 5 }, { :name => 'Martha', :age => 4 }]
- },
- {
- :name => 'Felipe',
- :age => 33,
- :children => [{ :name => 'Bryan', :age => 1 }, { :name => 'Luke', :age => 0 }]
- }
- ]
- },
- {
- :name => 'Eduardo',
- :age => 20,
- :colors => [],
- :brothers => [
- {
- :name => 'Sebas',
- :age => 23,
- :children => [{ :name => 'Andres', :age => 0 }, { :name => 'Jorge', :age => 2 }]
- },
- {
- :name => 'Elsa',
- :age => 19,
- :children => [{ :name => 'Natacha', :age => 1 }]
- },
- {
- :name => 'Milena',
- :age => 16,
- :children => []
- }
- ]
- }
- ]
-}
-
-class Customer < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
-end
-
-module Nested
- class Customer < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- end
-end
-
-Benchmark.bm(40) do |x|
- x.report('Model.new (instantiation)') { TIMES.times { Customer.new } }
- x.report('Nested::Model.new (instantiation)') { TIMES.times { Nested::Customer.new } }
- x.report('Model.new (setting attributes)') { TIMES.times { Customer.new attrs } }
- x.report('Nested::Model.new (setting attributes)') { TIMES.times { Nested::Customer.new attrs } }
-end
diff --git a/activeresource/lib/active_resource.rb b/activeresource/lib/active_resource.rb
deleted file mode 100644
index 0794a1a800..0000000000
--- a/activeresource/lib/active_resource.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-#--
-# Copyright (c) 2006-2011 David Heinemeier Hansson
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#++
-
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-
-require 'active_support'
-require 'active_model'
-require 'active_resource/version'
-
-module ActiveResource
- extend ActiveSupport::Autoload
-
- autoload :Base
- autoload :Connection
- autoload :CustomMethods
- autoload :Formats
- autoload :HttpMock
- autoload :Observing
- autoload :Schema
- autoload :Validations
-end
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
deleted file mode 100644
index 10cc727bd9..0000000000
--- a/activeresource/lib/active_resource/base.rb
+++ /dev/null
@@ -1,1483 +0,0 @@
-require 'active_support'
-require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/hash/indifferent_access'
-require 'active_support/core_ext/kernel/reporting'
-require 'active_support/core_ext/module/delegation'
-require 'active_support/core_ext/module/aliasing'
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/object/to_query'
-require 'active_support/core_ext/object/duplicable'
-require 'set'
-require 'uri'
-
-require 'active_support/core_ext/uri'
-require 'active_resource/exceptions'
-require 'active_resource/connection'
-require 'active_resource/formats'
-require 'active_resource/schema'
-require 'active_resource/log_subscriber'
-
-module ActiveResource
- # ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
- #
- # For an outline of what Active Resource is capable of, see its {README}[link:files/activeresource/README_rdoc.html].
- #
- # == Automated mapping
- #
- # Active Resource objects represent your RESTful resources as manipulatable Ruby objects. To map resources
- # to Ruby objects, Active Resource only needs a class name that corresponds to the resource name (e.g., the class
- # Person maps to the resources people, very similarly to Active Record) and a +site+ value, which holds the
- # URI of the resources.
- #
- # class Person < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
- # end
- #
- # Now the Person class is mapped to RESTful resources located at <tt>http://api.people.com:3000/people/</tt>, and
- # you can now use Active Resource's life cycle methods to manipulate resources. In the case where you already have
- # an existing model with the same name as the desired RESTful resource you can set the +element_name+ value.
- #
- # class PersonResource < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
- # self.element_name = "person"
- # end
- #
- # If your Active Resource object is required to use an HTTP proxy you can set the +proxy+ value which holds a URI.
- #
- # class PersonResource < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
- # self.proxy = "http://user:password@proxy.people.com:8080"
- # end
- #
- #
- # == Life cycle methods
- #
- # Active Resource exposes methods for creating, finding, updating, and deleting resources
- # from REST web services.
- #
- # ryan = Person.new(:first => 'Ryan', :last => 'Daigle')
- # ryan.save # => true
- # ryan.id # => 2
- # Person.exists?(ryan.id) # => true
- # ryan.exists? # => true
- #
- # ryan = Person.find(1)
- # # Resource holding our newly created Person object
- #
- # ryan.first = 'Rizzle'
- # ryan.save # => true
- #
- # ryan.destroy # => true
- #
- # As you can see, these are very similar to Active Record's life cycle methods for database records.
- # You can read more about each of these methods in their respective documentation.
- #
- # === Custom REST methods
- #
- # Since simple CRUD/life cycle methods can't accomplish every task, Active Resource also supports
- # defining your own custom REST methods. To invoke them, Active Resource provides the <tt>get</tt>,
- # <tt>post</tt>, <tt>put</tt> and <tt>\delete</tt> methods where you can specify a custom REST method
- # name to invoke.
- #
- # # POST to the custom 'register' REST method, i.e. POST /people/new/register.json.
- # Person.new(:name => 'Ryan').post(:register)
- # # => { :id => 1, :name => 'Ryan', :position => 'Clerk' }
- #
- # # PUT an update by invoking the 'promote' REST method, i.e. PUT /people/1/promote.json?position=Manager.
- # Person.find(1).put(:promote, :position => 'Manager')
- # # => { :id => 1, :name => 'Ryan', :position => 'Manager' }
- #
- # # GET all the positions available, i.e. GET /people/positions.json.
- # Person.get(:positions)
- # # => [{:name => 'Manager'}, {:name => 'Clerk'}]
- #
- # # DELETE to 'fire' a person, i.e. DELETE /people/1/fire.json.
- # Person.find(1).delete(:fire)
- #
- # For more information on using custom REST methods, see the
- # ActiveResource::CustomMethods documentation.
- #
- # == Validations
- #
- # You can validate resources client side by overriding validation methods in the base class.
- #
- # class Person < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
- # protected
- # def validate
- # errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
- # end
- # end
- #
- # See the ActiveResource::Validations documentation for more information.
- #
- # == Authentication
- #
- # Many REST APIs will require authentication, usually in the form of basic
- # HTTP authentication. Authentication can be specified by:
- #
- # === HTTP Basic Authentication
- # * putting the credentials in the URL for the +site+ variable.
- #
- # class Person < ActiveResource::Base
- # self.site = "http://ryan:password@api.people.com:3000/"
- # end
- #
- # * defining +user+ and/or +password+ variables
- #
- # class Person < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
- # self.user = "ryan"
- # self.password = "password"
- # end
- #
- # For obvious security reasons, it is probably best if such services are available
- # over HTTPS.
- #
- # Note: Some values cannot be provided in the URL passed to site. e.g. email addresses
- # as usernames. In those situations you should use the separate user and password option.
- #
- # === Certificate Authentication
- #
- # * End point uses an X509 certificate for authentication. <tt>See ssl_options=</tt> for all options.
- #
- # class Person < ActiveResource::Base
- # self.site = "https://secure.api.people.com/"
- # self.ssl_options = {:cert => OpenSSL::X509::Certificate.new(File.open(pem_file))
- # :key => OpenSSL::PKey::RSA.new(File.open(pem_file)),
- # :ca_path => "/path/to/OpenSSL/formatted/CA_Certs",
- # :verify_mode => OpenSSL::SSL::VERIFY_PEER}
- # end
- #
- #
- # == Errors & Validation
- #
- # Error handling and validation is handled in much the same manner as you're used to seeing in
- # Active Record. Both the response code in the HTTP response and the body of the response are used to
- # indicate that an error occurred.
- #
- # === Resource errors
- #
- # When a GET is requested for a resource that does not exist, the HTTP <tt>404</tt> (Resource Not Found)
- # response code will be returned from the server which will raise an ActiveResource::ResourceNotFound
- # exception.
- #
- # # GET http://api.people.com:3000/people/999.json
- # ryan = Person.find(999) # 404, raises ActiveResource::ResourceNotFound
- #
- #
- # <tt>404</tt> is just one of the HTTP error response codes that Active Resource will handle with its own exception. The
- # following HTTP response codes will also result in these exceptions:
- #
- # * 200..399 - Valid response. No exceptions, other than these redirects:
- # * 301, 302, 303, 307 - ActiveResource::Redirection
- # * 400 - ActiveResource::BadRequest
- # * 401 - ActiveResource::UnauthorizedAccess
- # * 403 - ActiveResource::ForbiddenAccess
- # * 404 - ActiveResource::ResourceNotFound
- # * 405 - ActiveResource::MethodNotAllowed
- # * 409 - ActiveResource::ResourceConflict
- # * 410 - ActiveResource::ResourceGone
- # * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
- # * 401..499 - ActiveResource::ClientError
- # * 500..599 - ActiveResource::ServerError
- # * Other - ActiveResource::ConnectionError
- #
- # These custom exceptions allow you to deal with resource errors more naturally and with more precision
- # rather than returning a general HTTP error. For example:
- #
- # begin
- # ryan = Person.find(my_id)
- # rescue ActiveResource::ResourceNotFound
- # redirect_to :action => 'not_found'
- # rescue ActiveResource::ResourceConflict, ActiveResource::ResourceInvalid
- # redirect_to :action => 'new'
- # end
- #
- # When a GET is requested for a nested resource and you don't provide the prefix_param
- # an ActiveResource::MissingPrefixParam will be raised.
- #
- # class Comment < ActiveResource::Base
- # self.site = "http://someip.com/posts/:post_id/"
- # end
- #
- # Comment.find(1)
- # # => ActiveResource::MissingPrefixParam: post_id prefix_option is missing
- #
- # === Validation errors
- #
- # Active Resource supports validations on resources and will return errors if any of these validations fail
- # (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
- # a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will
- # then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
- #
- # ryan = Person.find(1)
- # ryan.first # => ''
- # ryan.save # => false
- #
- # # When
- # # PUT http://api.people.com:3000/people/1.json
- # # or
- # # PUT http://api.people.com:3000/people/1.json
- # # is requested with invalid values, the response is:
- # #
- # # Response (422):
- # # <errors><error>First cannot be empty</error></errors>
- # # or
- # # {"errors":["First cannot be empty"]}
- # #
- #
- # ryan.errors.invalid?(:first) # => true
- # ryan.errors.full_messages # => ['First cannot be empty']
- #
- # Learn more about Active Resource's validation features in the ActiveResource::Validations documentation.
- #
- # === Timeouts
- #
- # Active Resource relies on HTTP to access RESTful APIs and as such is inherently susceptible to slow or
- # unresponsive servers. In such cases, your Active Resource method calls could \timeout. You can control the
- # amount of time before Active Resource times out with the +timeout+ variable.
- #
- # class Person < ActiveResource::Base
- # self.site = "http://api.people.com:3000/"
- # self.timeout = 5
- # end
- #
- # This sets the +timeout+ to 5 seconds. You can adjust the +timeout+ to a value suitable for the RESTful API
- # you are accessing. It is recommended to set this to a reasonably low value to allow your Active Resource
- # clients (especially if you are using Active Resource in a Rails application) to fail-fast (see
- # http://en.wikipedia.org/wiki/Fail-fast) rather than cause cascading failures that could incapacitate your
- # server.
- #
- # When a \timeout occurs, an ActiveResource::TimeoutError is raised. You should rescue from
- # ActiveResource::TimeoutError in your Active Resource method calls.
- #
- # Internally, Active Resource relies on Ruby's Net::HTTP library to make HTTP requests. Setting +timeout+
- # sets the <tt>read_timeout</tt> of the internal Net::HTTP instance to the same value. The default
- # <tt>read_timeout</tt> is 60 seconds on most Ruby implementations.
- class Base
- ##
- # :singleton-method:
- # The logger for diagnosing and tracing Active Resource calls.
- cattr_accessor :logger
-
- class_attribute :_format
-
- class << self
- # Creates a schema for this resource - setting the attributes that are
- # known prior to fetching an instance from the remote system.
- #
- # The schema helps define the set of <tt>known_attributes</tt> of the
- # current resource.
- #
- # There is no need to specify a schema for your Active Resource. If
- # you do not, the <tt>known_attributes</tt> will be guessed from the
- # instance attributes returned when an instance is fetched from the
- # remote system.
- #
- # example:
- # class Person < ActiveResource::Base
- # schema do
- # # define each attribute separately
- # attribute 'name', :string
- #
- # # or use the convenience methods and pass >=1 attribute names
- # string 'eye_color', 'hair_color'
- # integer 'age'
- # float 'height', 'weight'
- #
- # # unsupported types should be left as strings
- # # overload the accessor methods if you need to convert them
- # attribute 'created_at', 'string'
- # end
- # end
- #
- # p = Person.new
- # p.respond_to? :name # => true
- # p.respond_to? :age # => true
- # p.name # => nil
- # p.age # => nil
- #
- # j = Person.find_by_name('John') # <person><name>John</name><age>34</age><num_children>3</num_children></person>
- # j.respond_to? :name # => true
- # j.respond_to? :age # => true
- # j.name # => 'John'
- # j.age # => '34' # note this is a string!
- # j.num_children # => '3' # note this is a string!
- #
- # p.num_children # => NoMethodError
- #
- # Attribute-types must be one of:
- # string, integer, float
- #
- # Note: at present the attribute-type doesn't do anything, but stay
- # tuned...
- # Shortly it will also *cast* the value of the returned attribute.
- # ie:
- # j.age # => 34 # cast to an integer
- # j.weight # => '65' # still a string!
- #
- def schema(&block)
- if block_given?
- schema_definition = Schema.new
- schema_definition.instance_eval(&block)
-
- # skip out if we didn't define anything
- return unless schema_definition.attrs.present?
-
- @schema ||= {}.with_indifferent_access
- @known_attributes ||= []
-
- schema_definition.attrs.each do |k,v|
- @schema[k] = v
- @known_attributes << k
- end
-
- schema
- else
- @schema ||= nil
- end
- end
-
- # Alternative, direct way to specify a <tt>schema</tt> for this
- # Resource. <tt>schema</tt> is more flexible, but this is quick
- # for a very simple schema.
- #
- # Pass the schema as a hash with the keys being the attribute-names
- # and the value being one of the accepted attribute types (as defined
- # in <tt>schema</tt>)
- #
- # example:
- #
- # class Person < ActiveResource::Base
- # schema = {'name' => :string, 'age' => :integer }
- # end
- #
- # The keys/values can be strings or symbols. They will be converted to
- # strings.
- #
- def schema=(the_schema)
- unless the_schema.present?
- # purposefully nulling out the schema
- @schema = nil
- @known_attributes = []
- return
- end
-
- raise ArgumentError, "Expected a hash" unless the_schema.kind_of? Hash
-
- schema do
- the_schema.each {|k,v| attribute(k,v) }
- end
- end
-
- # Returns the list of known attributes for this resource, gathered
- # from the provided <tt>schema</tt>
- # Attributes that are known will cause your resource to return 'true'
- # when <tt>respond_to?</tt> is called on them. A known attribute will
- # return nil if not set (rather than <t>MethodNotFound</tt>); thus
- # known attributes can be used with <tt>validates_presence_of</tt>
- # without a getter-method.
- def known_attributes
- @known_attributes ||= []
- end
-
- # Gets the URI of the REST resources to map for this class. The site variable is required for
- # Active Resource's mapping to work.
- def site
- # Not using superclass_delegating_reader because don't want subclasses to modify superclass instance
- #
- # With superclass_delegating_reader
- #
- # Parent.site = 'http://anonymous@test.com'
- # Subclass.site # => 'http://anonymous@test.com'
- # Subclass.site.user = 'david'
- # Parent.site # => 'http://david@test.com'
- #
- # Without superclass_delegating_reader (expected behavior)
- #
- # Parent.site = 'http://anonymous@test.com'
- # Subclass.site # => 'http://anonymous@test.com'
- # Subclass.site.user = 'david' # => TypeError: can't modify frozen object
- #
- if defined?(@site)
- @site
- elsif superclass != Object && superclass.site
- superclass.site.dup.freeze
- end
- end
-
- # Sets the URI of the REST resources to map for this class to the value in the +site+ argument.
- # The site variable is required for Active Resource's mapping to work.
- def site=(site)
- @connection = nil
- if site.nil?
- @site = nil
- else
- @site = create_site_uri_from(site)
- @user = URI.parser.unescape(@site.user) if @site.user
- @password = URI.parser.unescape(@site.password) if @site.password
- end
- end
-
- # Gets the \proxy variable if a proxy is required
- def proxy
- # Not using superclass_delegating_reader. See +site+ for explanation
- if defined?(@proxy)
- @proxy
- elsif superclass != Object && superclass.proxy
- superclass.proxy.dup.freeze
- end
- end
-
- # Sets the URI of the http proxy to the value in the +proxy+ argument.
- def proxy=(proxy)
- @connection = nil
- @proxy = proxy.nil? ? nil : create_proxy_uri_from(proxy)
- end
-
- # Gets the \user for REST HTTP authentication.
- def user
- # Not using superclass_delegating_reader. See +site+ for explanation
- if defined?(@user)
- @user
- elsif superclass != Object && superclass.user
- superclass.user.dup.freeze
- end
- end
-
- # Sets the \user for REST HTTP authentication.
- def user=(user)
- @connection = nil
- @user = user
- end
-
- # Gets the \password for REST HTTP authentication.
- def password
- # Not using superclass_delegating_reader. See +site+ for explanation
- if defined?(@password)
- @password
- elsif superclass != Object && superclass.password
- superclass.password.dup.freeze
- end
- end
-
- # Sets the \password for REST HTTP authentication.
- def password=(password)
- @connection = nil
- @password = password
- end
-
- def auth_type
- if defined?(@auth_type)
- @auth_type
- end
- end
-
- def auth_type=(auth_type)
- @connection = nil
- @auth_type = auth_type
- end
-
- # Sets the format that attributes are sent and received in from a mime type reference:
- #
- # Person.format = :json
- # Person.find(1) # => GET /people/1.json
- #
- # Person.format = ActiveResource::Formats::XmlFormat
- # Person.find(1) # => GET /people/1.xml
- #
- # Default format is <tt>:json</tt>.
- def format=(mime_type_reference_or_format)
- format = mime_type_reference_or_format.is_a?(Symbol) ?
- ActiveResource::Formats[mime_type_reference_or_format] : mime_type_reference_or_format
-
- self._format = format
- connection.format = format if site
- end
-
- # Returns the current format, default is ActiveResource::Formats::JsonFormat.
- def format
- self._format || ActiveResource::Formats::JsonFormat
- end
-
- # Sets the number of seconds after which requests to the REST API should time out.
- def timeout=(timeout)
- @connection = nil
- @timeout = timeout
- end
-
- # Gets the number of seconds after which requests to the REST API should time out.
- def timeout
- if defined?(@timeout)
- @timeout
- elsif superclass != Object && superclass.timeout
- superclass.timeout
- end
- end
-
- # Options that will get applied to an SSL connection.
- #
- # * <tt>:key</tt> - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
- # * <tt>:cert</tt> - An OpenSSL::X509::Certificate object as client certificate
- # * <tt>:ca_file</tt> - Path to a CA certification file in PEM format. The file can contain several CA certificates.
- # * <tt>:ca_path</tt> - Path of a CA certification directory containing certifications in PEM format.
- # * <tt>:verify_mode</tt> - Flags for server the certification verification at beginning of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable)
- # * <tt>:verify_callback</tt> - The verify callback for the server certification verification.
- # * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification.
- # * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate.
- # * <tt>:ssl_timeout</tt> -The SSL timeout in seconds.
- def ssl_options=(opts={})
- @connection = nil
- @ssl_options = opts
- end
-
- # Returns the SSL options hash.
- def ssl_options
- if defined?(@ssl_options)
- @ssl_options
- elsif superclass != Object && superclass.ssl_options
- superclass.ssl_options
- end
- end
-
- # An instance of ActiveResource::Connection that is the base \connection to the remote service.
- # The +refresh+ parameter toggles whether or not the \connection is refreshed at every request
- # or not (defaults to <tt>false</tt>).
- def connection(refresh = false)
- if defined?(@connection) || superclass == Object
- @connection = Connection.new(site, format) if refresh || @connection.nil?
- @connection.proxy = proxy if proxy
- @connection.user = user if user
- @connection.password = password if password
- @connection.auth_type = auth_type if auth_type
- @connection.timeout = timeout if timeout
- @connection.ssl_options = ssl_options if ssl_options
- @connection
- else
- superclass.connection
- end
- end
-
- def headers
- @headers ||= {}
- end
-
- attr_writer :element_name
-
- def element_name
- @element_name ||= model_name.element
- end
-
- attr_writer :collection_name
-
- def collection_name
- @collection_name ||= ActiveSupport::Inflector.pluralize(element_name)
- end
-
- attr_writer :primary_key
-
- def primary_key
- @primary_key ||= 'id'
- end
-
- # Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>)
- # This method is regenerated at runtime based on what the \prefix is set to.
- def prefix(options={})
- default = site.path
- default << '/' unless default[-1..-1] == '/'
- # generate the actual method based on the current site path
- self.prefix = default
- prefix(options)
- end
-
- # An attribute reader for the source string for the resource path \prefix. This
- # method is regenerated at runtime based on what the \prefix is set to.
- def prefix_source
- prefix # generate #prefix and #prefix_source methods first
- prefix_source
- end
-
- # Sets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>).
- # Default value is <tt>site.path</tt>.
- def prefix=(value = '/')
- # Replace :placeholders with '#{embedded options[:lookups]}'
- prefix_call = value.gsub(/:\w+/) { |key| "\#{URI.parser.escape options[#{key}].to_s}" }
-
- # Clear prefix parameters in case they have been cached
- @prefix_parameters = nil
-
- silence_warnings do
- # Redefine the new methods.
- instance_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def prefix_source() "#{value}" end
- def prefix(options={}) "#{prefix_call}" end
- RUBY_EVAL
- end
- rescue Exception => e
- logger.error "Couldn't set prefix: #{e}\n #{code}" if logger
- raise
- end
-
- alias_method :set_prefix, :prefix= #:nodoc:
-
- alias_method :set_element_name, :element_name= #:nodoc:
- alias_method :set_collection_name, :collection_name= #:nodoc:
-
- # Gets the element path for the given ID in +id+. If the +query_options+ parameter is omitted, Rails
- # will split from the \prefix options.
- #
- # ==== Options
- # +prefix_options+ - A \hash to add a \prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
- # would yield a URL like <tt>/accounts/19/purchases.json</tt>).
- # +query_options+ - A \hash to add items to the query string for the request.
- #
- # ==== Examples
- # Post.element_path(1)
- # # => /posts/1.json
- #
- # class Comment < ActiveResource::Base
- # self.site = "http://37s.sunrise.i/posts/:post_id/"
- # end
- #
- # Comment.element_path(1, :post_id => 5)
- # # => /posts/5/comments/1.json
- #
- # Comment.element_path(1, :post_id => 5, :active => 1)
- # # => /posts/5/comments/1.json?active=1
- #
- # Comment.element_path(1, {:post_id => 5}, {:active => 1})
- # # => /posts/5/comments/1.json?active=1
- #
- def element_path(id, prefix_options = {}, query_options = nil)
- check_prefix_options(prefix_options)
-
- prefix_options, query_options = split_options(prefix_options) if query_options.nil?
- "#{prefix(prefix_options)}#{collection_name}/#{URI.parser.escape id.to_s}.#{format.extension}#{query_string(query_options)}"
- end
-
- # Gets the new element path for REST resources.
- #
- # ==== Options
- # * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
- # would yield a URL like <tt>/accounts/19/purchases/new.json</tt>).
- #
- # ==== Examples
- # Post.new_element_path
- # # => /posts/new.json
- #
- # class Comment < ActiveResource::Base
- # self.site = "http://37s.sunrise.i/posts/:post_id/"
- # end
- #
- # Comment.collection_path(:post_id => 5)
- # # => /posts/5/comments/new.json
- def new_element_path(prefix_options = {})
- "#{prefix(prefix_options)}#{collection_name}/new.#{format.extension}"
- end
-
- # Gets the collection path for the REST resources. If the +query_options+ parameter is omitted, Rails
- # will split from the +prefix_options+.
- #
- # ==== Options
- # * +prefix_options+ - A hash to add a prefix to the request for nested URLs (e.g., <tt>:account_id => 19</tt>
- # would yield a URL like <tt>/accounts/19/purchases.json</tt>).
- # * +query_options+ - A hash to add items to the query string for the request.
- #
- # ==== Examples
- # Post.collection_path
- # # => /posts.json
- #
- # Comment.collection_path(:post_id => 5)
- # # => /posts/5/comments.json
- #
- # Comment.collection_path(:post_id => 5, :active => 1)
- # # => /posts/5/comments.json?active=1
- #
- # Comment.collection_path({:post_id => 5}, {:active => 1})
- # # => /posts/5/comments.json?active=1
- #
- def collection_path(prefix_options = {}, query_options = nil)
- check_prefix_options(prefix_options)
- prefix_options, query_options = split_options(prefix_options) if query_options.nil?
- "#{prefix(prefix_options)}#{collection_name}.#{format.extension}#{query_string(query_options)}"
- end
-
- alias_method :set_primary_key, :primary_key= #:nodoc:
-
- # Builds a new, unsaved record using the default values from the remote server so
- # that it can be used with RESTful forms.
- #
- # ==== Options
- # * +attributes+ - A hash that overrides the default values from the server.
- #
- # Returns the new resource instance.
- #
- def build(attributes = {})
- attrs = self.format.decode(connection.get("#{new_element_path}").body).merge(attributes)
- self.new(attrs)
- end
-
- # Creates a new resource instance and makes a request to the remote service
- # that it be saved, making it equivalent to the following simultaneous calls:
- #
- # ryan = Person.new(:first => 'ryan')
- # ryan.save
- #
- # Returns the newly created resource. If a failure has occurred an
- # exception will be raised (see <tt>save</tt>). If the resource is invalid and
- # has not been saved then <tt>valid?</tt> will return <tt>false</tt>,
- # while <tt>new?</tt> will still return <tt>true</tt>.
- #
- # ==== Examples
- # Person.create(:name => 'Jeremy', :email => 'myname@nospam.com', :enabled => true)
- # my_person = Person.find(:first)
- # my_person.email # => myname@nospam.com
- #
- # dhh = Person.create(:name => 'David', :email => 'dhh@nospam.com', :enabled => true)
- # dhh.valid? # => true
- # dhh.new? # => false
- #
- # # We'll assume that there's a validation that requires the name attribute
- # that_guy = Person.create(:name => '', :email => 'thatguy@nospam.com', :enabled => true)
- # that_guy.valid? # => false
- # that_guy.new? # => true
- def create(attributes = {})
- self.new(attributes).tap { |resource| resource.save }
- end
-
- # Core method for finding resources. Used similarly to Active Record's +find+ method.
- #
- # ==== Arguments
- # The first argument is considered to be the scope of the query. That is, how many
- # resources are returned from the request. It can be one of the following.
- #
- # * <tt>:one</tt> - Returns a single resource.
- # * <tt>:first</tt> - Returns the first resource found.
- # * <tt>:last</tt> - Returns the last resource found.
- # * <tt>:all</tt> - Returns every resource that matches the request.
- #
- # ==== Options
- #
- # * <tt>:from</tt> - Sets the path or custom method that resources will be fetched from.
- # * <tt>:params</tt> - Sets query and \prefix (nested URL) parameters.
- #
- # ==== Examples
- # Person.find(1)
- # # => GET /people/1.json
- #
- # Person.find(:all)
- # # => GET /people.json
- #
- # Person.find(:all, :params => { :title => "CEO" })
- # # => GET /people.json?title=CEO
- #
- # Person.find(:first, :from => :managers)
- # # => GET /people/managers.json
- #
- # Person.find(:last, :from => :managers)
- # # => GET /people/managers.json
- #
- # Person.find(:all, :from => "/companies/1/people.json")
- # # => GET /companies/1/people.json
- #
- # Person.find(:one, :from => :leader)
- # # => GET /people/leader.json
- #
- # Person.find(:all, :from => :developers, :params => { :language => 'ruby' })
- # # => GET /people/developers.json?language=ruby
- #
- # Person.find(:one, :from => "/companies/1/manager.json")
- # # => GET /companies/1/manager.json
- #
- # StreetAddress.find(1, :params => { :person_id => 1 })
- # # => GET /people/1/street_addresses/1.json
- #
- # == Failure or missing data
- # A failure to find the requested object raises a ResourceNotFound
- # exception if the find was called with an id.
- # With any other scope, find returns nil when no data is returned.
- #
- # Person.find(1)
- # # => raises ResourceNotFound
- #
- # Person.find(:all)
- # Person.find(:first)
- # Person.find(:last)
- # # => nil
- def find(*arguments)
- scope = arguments.slice!(0)
- options = arguments.slice!(0) || {}
-
- case scope
- when :all then find_every(options)
- when :first then find_every(options).first
- when :last then find_every(options).last
- when :one then find_one(options)
- else find_single(scope, options)
- end
- end
-
-
- # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass
- # in all the same arguments to this method as you can to
- # <tt>find(:first)</tt>.
- def first(*args)
- find(:first, *args)
- end
-
- # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass
- # in all the same arguments to this method as you can to
- # <tt>find(:last)</tt>.
- def last(*args)
- find(:last, *args)
- end
-
- # This is an alias for find(:all). You can pass in all the same
- # arguments to this method as you can to <tt>find(:all)</tt>
- def all(*args)
- find(:all, *args)
- end
-
-
- # Deletes the resources with the ID in the +id+ parameter.
- #
- # ==== Options
- # All options specify \prefix and query parameters.
- #
- # ==== Examples
- # Event.delete(2) # sends DELETE /events/2
- #
- # Event.create(:name => 'Free Concert', :location => 'Community Center')
- # my_event = Event.find(:first) # let's assume this is event with ID 7
- # Event.delete(my_event.id) # sends DELETE /events/7
- #
- # # Let's assume a request to events/5/cancel.json
- # Event.delete(params[:id]) # sends DELETE /events/5
- def delete(id, options = {})
- connection.delete(element_path(id, options))
- end
-
- # Asserts the existence of a resource, returning <tt>true</tt> if the resource is found.
- #
- # ==== Examples
- # Note.create(:title => 'Hello, world.', :body => 'Nothing more for now...')
- # Note.exists?(1) # => true
- #
- # Note.exists(1349) # => false
- def exists?(id, options = {})
- if id
- prefix_options, query_options = split_options(options[:params])
- path = element_path(id, prefix_options, query_options)
- response = connection.head(path, headers)
- response.code.to_i == 200
- end
- # id && !find_single(id, options).nil?
- rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
- false
- end
-
- private
-
- def check_prefix_options(prefix_options)
- p_options = HashWithIndifferentAccess.new(prefix_options)
- prefix_parameters.each do |p|
- raise(MissingPrefixParam, "#{p} prefix_option is missing") if p_options[p].blank?
- end
- end
-
- # Find every resource
- def find_every(options)
- begin
- case from = options[:from]
- when Symbol
- instantiate_collection(get(from, options[:params]))
- when String
- path = "#{from}#{query_string(options[:params])}"
- instantiate_collection(format.decode(connection.get(path, headers).body) || [])
- else
- prefix_options, query_options = split_options(options[:params])
- path = collection_path(prefix_options, query_options)
- instantiate_collection( (format.decode(connection.get(path, headers).body) || []), prefix_options )
- end
- rescue ActiveResource::ResourceNotFound
- # Swallowing ResourceNotFound exceptions and return nil - as per
- # ActiveRecord.
- nil
- end
- end
-
- # Find a single resource from a one-off URL
- def find_one(options)
- case from = options[:from]
- when Symbol
- instantiate_record(get(from, options[:params]))
- when String
- path = "#{from}#{query_string(options[:params])}"
- instantiate_record(format.decode(connection.get(path, headers).body))
- end
- end
-
- # Find a single resource from the default URL
- def find_single(scope, options)
- prefix_options, query_options = split_options(options[:params])
- path = element_path(scope, prefix_options, query_options)
- instantiate_record(format.decode(connection.get(path, headers).body), prefix_options)
- end
-
- def instantiate_collection(collection, prefix_options = {})
- collection.collect! { |record| instantiate_record(record, prefix_options) }
- end
-
- def instantiate_record(record, prefix_options = {})
- new(record, true).tap do |resource|
- resource.prefix_options = prefix_options
- end
- end
-
-
- # Accepts a URI and creates the site URI from that.
- def create_site_uri_from(site)
- site.is_a?(URI) ? site.dup : URI.parser.parse(site)
- end
-
- # Accepts a URI and creates the proxy URI from that.
- def create_proxy_uri_from(proxy)
- proxy.is_a?(URI) ? proxy.dup : URI.parser.parse(proxy)
- end
-
- # contains a set of the current prefix parameters.
- def prefix_parameters
- @prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set
- end
-
- # Builds the query string for the request.
- def query_string(options)
- "?#{options.to_query}" unless options.nil? || options.empty?
- end
-
- # split an option hash into two hashes, one containing the prefix options,
- # and the other containing the leftovers.
- def split_options(options = {})
- prefix_options, query_options = {}, {}
-
- (options || {}).each do |key, value|
- next if key.blank? || !key.respond_to?(:to_sym)
- (prefix_parameters.include?(key.to_sym) ? prefix_options : query_options)[key.to_sym] = value
- end
-
- [ prefix_options, query_options ]
- end
- end
-
- attr_accessor :attributes #:nodoc:
- attr_accessor :prefix_options #:nodoc:
-
- # If no schema has been defined for the class (see
- # <tt>ActiveResource::schema=</tt>), the default automatic schema is
- # generated from the current instance's attributes
- def schema
- self.class.schema || self.attributes
- end
-
- # This is a list of known attributes for this resource. Either
- # gathered from the provided <tt>schema</tt>, or from the attributes
- # set on this instance after it has been fetched from the remote system.
- def known_attributes
- self.class.known_attributes + self.attributes.keys.map(&:to_s)
- end
-
-
- # Constructor method for \new resources; the optional +attributes+ parameter takes a \hash
- # of attributes for the \new resource.
- #
- # ==== Examples
- # my_course = Course.new
- # my_course.name = "Western Civilization"
- # my_course.lecturer = "Don Trotter"
- # my_course.save
- #
- # my_other_course = Course.new(:name => "Philosophy: Reason and Being", :lecturer => "Ralph Cling")
- # my_other_course.save
- def initialize(attributes = {}, persisted = false)
- @attributes = {}.with_indifferent_access
- @prefix_options = {}
- @persisted = persisted
- load(attributes)
- end
-
- # Returns a \clone of the resource that hasn't been assigned an +id+ yet and
- # is treated as a \new resource.
- #
- # ryan = Person.find(1)
- # not_ryan = ryan.clone
- # not_ryan.new? # => true
- #
- # Any active resource member attributes will NOT be cloned, though all other
- # attributes are. This is to prevent the conflict between any +prefix_options+
- # that refer to the original parent resource and the newly cloned parent
- # resource that does not exist.
- #
- # ryan = Person.find(1)
- # ryan.address = StreetAddress.find(1, :person_id => ryan.id)
- # ryan.hash = {:not => "an ARes instance"}
- #
- # not_ryan = ryan.clone
- # not_ryan.new? # => true
- # not_ryan.address # => NoMethodError
- # not_ryan.hash # => {:not => "an ARes instance"}
- def clone
- # Clone all attributes except the pk and any nested ARes
- cloned = Hash[attributes.reject {|k,v| k == self.class.primary_key || v.is_a?(ActiveResource::Base)}.map { |k, v| [k, v.clone] }]
- # Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
- # attempts to convert hashes into member objects and arrays into collections of objects. We want
- # the raw objects to be cloned so we bypass load by directly setting the attributes hash.
- resource = self.class.new({})
- resource.prefix_options = self.prefix_options
- resource.send :instance_variable_set, '@attributes', cloned
- resource
- end
-
-
- # Returns +true+ if this object hasn't yet been saved, otherwise, returns +false+.
- #
- # ==== Examples
- # not_new = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
- # not_new.new? # => false
- #
- # is_new = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM')
- # is_new.new? # => true
- #
- # is_new.save
- # is_new.new? # => false
- #
- def new?
- !persisted?
- end
- alias :new_record? :new?
-
- # Returns +true+ if this object has been saved, otherwise returns +false+.
- #
- # ==== Examples
- # persisted = Computer.create(:brand => 'Apple', :make => 'MacBook', :vendor => 'MacMall')
- # persisted.persisted? # => true
- #
- # not_persisted = Computer.new(:brand => 'IBM', :make => 'Thinkpad', :vendor => 'IBM')
- # not_persisted.persisted? # => false
- #
- # not_persisted.save
- # not_persisted.persisted? # => true
- #
- def persisted?
- @persisted
- end
-
- # Gets the <tt>\id</tt> attribute of the resource.
- def id
- attributes[self.class.primary_key]
- end
-
- # Sets the <tt>\id</tt> attribute of the resource.
- def id=(id)
- attributes[self.class.primary_key] = id
- end
-
- # Test for equality. Resource are equal if and only if +other+ is the same object or
- # is an instance of the same class, is not <tt>new?</tt>, and has the same +id+.
- #
- # ==== Examples
- # ryan = Person.create(:name => 'Ryan')
- # jamie = Person.create(:name => 'Jamie')
- #
- # ryan == jamie
- # # => false (Different name attribute and id)
- #
- # ryan_again = Person.new(:name => 'Ryan')
- # ryan == ryan_again
- # # => false (ryan_again is new?)
- #
- # ryans_clone = Person.create(:name => 'Ryan')
- # ryan == ryans_clone
- # # => false (Different id attributes)
- #
- # ryans_twin = Person.find(ryan.id)
- # ryan == ryans_twin
- # # => true
- #
- def ==(other)
- other.equal?(self) || (other.instance_of?(self.class) && other.id == id && other.prefix_options == prefix_options)
- end
-
- # Tests for equality (delegates to ==).
- def eql?(other)
- self == other
- end
-
- # Delegates to id in order to allow two resources of the same type and \id to work with something like:
- # [(a = Person.find 1), (b = Person.find 2)] & [(c = Person.find 1), (d = Person.find 4)] # => [a]
- def hash
- id.hash
- end
-
- # Duplicates the current resource without saving it.
- #
- # ==== Examples
- # my_invoice = Invoice.create(:customer => 'That Company')
- # next_invoice = my_invoice.dup
- # next_invoice.new? # => true
- #
- # next_invoice.save
- # next_invoice == my_invoice # => false (different id attributes)
- #
- # my_invoice.customer # => That Company
- # next_invoice.customer # => That Company
- def dup
- self.class.new.tap do |resource|
- resource.attributes = @attributes
- resource.prefix_options = @prefix_options
- end
- end
-
- # Saves (+POST+) or \updates (+PUT+) a resource. Delegates to +create+ if the object is \new,
- # +update+ if it exists. If the response to the \save includes a body, it will be assumed that this body
- # is Json for the final object as it looked after the \save (which would include attributes like +created_at+
- # that weren't part of the original submit).
- #
- # ==== Examples
- # my_company = Company.new(:name => 'RoleModel Software', :owner => 'Ken Auer', :size => 2)
- # my_company.new? # => true
- # my_company.save # sends POST /companies/ (create)
- #
- # my_company.new? # => false
- # my_company.size = 10
- # my_company.save # sends PUT /companies/1 (update)
- def save
- new? ? create : update
- end
-
- # Saves the resource.
- #
- # If the resource is new, it is created via +POST+, otherwise the
- # existing resource is updated via +PUT+.
- #
- # With <tt>save!</tt> validations always run. If any of them fail
- # ActiveResource::ResourceInvalid gets raised, and nothing is POSTed to
- # the remote system.
- # See ActiveResource::Validations for more information.
- #
- # There's a series of callbacks associated with <tt>save!</tt>. If any
- # of the <tt>before_*</tt> callbacks return +false+ the action is
- # cancelled and <tt>save!</tt> raises ActiveResource::ResourceInvalid.
- def save!
- save || raise(ResourceInvalid.new(self))
- end
-
- # Deletes the resource from the remote service.
- #
- # ==== Examples
- # my_id = 3
- # my_person = Person.find(my_id)
- # my_person.destroy
- # Person.find(my_id) # 404 (Resource Not Found)
- #
- # new_person = Person.create(:name => 'James')
- # new_id = new_person.id # => 7
- # new_person.destroy
- # Person.find(new_id) # 404 (Resource Not Found)
- def destroy
- connection.delete(element_path, self.class.headers)
- end
-
- # Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is
- # found on the remote service. Using this method, you can check for
- # resources that may have been deleted between the object's instantiation
- # and actions on it.
- #
- # ==== Examples
- # Person.create(:name => 'Theodore Roosevelt')
- # that_guy = Person.find(:first)
- # that_guy.exists? # => true
- #
- # that_lady = Person.new(:name => 'Paul Bean')
- # that_lady.exists? # => false
- #
- # guys_id = that_guy.id
- # Person.delete(guys_id)
- # that_guy.exists? # => false
- def exists?
- !new? && self.class.exists?(to_param, :params => prefix_options)
- end
-
- # Returns the serialized string representation of the resource in the configured
- # serialization format specified in ActiveResource::Base.format. The options
- # applicable depend on the configured encoding format.
- def encode(options={})
- send("to_#{self.class.format.extension}", options)
- end
-
- # A method to \reload the attributes of this object from the remote web service.
- #
- # ==== Examples
- # my_branch = Branch.find(:first)
- # my_branch.name # => "Wislon Raod"
- #
- # # Another client fixes the typo...
- #
- # my_branch.name # => "Wislon Raod"
- # my_branch.reload
- # my_branch.name # => "Wilson Road"
- def reload
- self.load(self.class.find(to_param, :params => @prefix_options).attributes)
- end
-
- # A method to manually load attributes from a \hash. Recursively loads collections of
- # resources. This method is called in +initialize+ and +create+ when a \hash of attributes
- # is provided.
- #
- # ==== Examples
- # my_attrs = {:name => 'J&J Textiles', :industry => 'Cloth and textiles'}
- # my_attrs = {:name => 'Marty', :colors => ["red", "green", "blue"]}
- #
- # the_supplier = Supplier.find(:first)
- # the_supplier.name # => 'J&M Textiles'
- # the_supplier.load(my_attrs)
- # the_supplier.name('J&J Textiles')
- #
- # # These two calls are the same as Supplier.new(my_attrs)
- # my_supplier = Supplier.new
- # my_supplier.load(my_attrs)
- #
- # # These three calls are the same as Supplier.create(my_attrs)
- # your_supplier = Supplier.new
- # your_supplier.load(my_attrs)
- # your_supplier.save
- def load(attributes, remove_root = false)
- raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
- @prefix_options, attributes = split_options(attributes)
-
- if attributes.keys.size == 1
- remove_root = self.class.element_name == attributes.keys.first.to_s
- end
-
- attributes = Formats.remove_root(attributes) if remove_root
-
- attributes.each do |key, value|
- @attributes[key.to_s] =
- case value
- when Array
- resource = nil
- value.map do |attrs|
- if attrs.is_a?(Hash)
- resource ||= find_or_create_resource_for_collection(key)
- resource.new(attrs)
- else
- attrs.duplicable? ? attrs.dup : attrs
- end
- end
- when Hash
- resource = find_or_create_resource_for(key)
- resource.new(value)
- else
- value.duplicable? ? value.dup : value
- end
- end
- self
- end
-
- # Updates a single attribute and then saves the object.
- #
- # Note: Unlike ActiveRecord::Base.update_attribute, this method <b>is</b>
- # subject to normal validation routines as an update sends the whole body
- # of the resource in the request. (See Validations).
- #
- # As such, this method is equivalent to calling update_attributes with a single attribute/value pair.
- #
- # If the saving fails because of a connection or remote service error, an
- # exception will be raised. If saving fails because the resource is
- # invalid then <tt>false</tt> will be returned.
- def update_attribute(name, value)
- self.send("#{name}=".to_sym, value)
- self.save
- end
-
- # Updates this resource with all the attributes from the passed-in Hash
- # and requests that the record be saved.
- #
- # If the saving fails because of a connection or remote service error, an
- # exception will be raised. If saving fails because the resource is
- # invalid then <tt>false</tt> will be returned.
- #
- # Note: Though this request can be made with a partial set of the
- # resource's attributes, the full body of the request will still be sent
- # in the save request to the remote service.
- def update_attributes(attributes)
- load(attributes, false) && save
- end
-
- # For checking <tt>respond_to?</tt> without searching the attributes (which is faster).
- alias_method :respond_to_without_attributes?, :respond_to?
-
- # A method to determine if an object responds to a message (e.g., a method call). In Active Resource, a Person object with a
- # +name+ attribute can answer <tt>true</tt> to <tt>my_person.respond_to?(:name)</tt>, <tt>my_person.respond_to?(:name=)</tt>, and
- # <tt>my_person.respond_to?(:name?)</tt>.
- def respond_to?(method, include_priv = false)
- method_name = method.to_s
- if attributes.nil?
- super
- elsif known_attributes.include?(method_name)
- true
- elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`)
- true
- else
- # super must be called at the end of the method, because the inherited respond_to?
- # would return true for generated readers, even if the attribute wasn't present
- super
- end
- end
-
- def to_json(options={})
- super({ :root => self.class.element_name }.merge(options))
- end
-
- def to_xml(options={})
- super({ :root => self.class.element_name }.merge(options))
- end
-
- protected
- def connection(refresh = false)
- self.class.connection(refresh)
- end
-
- # Update the resource on the remote service.
- def update
- connection.put(element_path(prefix_options), encode, self.class.headers).tap do |response|
- load_attributes_from_response(response)
- end
- end
-
- # Create (i.e., \save to the remote service) the \new resource.
- def create
- connection.post(collection_path, encode, self.class.headers).tap do |response|
- self.id = id_from_response(response)
- load_attributes_from_response(response)
- end
- end
-
- def load_attributes_from_response(response)
- if (response_code_allows_body?(response.code) &&
- (response['Content-Length'].nil? || response['Content-Length'] != "0") &&
- !response.body.nil? && response.body.strip.size > 0)
- load(self.class.format.decode(response.body), true)
- @persisted = true
- end
- end
-
- # Takes a response from a typical create post and pulls the ID out
- def id_from_response(response)
- response['Location'][/\/([^\/]*?)(\.\w+)?$/, 1] if response['Location']
- end
-
- def element_path(options = nil)
- self.class.element_path(to_param, options || prefix_options)
- end
-
- def new_element_path
- self.class.new_element_path(prefix_options)
- end
-
- def collection_path(options = nil)
- self.class.collection_path(options || prefix_options)
- end
-
- private
-
- def read_attribute_for_serialization(n)
- attributes[n]
- end
-
- # Determine whether the response is allowed to have a body per HTTP 1.1 spec section 4.4.1
- def response_code_allows_body?(c)
- !((100..199).include?(c) || [204,304].include?(c))
- end
-
- # Tries to find a resource for a given collection name; if it fails, then the resource is created
- def find_or_create_resource_for_collection(name)
- find_or_create_resource_for(ActiveSupport::Inflector.singularize(name.to_s))
- end
-
- # Tries to find a resource in a non empty list of nested modules
- # if it fails, then the resource is created
- def find_or_create_resource_in_modules(resource_name, module_names)
- receiver = Object
- namespaces = module_names[0, module_names.size-1].map do |module_name|
- receiver = receiver.const_get(module_name)
- end
- const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
- if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) }
- namespace.const_get(*const_args)
- else
- create_resource_for(resource_name)
- end
- end
-
- # Tries to find a resource for a given name; if it fails, then the resource is created
- def find_or_create_resource_for(name)
- resource_name = name.to_s.camelize
-
- const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false]
- if self.class.const_defined?(*const_args)
- self.class.const_get(*const_args)
- else
- ancestors = self.class.name.split("::")
- if ancestors.size > 1
- find_or_create_resource_in_modules(resource_name, ancestors)
- else
- if Object.const_defined?(*const_args)
- Object.const_get(*const_args)
- else
- create_resource_for(resource_name)
- end
- end
- end
- end
-
- # Create and return a class definition for a resource inside the current resource
- def create_resource_for(resource_name)
- resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base))
- resource.prefix = self.class.prefix
- resource.site = self.class.site
- resource
- end
-
- def split_options(options = {})
- self.class.__send__(:split_options, options)
- end
-
- def method_missing(method_symbol, *arguments) #:nodoc:
- method_name = method_symbol.to_s
-
- if method_name =~ /(=|\?)$/
- case $1
- when "="
- attributes[$`] = arguments.first
- when "?"
- attributes[$`]
- end
- else
- return attributes[method_name] if attributes.include?(method_name)
- # not set right now but we know about it
- return nil if known_attributes.include?(method_name)
- super
- end
- end
- end
-
- class Base
- extend ActiveModel::Naming
- include CustomMethods, Observing, Validations
- include ActiveModel::Conversion
- include ActiveModel::Serializers::JSON
- include ActiveModel::Serializers::Xml
- end
-end
diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb
deleted file mode 100644
index 94839c8c25..0000000000
--- a/activeresource/lib/active_resource/connection.rb
+++ /dev/null
@@ -1,287 +0,0 @@
-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'
-require 'uri'
-
-module ActiveResource
- # Class to handle connections to remote web services.
- # This class is used by ActiveResource::Base to interface with REST
- # services.
- class Connection
-
- HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
- :put => 'Content-Type',
- :post => 'Content-Type',
- :delete => 'Accept',
- :head => 'Accept'
- }
-
- attr_reader :site, :user, :password, :auth_type, :timeout, :proxy, :ssl_options
- attr_accessor :format
-
- class << self
- def requests
- @@requests ||= []
- end
- end
-
- # The +site+ parameter is required and will set the +site+
- # attribute to the URI for the remote resource service.
- def initialize(site, format = ActiveResource::Formats::JsonFormat)
- raise ArgumentError, 'Missing site URI' unless site
- @user = @password = nil
- self.site = site
- self.format = format
- end
-
- # Set URI for remote service.
- def site=(site)
- @site = site.is_a?(URI) ? site : URI.parser.parse(site)
- @user = URI.parser.unescape(@site.user) if @site.user
- @password = URI.parser.unescape(@site.password) if @site.password
- end
-
- # Set the proxy for remote service.
- def proxy=(proxy)
- @proxy = proxy.is_a?(URI) ? proxy : URI.parser.parse(proxy)
- end
-
- # Sets the user for remote service.
- def user=(user)
- @user = user
- end
-
- # Sets the password for remote service.
- def password=(password)
- @password = password
- end
-
- # Sets the auth type for remote service.
- def auth_type=(auth_type)
- @auth_type = legitimize_auth_type(auth_type)
- end
-
- # Sets the number of seconds after which HTTP requests to the remote service should time out.
- def timeout=(timeout)
- @timeout = timeout
- end
-
- # Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
- def ssl_options=(opts={})
- @ssl_options = opts
- end
-
- # Executes a GET request.
- # Used to get (find) resources.
- def get(path, headers = {})
- with_auth { request(:get, path, build_request_headers(headers, :get, self.site.merge(path))) }
- end
-
- # Executes a DELETE request (see HTTP protocol documentation if unfamiliar).
- # Used to delete resources.
- def delete(path, headers = {})
- with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) }
- end
-
- # Executes a PUT request (see HTTP protocol documentation if unfamiliar).
- # Used to update resources.
- def put(path, body = '', headers = {})
- with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) }
- end
-
- # Executes a POST request.
- # Used to create new resources.
- def post(path, body = '', headers = {})
- with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) }
- end
-
- # Executes a HEAD request.
- # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
- def head(path, headers = {})
- with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) }
- end
-
- private
- # Makes a request to the remote service.
- def request(method, path, *arguments)
- result = ActiveSupport::Notifications.instrument("request.active_resource") do |payload|
- payload[:method] = method
- payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}"
- payload[:result] = http.send(method, path, *arguments)
- end
- handle_response(result)
- rescue Timeout::Error => e
- raise TimeoutError.new(e.message)
- rescue OpenSSL::SSL::SSLError => e
- raise SSLError.new(e.message)
- end
-
- # Handles response and error codes from the remote service.
- def handle_response(response)
- case response.code.to_i
- when 301, 302, 303, 307
- raise(Redirection.new(response))
- when 200...400
- response
- when 400
- raise(BadRequest.new(response))
- when 401
- raise(UnauthorizedAccess.new(response))
- when 403
- raise(ForbiddenAccess.new(response))
- when 404
- raise(ResourceNotFound.new(response))
- when 405
- raise(MethodNotAllowed.new(response))
- when 409
- raise(ResourceConflict.new(response))
- when 410
- raise(ResourceGone.new(response))
- when 422
- raise(ResourceInvalid.new(response))
- when 401...500
- raise(ClientError.new(response))
- when 500...600
- raise(ServerError.new(response))
- else
- raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
- end
- end
-
- # Creates new Net::HTTP instance for communication with the
- # remote service and resources.
- def http
- configure_http(new_http)
- end
-
- def new_http
- if @proxy
- Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password)
- else
- Net::HTTP.new(@site.host, @site.port)
- end
- end
-
- def configure_http(http)
- http = apply_ssl_options(http)
-
- # Net::HTTP timeouts default to 60 seconds.
- if @timeout
- http.open_timeout = @timeout
- http.read_timeout = @timeout
- end
-
- http
- end
-
- def apply_ssl_options(http)
- return http unless @site.is_a?(URI::HTTPS)
-
- http.use_ssl = true
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
- return http unless defined?(@ssl_options)
-
- http.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path]
- http.ca_file = @ssl_options[:ca_file] if @ssl_options[:ca_file]
-
- http.cert = @ssl_options[:cert] if @ssl_options[:cert]
- http.key = @ssl_options[:key] if @ssl_options[:key]
-
- http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store]
- http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout]
-
- http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode]
- http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback]
- http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth]
-
- http
- end
-
- def default_header
- @default_header ||= {}
- end
-
- # Builds headers for request to remote service.
- def build_request_headers(headers, http_method, uri)
- authorization_header(http_method, uri).update(default_header).update(http_format_header(http_method)).update(headers)
- end
-
- def response_auth_header
- @response_auth_header ||= ""
- end
-
- def with_auth
- retried ||= false
- yield
- rescue UnauthorizedAccess => e
- raise if retried || auth_type != :digest
- @response_auth_header = e.response['WWW-Authenticate']
- retried = true
- retry
- end
-
- def authorization_header(http_method, uri)
- if @user || @password
- if auth_type == :digest
- { 'Authorization' => digest_auth_header(http_method, uri) }
- else
- { 'Authorization' => 'Basic ' + ["#{@user}:#{@password}"].pack('m').delete("\r\n") }
- end
- else
- {}
- end
- end
-
- def digest_auth_header(http_method, uri)
- params = extract_params_from_response
-
- request_uri = uri.path
- request_uri << "?#{uri.query}" if uri.query
-
- ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}")
- ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{request_uri}")
-
- params.merge!('cnonce' => client_nonce)
- request_digest = Digest::MD5.hexdigest([ha1, params['nonce'], "0", params['cnonce'], params['qop'], ha2].join(":"))
- "Digest #{auth_attributes_for(uri, request_digest, params)}"
- end
-
- def client_nonce
- Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535)))
- end
-
- def extract_params_from_response
- params = {}
- if response_auth_header =~ /^(\w+) (.*)/
- $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 }
- end
- params
- end
-
- def auth_attributes_for(uri, request_digest, params)
- [
- %Q(username="#{@user}"),
- %Q(realm="#{params['realm']}"),
- %Q(qop="#{params['qop']}"),
- %Q(uri="#{uri.path}"),
- %Q(nonce="#{params['nonce']}"),
- %Q(nc="0"),
- %Q(cnonce="#{params['cnonce']}"),
- %Q(opaque="#{params['opaque']}"),
- %Q(response="#{request_digest}")].join(", ")
- end
-
- def http_format_header(http_method)
- {HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type}
- end
-
- def legitimize_auth_type(auth_type)
- return :basic if auth_type.nil?
- auth_type = auth_type.to_sym
- auth_type.in?([:basic, :digest]) ? auth_type : :basic
- end
- end
-end
diff --git a/activeresource/lib/active_resource/custom_methods.rb b/activeresource/lib/active_resource/custom_methods.rb
deleted file mode 100644
index c1931b2758..0000000000
--- a/activeresource/lib/active_resource/custom_methods.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-require 'active_support/core_ext/object/blank'
-
-module ActiveResource
- # A module to support custom REST methods and sub-resources, allowing you to break out
- # of the "default" REST methods with your own custom resource requests. For example,
- # say you use Rails to expose a REST service and configure your routes with:
- #
- # map.resources :people, :new => { :register => :post },
- # :member => { :promote => :put, :deactivate => :delete }
- # :collection => { :active => :get }
- #
- # This route set creates routes for the following HTTP requests:
- #
- # POST /people/new/register.json # PeopleController.register
- # PUT /people/1/promote.json # PeopleController.promote with :id => 1
- # DELETE /people/1/deactivate.json # PeopleController.deactivate with :id => 1
- # GET /people/active.json # PeopleController.active
- #
- # Using this module, Active Resource can use these custom REST methods just like the
- # standard methods.
- #
- # class Person < ActiveResource::Base
- # self.site = "http://37s.sunrise.i:3000"
- # end
- #
- # Person.new(:name => 'Ryan).post(:register) # POST /people/new/register.json
- # # => { :id => 1, :name => 'Ryan' }
- #
- # Person.find(1).put(:promote, :position => 'Manager') # PUT /people/1/promote.json
- # Person.find(1).delete(:deactivate) # DELETE /people/1/deactivate.json
- #
- # Person.get(:active) # GET /people/active.json
- # # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
- #
- module CustomMethods
- extend ActiveSupport::Concern
-
- included do
- class << self
- alias :orig_delete :delete
-
- # Invokes a GET to a given custom REST method. For example:
- #
- # Person.get(:active) # GET /people/active.json
- # # => [{:id => 1, :name => 'Ryan'}, {:id => 2, :name => 'Joe'}]
- #
- # Person.get(:active, :awesome => true) # GET /people/active.json?awesome=true
- # # => [{:id => 1, :name => 'Ryan'}]
- #
- # Note: the objects returned from this method are not automatically converted
- # into ActiveResource::Base instances - they are ordinary Hashes. If you are expecting
- # ActiveResource::Base instances, use the <tt>find</tt> class method with the
- # <tt>:from</tt> option. For example:
- #
- # Person.find(:all, :from => :active)
- def get(custom_method_name, options = {})
- hashified = format.decode(connection.get(custom_method_collection_url(custom_method_name, options), headers).body)
- derooted = Formats.remove_root(hashified)
- derooted.is_a?(Array) ? derooted.map { |e| Formats.remove_root(e) } : derooted
- end
-
- def post(custom_method_name, options = {}, body = '')
- connection.post(custom_method_collection_url(custom_method_name, options), body, headers)
- end
-
- def put(custom_method_name, options = {}, body = '')
- connection.put(custom_method_collection_url(custom_method_name, options), body, headers)
- end
-
- def delete(custom_method_name, options = {})
- # Need to jump through some hoops to retain the original class 'delete' method
- if custom_method_name.is_a?(Symbol)
- connection.delete(custom_method_collection_url(custom_method_name, options), headers)
- else
- orig_delete(custom_method_name, options)
- end
- end
- end
- end
-
- module ClassMethods
- def custom_method_collection_url(method_name, options = {})
- prefix_options, query_options = split_options(options)
- "#{prefix(prefix_options)}#{collection_name}/#{method_name}.#{format.extension}#{query_string(query_options)}"
- end
- end
-
- module InstanceMethods
- def get(method_name, options = {})
- self.class.format.decode(connection.get(custom_method_element_url(method_name, options), self.class.headers).body)
- end
-
- def post(method_name, options = {}, body = nil)
- request_body = body.blank? ? encode : body
- if new?
- connection.post(custom_method_new_element_url(method_name, options), request_body, self.class.headers)
- else
- connection.post(custom_method_element_url(method_name, options), request_body, self.class.headers)
- end
- end
-
- def put(method_name, options = {}, body = '')
- connection.put(custom_method_element_url(method_name, options), body, self.class.headers)
- end
-
- def delete(method_name, options = {})
- connection.delete(custom_method_element_url(method_name, options), self.class.headers)
- end
-
-
- private
- def custom_method_element_url(method_name, options = {})
- "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/#{id}/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
- end
-
- def custom_method_new_element_url(method_name, options = {})
- "#{self.class.prefix(prefix_options)}#{self.class.collection_name}/new/#{method_name}.#{self.class.format.extension}#{self.class.__send__(:query_string, options)}"
- end
- end
- end
-end
diff --git a/activeresource/lib/active_resource/exceptions.rb b/activeresource/lib/active_resource/exceptions.rb
deleted file mode 100644
index 6b953b28ad..0000000000
--- a/activeresource/lib/active_resource/exceptions.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-module ActiveResource
- class ConnectionError < StandardError # :nodoc:
- attr_reader :response
-
- def initialize(response, message = nil)
- @response = response
- @message = message
- end
-
- def to_s
- message = "Failed."
- message << " Response code = #{response.code}." if response.respond_to?(:code)
- message << " Response message = #{response.message}." if response.respond_to?(:message)
- message
- end
- end
-
- # Raised when a Timeout::Error occurs.
- class TimeoutError < ConnectionError
- def initialize(message)
- @message = message
- end
- def to_s; @message ;end
- end
-
- # Raised when a OpenSSL::SSL::SSLError occurs.
- class SSLError < ConnectionError
- def initialize(message)
- @message = message
- end
- def to_s; @message ;end
- end
-
- # 3xx Redirection
- class Redirection < ConnectionError # :nodoc:
- def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
- end
-
- # Raised when ...
- class MissingPrefixParam < ArgumentError; end # :nodoc:
-
- # 4xx Client Error
- class ClientError < ConnectionError; end # :nodoc:
-
- # 400 Bad Request
- class BadRequest < ClientError; end # :nodoc
-
- # 401 Unauthorized
- class UnauthorizedAccess < ClientError; end # :nodoc
-
- # 403 Forbidden
- class ForbiddenAccess < ClientError; end # :nodoc
-
- # 404 Not Found
- class ResourceNotFound < ClientError; end # :nodoc:
-
- # 409 Conflict
- class ResourceConflict < ClientError; end # :nodoc:
-
- # 410 Gone
- class ResourceGone < ClientError; end # :nodoc:
-
- # 5xx Server Error
- class ServerError < ConnectionError; end # :nodoc:
-
- # 405 Method Not Allowed
- class MethodNotAllowed < ClientError # :nodoc:
- def allowed_methods
- @response['Allow'].split(',').map { |verb| verb.strip.downcase.to_sym }
- end
- end
-end
diff --git a/activeresource/lib/active_resource/formats.rb b/activeresource/lib/active_resource/formats.rb
deleted file mode 100644
index f7ad689cc5..0000000000
--- a/activeresource/lib/active_resource/formats.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-module ActiveResource
- module Formats
- autoload :XmlFormat, 'active_resource/formats/xml_format'
- autoload :JsonFormat, 'active_resource/formats/json_format'
-
- # Lookup the format class from a mime type reference symbol. Example:
- #
- # ActiveResource::Formats[:xml] # => ActiveResource::Formats::XmlFormat
- # ActiveResource::Formats[:json] # => ActiveResource::Formats::JsonFormat
- def self.[](mime_type_reference)
- ActiveResource::Formats.const_get(ActiveSupport::Inflector.camelize(mime_type_reference.to_s) + "Format")
- end
-
- def self.remove_root(data)
- if data.is_a?(Hash) && data.keys.size == 1
- data.values.first
- else
- data
- end
- end
- end
-end
diff --git a/activeresource/lib/active_resource/formats/json_format.rb b/activeresource/lib/active_resource/formats/json_format.rb
deleted file mode 100644
index 827d1cc23a..0000000000
--- a/activeresource/lib/active_resource/formats/json_format.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'active_support/json'
-
-module ActiveResource
- module Formats
- module JsonFormat
- extend self
-
- def extension
- "json"
- end
-
- def mime_type
- "application/json"
- end
-
- def encode(hash, options = nil)
- ActiveSupport::JSON.encode(hash, options)
- end
-
- def decode(json)
- Formats.remove_root(ActiveSupport::JSON.decode(json))
- end
- end
- end
-end
diff --git a/activeresource/lib/active_resource/formats/xml_format.rb b/activeresource/lib/active_resource/formats/xml_format.rb
deleted file mode 100644
index 49cb9aa1ac..0000000000
--- a/activeresource/lib/active_resource/formats/xml_format.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'active_support/core_ext/hash/conversions'
-
-module ActiveResource
- module Formats
- module XmlFormat
- extend self
-
- def extension
- "xml"
- end
-
- def mime_type
- "application/xml"
- end
-
- def encode(hash, options={})
- hash.to_xml(options)
- end
-
- def decode(xml)
- Formats.remove_root(Hash.from_xml(xml))
- end
- end
- end
-end
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
deleted file mode 100644
index 36f52d61d3..0000000000
--- a/activeresource/lib/active_resource/http_mock.rb
+++ /dev/null
@@ -1,335 +0,0 @@
-require 'active_support/core_ext/kernel/reporting'
-require 'active_support/core_ext/object/inclusion'
-
-module ActiveResource
- class InvalidRequestError < StandardError; end #:nodoc:
-
- # One thing that has always been a pain with remote web services is testing. The HttpMock
- # class makes it easy to test your Active Resource models by creating a set of mock responses to specific
- # requests.
- #
- # To test your Active Resource model, you simply call the ActiveResource::HttpMock.respond_to
- # method with an attached block. The block declares a set of URIs with expected input, and the output
- # each request should return. The passed in block has any number of entries in the following generalized
- # format:
- #
- # mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
- #
- # * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or
- # +head+.
- # * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
- # called.
- # * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
- # hash format, such as <tt>{ "Content-Type" => "application/json" }</tt>. This mock will only trigger
- # if your tests sends a request with identical headers.
- # * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
- # such as Json.
- # * <tt>status</tt> - The HTTP response code, as an integer, to return with the response.
- # * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
- # <tt>request_headers</tt> listed above.
- #
- # In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
- # +path+ and <tt>request_headers</tt>. If no match is found an +InvalidRequestError+ exception
- # will be raised showing you what request it could not find a response for and also what requests and response
- # pairs have been recorded so you can create a new mock for that request.
- #
- # ==== Example
- # def setup
- # @matz = { :person => { :id => 1, :name => "Matz" } }.to_json
- # ActiveResource::HttpMock.respond_to do |mock|
- # mock.post "/people.json", {}, @matz, 201, "Location" => "/people/1.json"
- # mock.get "/people/1.json", {}, @matz
- # mock.put "/people/1.json", {}, nil, 204
- # mock.delete "/people/1.json", {}, nil, 200
- # end
- # end
- #
- # def test_get_matz
- # person = Person.find(1)
- # assert_equal "Matz", person.name
- # end
- #
- class HttpMock
- class Responder #:nodoc:
- def initialize(responses)
- @responses = responses
- end
-
- [ :post, :put, :get, :delete, :head ].each do |method|
- # def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
- # @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
- # end
- module_eval <<-EOE, __FILE__, __LINE__ + 1
- def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
- request = Request.new(:#{method}, path, nil, request_headers)
- response = Response.new(body || "", status, response_headers)
-
- delete_duplicate_responses(request)
-
- @responses << [request, response]
- end
- EOE
- end
-
- private
-
- def delete_duplicate_responses(request)
- @responses.delete_if {|r| r[0] == request }
- end
- end
-
- class << self
-
- # Returns an array of all request objects that have been sent to the mock. You can use this to check
- # if your model actually sent an HTTP request.
- #
- # ==== Example
- # def setup
- # @matz = { :person => { :id => 1, :name => "Matz" } }.to_json
- # ActiveResource::HttpMock.respond_to do |mock|
- # mock.get "/people/1.json", {}, @matz
- # end
- # end
- #
- # def test_should_request_remote_service
- # person = Person.find(1) # Call the remote service
- #
- # # This request object has the same HTTP method and path as declared by the mock
- # expected_request = ActiveResource::Request.new(:get, "/people/1.json")
- #
- # # Assert that the mock received, and responded to, the expected request from the model
- # assert ActiveResource::HttpMock.requests.include?(expected_request)
- # end
- def requests
- @@requests ||= []
- end
-
- # Returns the list of requests and their mocked responses. Look up a
- # response for a request using <tt>responses.assoc(request)</tt>.
- def responses
- @@responses ||= []
- end
-
- # Accepts a block which declares a set of requests and responses for the HttpMock to respond to in
- # the following format:
- #
- # mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
- #
- # === Example
- #
- # @matz = { :person => { :id => 1, :name => "Matz" } }.to_json
- # ActiveResource::HttpMock.respond_to do |mock|
- # mock.post "/people.json", {}, @matz, 201, "Location" => "/people/1.json"
- # mock.get "/people/1.json", {}, @matz
- # mock.put "/people/1.json", {}, nil, 204
- # mock.delete "/people/1.json", {}, nil, 200
- # end
- #
- # Alternatively, accepts a hash of <tt>{Request => Response}</tt> pairs allowing you to generate
- # these the following format:
- #
- # ActiveResource::Request.new(method, path, body, request_headers)
- # ActiveResource::Response.new(body, status, response_headers)
- #
- # === Example
- #
- # Request.new(:#{method}, path, nil, request_headers)
- #
- # @matz = { :person => { :id => 1, :name => "Matz" } }.to_json
- #
- # create_matz = ActiveResource::Request.new(:post, '/people.json', @matz, {})
- # created_response = ActiveResource::Response.new("", 201, {"Location" => "/people/1.json"})
- # get_matz = ActiveResource::Request.new(:get, '/people/1.json', nil)
- # ok_response = ActiveResource::Response.new("", 200, {})
- #
- # pairs = {create_matz => created_response, get_matz => ok_response}
- #
- # ActiveResource::HttpMock.respond_to(pairs)
- #
- # Note, by default, every time you call +respond_to+, any previous request and response pairs stored
- # in HttpMock will be deleted giving you a clean slate to work on.
- #
- # If you want to override this behavior, pass in +false+ as the last argument to +respond_to+
- #
- # === Example
- #
- # ActiveResource::HttpMock.respond_to do |mock|
- # mock.send(:get, "/people/1", {}, "JSON1")
- # end
- # ActiveResource::HttpMock.responses.length #=> 1
- #
- # ActiveResource::HttpMock.respond_to(false) do |mock|
- # mock.send(:get, "/people/2", {}, "JSON2")
- # end
- # ActiveResource::HttpMock.responses.length #=> 2
- #
- # This also works with passing in generated pairs of requests and responses, again, just pass in false
- # as the last argument:
- #
- # === Example
- #
- # ActiveResource::HttpMock.respond_to do |mock|
- # mock.send(:get, "/people/1", {}, "JSON1")
- # end
- # ActiveResource::HttpMock.responses.length #=> 1
- #
- # get_matz = ActiveResource::Request.new(:get, '/people/1.json', nil)
- # ok_response = ActiveResource::Response.new("", 200, {})
- #
- # pairs = {get_matz => ok_response}
- #
- # ActiveResource::HttpMock.respond_to(pairs, false)
- # ActiveResource::HttpMock.responses.length #=> 2
- #
- # # If you add a response with an existing request, it will be replaced
- #
- # fail_response = ActiveResource::Response.new("", 404, {})
- # pairs = {get_matz => fail_response}
- #
- # ActiveResource::HttpMock.respond_to(pairs, false)
- # ActiveResource::HttpMock.responses.length #=> 2
- #
- def respond_to(*args) #:yields: mock
- pairs = args.first || {}
- reset! if args.last.class != FalseClass
-
- if block_given?
- yield Responder.new(responses)
- else
- delete_responses_to_replace pairs.to_a
- responses.concat pairs.to_a
- Responder.new(responses)
- end
- end
-
- def delete_responses_to_replace(new_responses)
- new_responses.each{|nr|
- request_to_remove = nr[0]
- @@responses = responses.delete_if{|r| r[0] == request_to_remove}
- }
- end
-
- # Deletes all logged requests and responses.
- def reset!
- requests.clear
- responses.clear
- end
- end
-
- # body? methods
- { true => %w(post put),
- false => %w(get delete head) }.each do |has_body, methods|
- methods.each do |method|
- # def post(path, body, headers)
- # request = ActiveResource::Request.new(:post, path, body, headers)
- # self.class.requests << request
- # if response = self.class.responses.assoc(request)
- # response[1]
- # else
- # raise InvalidRequestError.new("Could not find a response recorded for #{request.to_s} - Responses recorded are: - #{inspect_responses}")
- # end
- # end
- module_eval <<-EOE, __FILE__, __LINE__ + 1
- def #{method}(path, #{'body, ' if has_body}headers)
- request = ActiveResource::Request.new(:#{method}, path, #{has_body ? 'body, ' : 'nil, '}headers)
- self.class.requests << request
- if response = self.class.responses.assoc(request)
- response[1]
- else
- raise InvalidRequestError.new("Could not find a response recorded for \#{request.to_s} - Responses recorded are: \#{inspect_responses}")
- end
- end
- EOE
- end
- end
-
- def initialize(site) #:nodoc:
- @site = site
- end
-
- def inspect_responses #:nodoc:
- self.class.responses.map { |r| r[0].to_s }.inspect
- end
- end
-
- class Request
- attr_accessor :path, :method, :body, :headers
-
- def initialize(method, path, body = nil, headers = {})
- @method, @path, @body, @headers = method, path, body, headers
- end
-
- def ==(req)
- path == req.path && method == req.method && headers_match?(req)
- end
-
- def to_s
- "<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"
- end
-
- private
-
- def headers_match?(req)
- # Ignore format header on equality if it's not defined
- format_header = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method]
- if headers[format_header].present? || req.headers[format_header].blank?
- headers == req.headers
- else
- headers.dup.merge(format_header => req.headers[format_header]) == req.headers
- end
- end
- end
-
- class Response
- attr_accessor :body, :message, :code, :headers
-
- def initialize(body, message = 200, headers = {})
- @body, @message, @headers = body, message.to_s, headers
- @code = @message[0,3].to_i
-
- resp_cls = Net::HTTPResponse::CODE_TO_OBJ[@code.to_s]
- if resp_cls && !resp_cls.body_permitted?
- @body = nil
- end
-
- if @body.nil?
- self['Content-Length'] = "0"
- else
- self['Content-Length'] = body.size.to_s
- end
- end
-
- # Returns true if code is 2xx,
- # false otherwise.
- def success?
- code.in?(200..299)
- end
-
- def [](key)
- headers[key]
- end
-
- def []=(key, value)
- headers[key] = value
- end
-
- # Returns true if the other is a Response with an equal body, equal message
- # and equal headers. Otherwise it returns false.
- def ==(other)
- if (other.is_a?(Response))
- other.body == body && other.message == message && other.headers == headers
- else
- false
- end
- end
- end
-
- class Connection
- private
- silence_warnings do
- def http
- @http ||= HttpMock.new(@site)
- end
- end
- end
-end
diff --git a/activeresource/lib/active_resource/log_subscriber.rb b/activeresource/lib/active_resource/log_subscriber.rb
deleted file mode 100644
index 9e52baf36d..0000000000
--- a/activeresource/lib/active_resource/log_subscriber.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module ActiveResource
- class LogSubscriber < ActiveSupport::LogSubscriber
- def request(event)
- result = event.payload[:result]
- info "#{event.payload[:method].to_s.upcase} #{event.payload[:request_uri]}"
- info "--> %d %s %d (%.1fms)" % [result.code, result.message, result.body.to_s.length, event.duration]
- end
-
- def logger
- ActiveResource::Base.logger
- end
- end
-end
-
-ActiveResource::LogSubscriber.attach_to :active_resource \ No newline at end of file
diff --git a/activeresource/lib/active_resource/observing.rb b/activeresource/lib/active_resource/observing.rb
deleted file mode 100644
index 1bfceb8dc8..0000000000
--- a/activeresource/lib/active_resource/observing.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-module ActiveResource
- module Observing
- extend ActiveSupport::Concern
- include ActiveModel::Observing
-
- included do
- %w( create save update destroy ).each do |method|
- # def create_with_notifications(*args, &block)
- # notify_observers(:before_create)
- # if result = create_without_notifications(*args, &block)
- # notify_observers(:after_create)
- # end
- # result
- # end
- # alias_method_chain(create, :notifications)
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{method}_with_notifications(*args, &block)
- notify_observers(:before_#{method})
- if result = #{method}_without_notifications(*args, &block)
- notify_observers(:after_#{method})
- end
- result
- end
- EOS
- alias_method_chain(method, :notifications)
- end
- end
- end
-end
diff --git a/activeresource/lib/active_resource/railtie.rb b/activeresource/lib/active_resource/railtie.rb
deleted file mode 100644
index 60f6f88311..0000000000
--- a/activeresource/lib/active_resource/railtie.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-require "active_resource"
-require "rails"
-
-module ActiveResource
- class Railtie < Rails::Railtie
- config.active_resource = ActiveSupport::OrderedOptions.new
-
- initializer "active_resource.set_configs" do |app|
- app.config.active_resource.each do |k,v|
- ActiveResource::Base.send "#{k}=", v
- end
- end
- end
-end \ No newline at end of file
diff --git a/activeresource/lib/active_resource/schema.rb b/activeresource/lib/active_resource/schema.rb
deleted file mode 100644
index 5957969aa2..0000000000
--- a/activeresource/lib/active_resource/schema.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'active_resource/exceptions'
-
-module ActiveResource # :nodoc:
- class Schema # :nodoc:
- # attributes can be known to be one of these types. They are easy to
- # cast to/from.
- KNOWN_ATTRIBUTE_TYPES = %w( string text integer float decimal datetime timestamp time date binary boolean )
-
- # An array of attribute definitions, representing the attributes that
- # have been defined.
- attr_accessor :attrs
-
- # The internals of an Active Resource Schema are very simple -
- # unlike an Active Record TableDefinition (on which it is based).
- # It provides a set of convenience methods for people to define their
- # schema using the syntax:
- # schema do
- # string :foo
- # integer :bar
- # end
- #
- # The schema stores the name and type of each attribute. That is then
- # read out by the schema method to populate the schema of the actual
- # resource.
- def initialize
- @attrs = {}
- end
-
- def attribute(name, type, options = {})
- raise ArgumentError, "Unknown Attribute type: #{type.inspect} for key: #{name.inspect}" unless type.nil? || Schema::KNOWN_ATTRIBUTE_TYPES.include?(type.to_s)
-
- the_type = type.to_s
- # TODO: add defaults
- #the_attr = [type.to_s]
- #the_attr << options[:default] if options.has_key? :default
- @attrs[name.to_s] = the_type
- self
- end
-
- # The following are the attribute types supported by Active Resource
- # migrations.
- KNOWN_ATTRIBUTE_TYPES.each do |attr_type|
- # def string(*args)
- # options = args.extract_options!
- # attr_names = args
- #
- # attr_names.each { |name| attribute(name, 'string', options) }
- # end
- class_eval <<-EOV, __FILE__, __LINE__ + 1
- def #{attr_type.to_s}(*args)
- options = args.extract_options!
- attr_names = args
-
- attr_names.each { |name| attribute(name, '#{attr_type}', options) }
- end
- EOV
- end
- end
-end
diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb
deleted file mode 100644
index ca265d053e..0000000000
--- a/activeresource/lib/active_resource/validations.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'active_support/core_ext/array/wrap'
-require 'active_support/core_ext/object/blank'
-
-module ActiveResource
- class ResourceInvalid < ClientError #:nodoc:
- end
-
- # Active Resource validation is reported to and from this object, which is used by Base#save
- # to determine whether the object in a valid state to be saved. See usage example in Validations.
- class Errors < ActiveModel::Errors
- # Grabs errors from an array of messages (like ActiveRecord::Validations).
- # The second parameter directs the errors cache to be cleared (default)
- # or not (by passing true).
- def from_array(messages, save_cache = false)
- clear unless save_cache
- humanized_attributes = Hash[@base.attributes.keys.map { |attr_name| [attr_name.humanize, attr_name] }]
- messages.each do |message|
- attr_message = humanized_attributes.keys.detect do |attr_name|
- if message[0, attr_name.size + 1] == "#{attr_name} "
- add humanized_attributes[attr_name], message[(attr_name.size + 1)..-1]
- end
- end
-
- self[:base] << message if attr_message.nil?
- end
- end
-
- # Grabs errors from a json response.
- def from_json(json, save_cache = false)
- array = Array.wrap(ActiveSupport::JSON.decode(json)['errors']) rescue []
- from_array array, save_cache
- end
-
- # Grabs errors from an XML response.
- def from_xml(xml, save_cache = false)
- array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
- from_array array, save_cache
- end
- end
-
- # Module to support validation and errors with Active Resource objects. The module overrides
- # Base#save to rescue ActiveResource::ResourceInvalid exceptions and parse the errors returned
- # in the web service response. The module also adds an +errors+ collection that mimics the interface
- # of the errors provided by ActiveRecord::Errors.
- #
- # ==== Example
- #
- # Consider a Person resource on the server requiring both a +first_name+ and a +last_name+ with a
- # <tt>validates_presence_of :first_name, :last_name</tt> declaration in the model:
- #
- # person = Person.new(:first_name => "Jim", :last_name => "")
- # person.save # => false (server returns an HTTP 422 status code and errors)
- # person.valid? # => false
- # person.errors.empty? # => false
- # person.errors.count # => 1
- # person.errors.full_messages # => ["Last name can't be empty"]
- # person.errors[:last_name] # => ["can't be empty"]
- # person.last_name = "Halpert"
- # person.save # => true (and person is now saved to the remote service)
- #
- module Validations
- extend ActiveSupport::Concern
- include ActiveModel::Validations
-
- included do
- alias_method_chain :save, :validation
- end
-
- # Validate a resource and save (POST) it to the remote web service.
- # If any local validations fail - the save (POST) will not be attempted.
- def save_with_validation(options={})
- perform_validation = options[:validate] != false
-
- # clear the remote validations so they don't interfere with the local
- # ones. Otherwise we get an endless loop and can never change the
- # fields so as to make the resource valid.
- @remote_errors = nil
- if perform_validation && valid? || !perform_validation
- save_without_validation
- true
- else
- false
- end
- rescue ResourceInvalid => error
- # cache the remote errors because every call to <tt>valid?</tt> clears
- # all errors. We must keep a copy to add these back after local
- # validations.
- @remote_errors = error
- load_remote_errors(@remote_errors, true)
- false
- end
-
-
- # Loads the set of remote errors into the object's Errors based on the
- # content-type of the error-block received.
- def load_remote_errors(remote_errors, save_cache = false ) #:nodoc:
- case self.class.format
- when ActiveResource::Formats[:xml]
- errors.from_xml(remote_errors.response.body, save_cache)
- when ActiveResource::Formats[:json]
- errors.from_json(remote_errors.response.body, save_cache)
- end
- end
-
- # Checks for errors on an object (i.e., is resource.errors empty?).
- #
- # Runs all the specified local validations and returns true if no errors
- # were added, otherwise false.
- # Runs local validations (eg those on your Active Resource model), and
- # also any errors returned from the remote system the last time we
- # saved.
- # Remote errors can only be cleared by trying to re-save the resource.
- #
- # ==== Examples
- # my_person = Person.create(params[:person])
- # my_person.valid?
- # # => true
- #
- # my_person.errors.add('login', 'can not be empty') if my_person.login == ''
- # my_person.valid?
- # # => false
- #
- def valid?
- super
- load_remote_errors(@remote_errors, true) if defined?(@remote_errors) && @remote_errors.present?
- errors.empty?
- end
-
- # Returns the Errors object that holds all information about attribute error messages.
- def errors
- @errors ||= Errors.new(self)
- end
- end
-end
diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb
deleted file mode 100644
index d53374b261..0000000000
--- a/activeresource/lib/active_resource/version.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module ActiveResource
- module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
- TINY = 0
- PRE = "beta"
-
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
- end
-end
diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb
deleted file mode 100644
index 9c1e9a526d..0000000000
--- a/activeresource/test/abstract_unit.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-require File.expand_path('../../../load_paths', __FILE__)
-
-lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
-$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
-
-require 'test/unit'
-require 'active_resource'
-require 'active_support'
-require 'active_support/test_case'
-
-require 'setter_trap'
-
-require 'logger'
-ActiveResource::Base.logger = Logger.new("#{File.dirname(__FILE__)}/debug.log")
-
-def setup_response
- matz_hash = { 'person' => { :id => 1, :name => 'Matz' } }
-
- @default_request_headers = { 'Content-Type' => 'application/json' }
- @matz = matz_hash.to_json
- @matz_xml = matz_hash.to_xml
- @david = { :person => { :id => 2, :name => 'David' } }.to_json
- @greg = { :person => { :id => 3, :name => 'Greg' } }.to_json
- @addy = { :address => { :id => 1, :street => '12345 Street', :country => 'Australia' } }.to_json
- @rick = { :person => { :name => "Rick", :age => 25 } }.to_json
- @joe = { :person => { :id => 6, :name => 'Joe', :likes_hats => true }}.to_json
- @people = { :people => [ { :person => { :id => 1, :name => 'Matz' } }, { :person => { :id => 2, :name => 'David' } }] }.to_json
- @people_david = { :people => [ { :person => { :id => 2, :name => 'David' } }] }.to_json
- @addresses = { :addresses => [{ :address => { :id => 1, :street => '12345 Street', :country => 'Australia' } }] }.to_json
-
- # - deep nested resource -
- # - Luis (Customer)
- # - JK (Customer::Friend)
- # - Mateo (Customer::Friend::Brother)
- # - Edith (Customer::Friend::Brother::Child)
- # - Martha (Customer::Friend::Brother::Child)
- # - Felipe (Customer::Friend::Brother)
- # - Bryan (Customer::Friend::Brother::Child)
- # - Luke (Customer::Friend::Brother::Child)
- # - Eduardo (Customer::Friend)
- # - Sebas (Customer::Friend::Brother)
- # - Andres (Customer::Friend::Brother::Child)
- # - Jorge (Customer::Friend::Brother::Child)
- # - Elsa (Customer::Friend::Brother)
- # - Natacha (Customer::Friend::Brother::Child)
- # - Milena (Customer::Friend::Brother)
- #
- @luis = {
- :customer => {
- :id => 1,
- :name => 'Luis',
- :friends => [{
- :name => 'JK',
- :brothers => [
- {
- :name => 'Mateo',
- :children => [{ :name => 'Edith' },{ :name => 'Martha' }]
- }, {
- :name => 'Felipe',
- :children => [{ :name => 'Bryan' },{ :name => 'Luke' }]
- }
- ]
- }, {
- :name => 'Eduardo',
- :brothers => [
- {
- :name => 'Sebas',
- :children => [{ :name => 'Andres' },{ :name => 'Jorge' }]
- }, {
- :name => 'Elsa',
- :children => [{ :name => 'Natacha' }]
- }, {
- :name => 'Milena',
- :children => []
- }
- ]
- }]
- }
- }.to_json
- # - resource with yaml array of strings; for ARs using serialize :bar, Array
- @marty = <<-eof.strip
- <?xml version=\"1.0\" encoding=\"UTF-8\"?>
- <person>
- <id type=\"integer\">5</id>
- <name>Marty</name>
- <colors type=\"yaml\">---
- - \"red\"
- - \"green\"
- - \"blue\"
- </colors>
- </person>
- eof
-
- @startup_sound = {
- :sound => {
- :name => "Mac Startup Sound", :author => { :name => "Jim Reekes" }
- }
- }.to_json
-
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.json", {}, @matz
- mock.get "/people/1.xml", {}, @matz_xml
- mock.get "/people/2.xml", {}, @david
- mock.get "/people/5.xml", {}, @marty
- mock.get "/people/Greg.json", {}, @greg
- mock.get "/people/6.json", {}, @joe
- mock.get "/people/4.json", { 'key' => 'value' }, nil, 404
- mock.put "/people/1.json", {}, nil, 204
- mock.delete "/people/1.json", {}, nil, 200
- mock.delete "/people/2.xml", {}, nil, 400
- mock.get "/people/99.json", {}, nil, 404
- mock.post "/people.json", {}, @rick, 201, 'Location' => '/people/5.xml'
- mock.get "/people.json", {}, @people
- mock.get "/people/1/addresses.json", {}, @addresses
- mock.get "/people/1/addresses/1.json", {}, @addy
- mock.get "/people/1/addresses/2.xml", {}, nil, 404
- mock.get "/people/2/addresses.json", {}, nil, 404
- mock.get "/people/2/addresses/1.xml", {}, nil, 404
- mock.get "/people/Greg/addresses/1.json", {}, @addy
- mock.put "/people/1/addresses/1.json", {}, nil, 204
- mock.delete "/people/1/addresses/1.json", {}, nil, 200
- mock.post "/people/1/addresses.json", {}, nil, 201, 'Location' => '/people/1/addresses/5'
- mock.get "/people/1/addresses/99.json", {}, nil, 404
- mock.get "/people//addresses.xml", {}, nil, 404
- mock.get "/people//addresses/1.xml", {}, nil, 404
- mock.put "/people//addresses/1.xml", {}, nil, 404
- mock.delete "/people//addresses/1.xml", {}, nil, 404
- mock.post "/people//addresses.xml", {}, nil, 404
- mock.head "/people/1.json", {}, nil, 200
- mock.head "/people/Greg.json", {}, nil, 200
- mock.head "/people/99.json", {}, nil, 404
- mock.head "/people/1/addresses/1.json", {}, nil, 200
- mock.head "/people/1/addresses/2.json", {}, nil, 404
- mock.head "/people/2/addresses/1.json", {}, nil, 404
- mock.head "/people/Greg/addresses/1.json", {}, nil, 200
- # customer
- mock.get "/customers/1.json", {}, @luis
- # sound
- mock.get "/sounds/1.json", {}, @startup_sound
- end
-
- Person.user = nil
- Person.password = nil
-end
diff --git a/activeresource/test/cases/authorization_test.rb b/activeresource/test/cases/authorization_test.rb
deleted file mode 100644
index 17cd9b30fc..0000000000
--- a/activeresource/test/cases/authorization_test.rb
+++ /dev/null
@@ -1,254 +0,0 @@
-require 'abstract_unit'
-
-class AuthorizationTest < Test::Unit::TestCase
- Response = Struct.new(:code)
-
- def setup
- @conn = ActiveResource::Connection.new('http://localhost')
- @matz = { :person => { :id => 1, :name => 'Matz' } }.to_json
- @david = { :person => { :id => 2, :name => 'David' } }.to_json
- @authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost")
- @basic_authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' }
-
- @nonce = "MTI0OTUxMzc4NzpjYWI3NDM3NDNmY2JmODU4ZjQ2ZjcwNGZkMTJiMjE0NA=="
-
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/2.json", @basic_authorization_request_header, @david
- mock.get "/people/1.json", @basic_authorization_request_header, nil, 401, { 'WWW-Authenticate' => 'i_should_be_ignored' }
- mock.put "/people/2.json", @basic_authorization_request_header, nil, 204
- mock.delete "/people/2.json", @basic_authorization_request_header, nil, 200
- mock.post "/people/2/addresses.json", @basic_authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5'
- mock.head "/people/2.json", @basic_authorization_request_header, nil, 200
-
- mock.get "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
- mock.get "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "c064d5ba8891a25290c76c8c7d31fb7b") }, @david, 200
- mock.get "/people/1.json", { 'Authorization' => request_digest_auth_header("/people/1.json", "f9c0b594257bb8422af4abd429c5bb70") }, @matz, 200
-
- mock.put "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "50a685d814f94665b9d160fbbaa3958a") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
- mock.put "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "5a75cde841122d8e0f20f8fd1f98a743") }, nil, 204
-
- mock.delete "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "846f799107eab5ca4285b909ee299a33") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
- mock.delete "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "9f5b155224edbbb69fd99d8ce094681e") }, nil, 200
-
- mock.post "/people/2/addresses.json", { 'Authorization' => blank_digest_auth_header("/people/2/addresses.json", "6984d405ff3d9ed07bbf747dcf16afb0") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
- mock.post "/people/2/addresses.json", { 'Authorization' => request_digest_auth_header("/people/2/addresses.json", "4bda6a28dbf930b5af9244073623bd04") }, nil, 201, 'Location' => '/people/1/addresses/5'
-
- mock.head "/people/2.json", { 'Authorization' => blank_digest_auth_header("/people/2.json", "15e5ed84ba5c4cfcd5c98a36c2e4f421") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header }
- mock.head "/people/2.json", { 'Authorization' => request_digest_auth_header("/people/2.json", "d4c6d2bcc8717abb2e2ccb8c49ee6a91") }, nil, 200
- end
-
- # Make client nonce deterministic
- class << @authenticated_conn
- private
-
- def client_nonce
- 'i-am-a-client-nonce'
- end
- end
- end
-
- def test_authorization_header
- authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
- assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization']
- authorization = authorization_header["Authorization"].to_s.split
-
- assert_equal "Basic", authorization[0]
- assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
- end
-
- def test_authorization_header_with_username_but_no_password
- @conn = ActiveResource::Connection.new("http://david:@localhost")
- authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
- authorization = authorization_header["Authorization"].to_s.split
-
- assert_equal "Basic", authorization[0]
- assert_equal ["david"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
- end
-
- def test_authorization_header_with_password_but_no_username
- @conn = ActiveResource::Connection.new("http://:test123@localhost")
- authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
- authorization = authorization_header["Authorization"].to_s.split
-
- assert_equal "Basic", authorization[0]
- assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
- end
-
- def test_authorization_header_with_decoded_credentials_from_url
- @conn = ActiveResource::Connection.new("http://my%40email.com:%31%32%33@localhost")
- authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
- authorization = authorization_header["Authorization"].to_s.split
-
- assert_equal "Basic", authorization[0]
- assert_equal ["my@email.com", "123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
- end
-
- def test_authorization_header_explicitly_setting_username_and_password
- @authenticated_conn = ActiveResource::Connection.new("http://@localhost")
- @authenticated_conn.user = 'david'
- @authenticated_conn.password = 'test123'
- authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
- assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization']
- authorization = authorization_header["Authorization"].to_s.split
-
- assert_equal "Basic", authorization[0]
- assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
- end
-
- def test_authorization_header_explicitly_setting_username_but_no_password
- @conn = ActiveResource::Connection.new("http://@localhost")
- @conn.user = "david"
- authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
- authorization = authorization_header["Authorization"].to_s.split
-
- assert_equal "Basic", authorization[0]
- assert_equal ["david"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
- end
-
- def test_authorization_header_explicitly_setting_password_but_no_username
- @conn = ActiveResource::Connection.new("http://@localhost")
- @conn.password = "test123"
- authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
- authorization = authorization_header["Authorization"].to_s.split
-
- assert_equal "Basic", authorization[0]
- assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
- end
-
- def test_authorization_header_if_credentials_supplied_and_auth_type_is_basic
- @authenticated_conn.auth_type = :basic
- authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
- assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization']
- authorization = authorization_header["Authorization"].to_s.split
-
- assert_equal "Basic", authorization[0]
- assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
- end
-
- def test_authorization_header_if_credentials_supplied_and_auth_type_is_digest
- @authenticated_conn.auth_type = :digest
- authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json'))
- assert_equal blank_digest_auth_header("/people/2.json", "fad396f6a34aeba28e28b9b96ddbb671"), authorization_header['Authorization']
- end
-
- def test_authorization_header_with_query_string_if_auth_type_is_digest
- @authenticated_conn.auth_type = :digest
- authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.json?only=name'))
- assert_equal blank_digest_auth_header("/people/2.json?only=name", "f8457b0b5d21b6b80737a386217afb24"), authorization_header['Authorization']
- end
-
- def test_get
- david = decode(@authenticated_conn.get("/people/2.json"))
- assert_equal "David", david["name"]
- end
-
- def test_post
- response = @authenticated_conn.post("/people/2/addresses.json")
- assert_equal "/people/1/addresses/5", response["Location"]
- end
-
- def test_put
- response = @authenticated_conn.put("/people/2.json")
- assert_equal 204, response.code
- end
-
- def test_delete
- response = @authenticated_conn.delete("/people/2.json")
- assert_equal 200, response.code
- end
-
- def test_head
- response = @authenticated_conn.head("/people/2.json")
- assert_equal 200, response.code
- end
-
- def test_get_with_digest_auth_handles_initial_401_response_and_retries
- @authenticated_conn.auth_type = :digest
- response = @authenticated_conn.get("/people/2.json")
- assert_equal "David", decode(response)["name"]
- end
-
- def test_post_with_digest_auth_handles_initial_401_response_and_retries
- @authenticated_conn.auth_type = :digest
- response = @authenticated_conn.post("/people/2/addresses.json")
- assert_equal "/people/1/addresses/5", response["Location"]
- assert_equal 201, response.code
- end
-
- def test_put_with_digest_auth_handles_initial_401_response_and_retries
- @authenticated_conn.auth_type = :digest
- response = @authenticated_conn.put("/people/2.json")
- assert_equal 204, response.code
- end
-
- def test_delete_with_digest_auth_handles_initial_401_response_and_retries
- @authenticated_conn.auth_type = :digest
- response = @authenticated_conn.delete("/people/2.json")
- assert_equal 200, response.code
- end
-
- def test_head_with_digest_auth_handles_initial_401_response_and_retries
- @authenticated_conn.auth_type = :digest
- response = @authenticated_conn.head("/people/2.json")
- assert_equal 200, response.code
- end
-
- def test_get_with_digest_auth_caches_nonce
- @authenticated_conn.auth_type = :digest
- response = @authenticated_conn.get("/people/2.json")
- assert_equal "David", decode(response)["name"]
-
- # There is no mock for this request with a non-cached nonce.
- response = @authenticated_conn.get("/people/1.json")
- assert_equal "Matz", decode(response)["name"]
- end
-
- def test_retry_on_401_only_happens_with_digest_auth
- assert_raise(ActiveResource::UnauthorizedAccess) { @authenticated_conn.get("/people/1.json") }
- assert_equal "", @authenticated_conn.send(:response_auth_header)
- end
-
- def test_raises_invalid_request_on_unauthorized_requests
- assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.json") }
- end
-
- def test_raises_invalid_request_on_unauthorized_requests_with_digest_auth
- @conn.auth_type = :digest
- assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.json") }
- assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.json") }
- end
-
- def test_client_nonce_is_not_nil
- assert_not_nil ActiveResource::Connection.new("http://david:test123@localhost").send(:client_nonce)
- end
-
- protected
- def assert_response_raises(klass, code)
- assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
- @conn.__send__(:handle_response, Response.new(code))
- end
- end
-
- def blank_digest_auth_header(uri, response)
- %Q(Digest username="david", realm="", qop="", uri="#{uri}", nonce="", nc="0", cnonce="i-am-a-client-nonce", opaque="", response="#{response}")
- end
-
- def request_digest_auth_header(uri, response)
- %Q(Digest username="david", realm="RailsTestApp", qop="auth", uri="#{uri}", nonce="#{@nonce}", nc="0", cnonce="i-am-a-client-nonce", opaque="ef6dfb078ba22298d366f99567814ffb", response="#{response}")
- end
-
- def response_digest_auth_header
- %Q(Digest realm="RailsTestApp", qop="auth", algorithm=MD5, nonce="#{@nonce}", opaque="ef6dfb078ba22298d366f99567814ffb")
- end
-
- def decode(response)
- @authenticated_conn.format.decode(response.body)
- end
-end
diff --git a/activeresource/test/cases/base/custom_methods_test.rb b/activeresource/test/cases/base/custom_methods_test.rb
deleted file mode 100644
index 3eaa9b1c5b..0000000000
--- a/activeresource/test/cases/base/custom_methods_test.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-require 'abstract_unit'
-require 'fixtures/person'
-require 'fixtures/street_address'
-require 'active_support/core_ext/hash/conversions'
-
-class CustomMethodsTest < Test::Unit::TestCase
- def setup
- @matz = { :person => { :id => 1, :name => 'Matz' } }.to_json
- @matz_deep = { :person => { :id => 1, :name => 'Matz', :other => 'other' } }.to_json
- @matz_array = { :people => [{ :person => { :id => 1, :name => 'Matz' } }] }.to_json
- @ryan = { :person => { :name => 'Ryan' } }.to_json
- @addy = { :address => { :id => 1, :street => '12345 Street' } }.to_json
- @addy_deep = { :address => { :id => 1, :street => '12345 Street', :zip => "27519" } }.to_json
-
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.json", {}, @matz
- mock.get "/people/1/shallow.json", {}, @matz
- mock.get "/people/1/deep.json", {}, @matz_deep
- mock.get "/people/retrieve.json?name=Matz", {}, @matz_array
- mock.get "/people/managers.json", {}, @matz_array
- mock.post "/people/hire.json?name=Matz", {}, nil, 201
- mock.put "/people/1/promote.json?position=Manager", {}, nil, 204
- mock.put "/people/promote.json?name=Matz", {}, nil, 204, {}
- mock.put "/people/sort.json?by=name", {}, nil, 204
- mock.delete "/people/deactivate.json?name=Matz", {}, nil, 200
- mock.delete "/people/1/deactivate.json", {}, nil, 200
- mock.post "/people/new/register.json", {}, @ryan, 201, 'Location' => '/people/5.json'
- mock.post "/people/1/register.json", {}, @matz, 201
- mock.get "/people/1/addresses/1.json", {}, @addy
- mock.get "/people/1/addresses/1/deep.json", {}, @addy_deep
- mock.put "/people/1/addresses/1/normalize_phone.json?locale=US", {}, nil, 204
- mock.put "/people/1/addresses/sort.json?by=name", {}, nil, 204
- mock.post "/people/1/addresses/new/link.json", {}, { :address => { :street => '12345 Street' } }.to_json, 201, 'Location' => '/people/1/addresses/2.json'
- end
-
- Person.user = nil
- Person.password = nil
- end
-
- def teardown
- ActiveResource::HttpMock.reset!
- end
-
- def test_custom_collection_method
- # GET
- assert_equal([{ "id" => 1, "name" => 'Matz' }], Person.get(:retrieve, :name => 'Matz'))
-
- # POST
- assert_equal(ActiveResource::Response.new("", 201, {}), Person.post(:hire, :name => 'Matz'))
-
- # PUT
- assert_equal ActiveResource::Response.new("", 204, {}),
- Person.put(:promote, {:name => 'Matz'}, 'atestbody')
- assert_equal ActiveResource::Response.new("", 204, {}), Person.put(:sort, :by => 'name')
-
- # DELETE
- Person.delete :deactivate, :name => 'Matz'
-
- # Nested resource
- assert_equal ActiveResource::Response.new("", 204, {}), StreetAddress.put(:sort, :person_id => 1, :by => 'name')
- end
-
- def test_custom_element_method
- # Test GET against an element URL
- assert_equal Person.find(1).get(:shallow), {"id" => 1, "name" => 'Matz'}
- assert_equal Person.find(1).get(:deep), {"id" => 1, "name" => 'Matz', "other" => 'other'}
-
- # Test PUT against an element URL
- assert_equal ActiveResource::Response.new("", 204, {}), Person.find(1).put(:promote, {:position => 'Manager'}, 'body')
-
- # Test DELETE against an element URL
- assert_equal ActiveResource::Response.new("", 200, {}), Person.find(1).delete(:deactivate)
-
- # With nested resources
- assert_equal StreetAddress.find(1, :params => { :person_id => 1 }).get(:deep),
- { "id" => 1, "street" => '12345 Street', "zip" => "27519" }
- assert_equal ActiveResource::Response.new("", 204, {}),
- StreetAddress.find(1, :params => { :person_id => 1 }).put(:normalize_phone, :locale => 'US')
- end
-
- def test_custom_new_element_method
- # Test POST against a new element URL
- ryan = Person.new(:name => 'Ryan')
- assert_equal ActiveResource::Response.new(@ryan, 201, { 'Location' => '/people/5.json' }), ryan.post(:register)
- expected_request = ActiveResource::Request.new(:post, '/people/new/register.json', @ryan)
- assert_equal expected_request.body, ActiveResource::HttpMock.requests.first.body
-
- # Test POST against a nested collection URL
- addy = StreetAddress.new(:street => '123 Test Dr.', :person_id => 1)
- assert_equal ActiveResource::Response.new({ :address => { :street => '12345 Street' } }.to_json,
- 201, { 'Location' => '/people/1/addresses/2.json' }),
- addy.post(:link)
-
- matz = Person.find(1)
- assert_equal ActiveResource::Response.new(@matz, 201), matz.post(:register)
- end
-
- def test_find_custom_resources
- assert_equal 'Matz', Person.find(:all, :from => :managers).first.name
- end
-end
diff --git a/activeresource/test/cases/base/equality_test.rb b/activeresource/test/cases/base/equality_test.rb
deleted file mode 100644
index 84f1a7b998..0000000000
--- a/activeresource/test/cases/base/equality_test.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-require 'abstract_unit'
-require "fixtures/person"
-require "fixtures/street_address"
-
-class BaseEqualityTest < Test::Unit::TestCase
- def setup
- @new = Person.new
- @one = Person.new(:id => 1)
- @two = Person.new(:id => 2)
- @street = StreetAddress.new(:id => 2)
- end
-
- def test_should_equal_self
- assert @new == @new, '@new == @new'
- assert @one == @one, '@one == @one'
- end
-
- def test_shouldnt_equal_new_resource
- assert @new != @one, '@new != @one'
- assert @one != @new, '@one != @new'
- end
-
- def test_shouldnt_equal_different_class
- assert @two != @street, 'person != street_address with same id'
- assert @street != @two, 'street_address != person with same id'
- end
-
- def test_eql_should_alias_equals_operator
- assert_equal @new == @new, @new.eql?(@new)
- assert_equal @new == @one, @new.eql?(@one)
-
- assert_equal @one == @one, @one.eql?(@one)
- assert_equal @one == @new, @one.eql?(@new)
-
- assert_equal @one == @street, @one.eql?(@street)
- end
-
- def test_hash_should_be_id_hash
- [@new, @one, @two, @street].each do |resource|
- assert_equal resource.id.hash, resource.hash
- end
- end
-
- def test_with_prefix_options
- assert_equal @one == @one, @one.eql?(@one)
- assert_equal @one == @one.dup, @one.eql?(@one.dup)
- new_one = @one.dup
- new_one.prefix_options = {:foo => 'bar'}
- assert_not_equal @one, new_one
- end
-
-end
diff --git a/activeresource/test/cases/base/load_test.rb b/activeresource/test/cases/base/load_test.rb
deleted file mode 100644
index 784e7dd036..0000000000
--- a/activeresource/test/cases/base/load_test.rb
+++ /dev/null
@@ -1,199 +0,0 @@
-require 'abstract_unit'
-require "fixtures/person"
-require "fixtures/street_address"
-require 'active_support/core_ext/hash/conversions'
-
-module Highrise
- class Note < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- end
-
- class Comment < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- end
-
- module Deeply
- module Nested
- class Note < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- end
-
- class Comment < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- end
-
- module TestDifferentLevels
- class Note < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- end
- end
- end
- end
-end
-
-
-class BaseLoadTest < Test::Unit::TestCase
- def setup
- @matz = { :id => 1, :name => 'Matz' }
-
- @first_address = { :address => { :id => 1, :street => '12345 Street' } }
- @addresses = [@first_address, { :address => { :id => 2, :street => '67890 Street' } }]
- @addresses_from_json = { :street_addresses => @addresses }
- @addresses_from_json_single = { :street_addresses => [ @first_address ] }
-
- @deep = { :id => 1, :street => {
- :id => 1, :state => { :id => 1, :name => 'Oregon',
- :notable_rivers => [
- { :id => 1, :name => 'Willamette' },
- { :id => 2, :name => 'Columbia', :rafted_by => @matz }],
- :postal_codes => [ 97018, 1234567890 ],
- :dates => [ Time.now ],
- :votes => [ true, false, true ],
- :places => [ "Columbia City", "Unknown" ]}}}
-
-
- # List of books formated as [{timestamp_of_publication => name}, ...]
- @books = {:books => [
- {1009839600 => "Ruby in a Nutshell"},
- {1199142000 => "The Ruby Programming Language"}
- ]}
-
- @books_date = {:books => [
- {Time.at(1009839600) => "Ruby in a Nutshell"},
- {Time.at(1199142000) => "The Ruby Programming Language"}
- ]}
- @person = Person.new
- end
-
- def test_load_hash_with_integers_as_keys
- assert_nothing_raised{@person.load(@books)}
- end
-
- def test_load_hash_with_dates_as_keys
- assert_nothing_raised{@person.load(@books_date)}
- end
-
- def test_load_expects_hash
- assert_raise(ArgumentError) { @person.load nil }
- assert_raise(ArgumentError) { @person.load '<person id="1"/>' }
- end
-
- def test_load_simple_hash
- assert_equal Hash.new, @person.attributes
- assert_equal @matz.stringify_keys, @person.load(@matz).attributes
- end
-
- def test_after_load_attributes_are_accessible
- assert_equal Hash.new, @person.attributes
- assert_equal @matz.stringify_keys, @person.load(@matz).attributes
- assert_equal @matz[:name], @person.attributes['name']
- end
-
- def test_after_load_attributes_are_accessible_via_indifferent_access
- assert_equal Hash.new, @person.attributes
- assert_equal @matz.stringify_keys, @person.load(@matz).attributes
- assert_equal @matz[:name], @person.attributes['name']
- assert_equal @matz[:name], @person.attributes[:name]
- end
-
- def test_load_one_with_existing_resource
- address = @person.load(:street_address => @first_address.values.first).street_address
- assert_kind_of StreetAddress, address
- assert_equal @first_address.values.first.stringify_keys, address.attributes
- end
-
- def test_load_one_with_unknown_resource
- address = silence_warnings { @person.load(@first_address).address }
- assert_kind_of Person::Address, address
- assert_equal @first_address.values.first.stringify_keys, address.attributes
- end
-
- def test_load_collection_with_existing_resource
- addresses = @person.load(@addresses_from_json).street_addresses
- assert_kind_of Array, addresses
- addresses.each { |address| assert_kind_of StreetAddress, address }
- assert_equal @addresses.map { |a| a[:address].stringify_keys }, addresses.map(&:attributes)
- end
-
- def test_load_collection_with_unknown_resource
- Person.__send__(:remove_const, :Address) if Person.const_defined?(:Address)
- assert !Person.const_defined?(:Address), "Address shouldn't exist until autocreated"
- addresses = silence_warnings { @person.load(:addresses => @addresses).addresses }
- assert Person.const_defined?(:Address), "Address should have been autocreated"
- addresses.each { |address| assert_kind_of Person::Address, address }
- assert_equal @addresses.map { |a| a[:address].stringify_keys }, addresses.map(&:attributes)
- end
-
- def test_load_collection_with_single_existing_resource
- addresses = @person.load(@addresses_from_json_single).street_addresses
- assert_kind_of Array, addresses
- addresses.each { |address| assert_kind_of StreetAddress, address }
- assert_equal [ @first_address.values.first ].map(&:stringify_keys), addresses.map(&:attributes)
- end
-
- def test_load_collection_with_single_unknown_resource
- Person.__send__(:remove_const, :Address) if Person.const_defined?(:Address)
- assert !Person.const_defined?(:Address), "Address shouldn't exist until autocreated"
- addresses = silence_warnings { @person.load(:addresses => [ @first_address ]).addresses }
- assert Person.const_defined?(:Address), "Address should have been autocreated"
- addresses.each { |address| assert_kind_of Person::Address, address }
- assert_equal [ @first_address.values.first ].map(&:stringify_keys), addresses.map(&:attributes)
- end
-
- def test_recursively_loaded_collections
- person = @person.load(@deep)
- assert_equal @deep[:id], person.id
-
- street = person.street
- assert_kind_of Person::Street, street
- assert_equal @deep[:street][:id], street.id
-
- state = street.state
- assert_kind_of Person::Street::State, state
- assert_equal @deep[:street][:state][:id], state.id
-
- rivers = state.notable_rivers
- assert_kind_of Array, rivers
- assert_kind_of Person::Street::State::NotableRiver, rivers.first
- assert_equal @deep[:street][:state][:notable_rivers].first[:id], rivers.first.id
- assert_equal @matz[:id], rivers.last.rafted_by.id
-
- postal_codes = state.postal_codes
- assert_kind_of Array, postal_codes
- assert_equal 2, postal_codes.size
- assert_kind_of Fixnum, postal_codes.first
- assert_equal @deep[:street][:state][:postal_codes].first, postal_codes.first
- assert_kind_of Numeric, postal_codes.last
- assert_equal @deep[:street][:state][:postal_codes].last, postal_codes.last
-
- places = state.places
- assert_kind_of Array, places
- assert_kind_of String, places.first
- assert_equal @deep[:street][:state][:places].first, places.first
-
- dates = state.dates
- assert_kind_of Array, dates
- assert_kind_of Time, dates.first
- assert_equal @deep[:street][:state][:dates].first, dates.first
-
- votes = state.votes
- assert_kind_of Array, votes
- assert_kind_of TrueClass, votes.first
- assert_equal @deep[:street][:state][:votes].first, votes.first
- end
-
- def test_nested_collections_within_the_same_namespace
- n = Highrise::Note.new(:comments => [{ :comment => { :name => "1" } }])
- assert_kind_of Highrise::Comment, n.comments.first
- end
-
- def test_nested_collections_within_deeply_nested_namespace
- n = Highrise::Deeply::Nested::Note.new(:comments => [{ :name => "1" }])
- assert_kind_of Highrise::Deeply::Nested::Comment, n.comments.first
- end
-
- def test_nested_collections_in_different_levels_of_namespaces
- n = Highrise::Deeply::Nested::TestDifferentLevels::Note.new(:comments => [{ :name => "1" }])
- assert_kind_of Highrise::Deeply::Nested::Comment, n.comments.first
- end
-end
diff --git a/activeresource/test/cases/base/schema_test.rb b/activeresource/test/cases/base/schema_test.rb
deleted file mode 100644
index d29eaf5fb6..0000000000
--- a/activeresource/test/cases/base/schema_test.rb
+++ /dev/null
@@ -1,411 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/hash/conversions'
-require "fixtures/person"
-require "fixtures/street_address"
-
-########################################################################
-# Testing the schema of your Active Resource models
-########################################################################
-class SchemaTest < ActiveModel::TestCase
- def setup
- setup_response # find me in abstract_unit
- end
-
- def teardown
- Person.schema = nil # hack to stop test bleedthrough...
- end
-
- #####################################################
- # Passing in a schema directly and returning it
- ####
-
- test "schema on a new model should be empty" do
- assert Person.schema.blank?, "should have a blank class schema"
- assert Person.new.schema.blank?, "should have a blank instance schema"
- end
-
- test "schema should only accept a hash" do
- ["blahblah", ['one','two'], [:age, :name], Person.new].each do |bad_schema|
- assert_raises(ArgumentError,"should only accept a hash (or nil), but accepted: #{bad_schema.inspect}") do
- Person.schema = bad_schema
- end
- end
- end
-
- test "schema should accept a simple hash" do
- new_schema = {'age' => 'integer', 'name' => 'string',
- 'height' => 'float', 'bio' => 'text',
- 'weight' => 'decimal', 'photo' => 'binary',
- 'alive' => 'boolean', 'created_at' => 'timestamp',
- 'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
-
-
- assert_nothing_raised { Person.schema = new_schema }
- assert_equal new_schema, Person.schema
- end
-
- test "schema should accept a hash with simple values" do
- new_schema = {'age' => 'integer', 'name' => 'string',
- 'height' => 'float', 'bio' => 'text',
- 'weight' => 'decimal', 'photo' => 'binary',
- 'alive' => 'boolean', 'created_at' => 'timestamp',
- 'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
-
- assert_nothing_raised { Person.schema = new_schema }
- assert_equal new_schema, Person.schema
- end
-
- test "schema should accept all known attribute types as values" do
- # I'd prefer to use here...
- ActiveResource::Schema::KNOWN_ATTRIBUTE_TYPES.each do |the_type|
- assert_nothing_raised("should have accepted #{the_type.inspect}"){ Person.schema = {'my_key' => the_type }}
- end
- end
-
- test "schema should not accept unknown values" do
- bad_values = [ :oogle, :blob, 'thing']
-
- bad_values.each do |bad_value|
- assert_raises(ArgumentError,"should only accept a known attribute type, but accepted: #{bad_value.inspect}") do
- Person.schema = {'key' => bad_value}
- end
- end
- end
-
- test "schema should accept nil and remove the schema" do
- new_schema = {'age' => 'integer', 'name' => 'string',
- 'height' => 'float', 'bio' => 'text',
- 'weight' => 'decimal', 'photo' => 'binary',
- 'alive' => 'boolean', 'created_at' => 'timestamp',
- 'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
-
- assert_nothing_raised { Person.schema = new_schema }
- assert_equal new_schema, Person.schema # sanity check
-
-
- assert_nothing_raised { Person.schema = nil }
- assert_nil Person.schema, "should have nulled out the schema, but still had: #{Person.schema.inspect}"
- end
-
-
- test "schema should be with indifferent access" do
- new_schema = {'age' => 'integer', 'name' => 'string',
- 'height' => 'float', 'bio' => 'text',
- 'weight' => 'decimal', 'photo' => 'binary',
- 'alive' => 'boolean', 'created_at' => 'timestamp',
- 'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
-
- new_schema_syms = new_schema.keys
-
- assert_nothing_raised { Person.schema = new_schema }
- new_schema_syms.each do |col|
- assert Person.new.respond_to?(col.to_s), "should respond to the schema's string key, but failed on: #{col.to_s}"
- assert Person.new.respond_to?(col.to_sym), "should respond to the schema's symbol key, but failed on: #{col.to_sym}"
- end
- end
-
-
- test "schema on a fetched resource should return all the attributes of that model instance" do
- p = Person.find(1)
- s = p.schema
-
- assert s.present?, "should have found a non-empty schema!"
-
- p.attributes.each do |the_attr, val|
- assert s.has_key?(the_attr), "should have found attr: #{the_attr} in schema, but only had: #{s.inspect}"
- end
- end
-
- test "with two instances, default schema should match the attributes of the individual instances - even if they differ" do
- matz = Person.find(1)
- rick = Person.find(6)
-
- m_attrs = matz.attributes.keys.sort
- r_attrs = rick.attributes.keys.sort
-
- assert_not_equal m_attrs, r_attrs, "should have different attributes on each model"
-
- assert_not_equal matz.schema, rick.schema, "should have had different schemas too"
- end
-
- test "defining a schema should return it when asked" do
- assert Person.schema.blank?, "should have a blank class schema"
- new_schema = {'age' => 'integer', 'name' => 'string',
- 'height' => 'float', 'bio' => 'text',
- 'weight' => 'decimal', 'photo' => 'binary',
- 'alive' => 'boolean', 'created_at' => 'timestamp',
- 'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
-
- assert_nothing_raised {
- Person.schema = new_schema
- assert_equal new_schema, Person.schema, "should have saved the schema on the class"
- assert_equal new_schema, Person.new.schema, "should have made the schema available to every instance"
- }
- end
-
- test "defining a schema, then fetching a model should still match the defined schema" do
- # sanity checks
- assert Person.schema.blank?, "should have a blank class schema"
- new_schema = {'age' => 'integer', 'name' => 'string',
- 'height' => 'float', 'bio' => 'text',
- 'weight' => 'decimal', 'photo' => 'binary',
- 'alive' => 'boolean', 'created_at' => 'timestamp',
- 'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
-
- matz = Person.find(1)
- assert !matz.schema.blank?, "should have some sort of schema on an instance variable"
- assert_not_equal new_schema, matz.schema, "should not have the class-level schema until it's been added to the class!"
-
- assert_nothing_raised {
- Person.schema = new_schema
- assert_equal new_schema, matz.schema, "class-level schema should override instance-level schema"
- }
- end
-
-
- #####################################################
- # Using the schema syntax
- ####
-
- test "should be able to use schema" do
- assert_respond_to Person, :schema, "should at least respond to the schema method"
-
- assert_nothing_raised("Should allow the schema to take a block") do
- Person.schema { }
- end
- end
-
- test "schema definition should store and return attribute set" do
- assert_nothing_raised do
- s = nil
- Person.schema do
- s = self
- attribute :foo, :string
- end
- assert_respond_to s, :attrs, "should return attributes in theory"
- assert_equal({'foo' => 'string' }, s.attrs, "should return attributes in practice")
- end
- end
-
- test "should be able to add attributes through schema" do
- assert_nothing_raised do
- s = nil
- Person.schema do
- s = self
- attribute('foo', 'string')
- end
- assert s.attrs.has_key?('foo'), "should have saved the attribute name"
- assert_equal 'string', s.attrs['foo'], "should have saved the attribute type"
- end
- end
-
- test "should convert symbol attributes to strings" do
- assert_nothing_raised do
- s = nil
- Person.schema do
- attribute(:foo, :integer)
- s = self
- end
-
- assert s.attrs.has_key?('foo'), "should have saved the attribute name as a string"
- assert_equal 'integer', s.attrs['foo'], "should have saved the attribute type as a string"
- end
- end
-
- test "should be able to add all known attribute types" do
- assert_nothing_raised do
- ActiveResource::Schema::KNOWN_ATTRIBUTE_TYPES.each do |the_type|
- s = nil
- Person.schema do
- s = self
- attribute('foo', the_type)
- end
- assert s.attrs.has_key?('foo'), "should have saved the attribute name"
- assert_equal the_type.to_s, s.attrs['foo'], "should have saved the attribute type of: #{the_type}"
- end
- end
- end
-
- test "attributes should not accept unknown values" do
- bad_values = [ :oogle, :blob, 'thing']
-
- bad_values.each do |bad_value|
- s = nil
- assert_raises(ArgumentError,"should only accept a known attribute type, but accepted: #{bad_value.inspect}") do
- Person.schema do
- s = self
- attribute 'key', bad_value
- end
- end
- assert !self.respond_to?(bad_value), "should only respond to a known attribute type, but accepted: #{bad_value.inspect}"
- assert_raises(NoMethodError,"should only have methods for known attribute types, but accepted: #{bad_value.inspect}") do
- Person.schema do
- send bad_value, 'key'
- end
- end
- end
- end
-
-
- test "should accept attribute types as the type's name as the method" do
- ActiveResource::Schema::KNOWN_ATTRIBUTE_TYPES.each do |the_type|
- s = nil
- Person.schema do
- s = self
- send(the_type,'foo')
- end
- assert s.attrs.has_key?('foo'), "should now have saved the attribute name"
- assert_equal the_type.to_s, s.attrs['foo'], "should have saved the attribute type of: #{the_type}"
- end
- end
-
- test "should accept multiple attribute names for an attribute method" do
- names = ['foo','bar','baz']
- s = nil
- Person.schema do
- s = self
- string(*names)
- end
- names.each do |the_name|
- assert s.attrs.has_key?(the_name), "should now have saved the attribute name: #{the_name}"
- assert_equal 'string', s.attrs[the_name], "should have saved the attribute as a string"
- end
- end
-
- #####################################################
- # What a schema does for us
- ####
-
- # respond_to?
-
- test "should respond positively to attributes that are only in the schema" do
- new_attr_name = :my_new_schema_attribute
- new_attr_name_two = :another_new_schema_attribute
- assert Person.schema.blank?, "sanity check - should have a blank class schema"
-
- assert !Person.new.respond_to?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet"
- assert !Person.new.respond_to?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet"
-
- assert_nothing_raised do
- Person.schema = {new_attr_name.to_s => 'string'}
- Person.schema { string new_attr_name_two }
- end
-
- assert_respond_to Person.new, new_attr_name, "should respond to the attribute in a passed-in schema, but failed on: #{new_attr_name}"
- assert_respond_to Person.new, new_attr_name_two, "should respond to the attribute from the schema, but failed on: #{new_attr_name_two}"
- end
-
- test "should not care about ordering of schema definitions" do
- new_attr_name = :my_new_schema_attribute
- new_attr_name_two = :another_new_schema_attribute
-
- assert Person.schema.blank?, "sanity check - should have a blank class schema"
-
- assert !Person.new.respond_to?(new_attr_name), "sanity check - should not respond to the brand-new attribute yet"
- assert !Person.new.respond_to?(new_attr_name_two), "sanity check - should not respond to the brand-new attribute yet"
-
- assert_nothing_raised do
- Person.schema { string new_attr_name_two }
- Person.schema = {new_attr_name.to_s => 'string'}
- end
-
- assert_respond_to Person.new, new_attr_name, "should respond to the attribute in a passed-in schema, but failed on: #{new_attr_name}"
- assert_respond_to Person.new, new_attr_name_two, "should respond to the attribute from the schema, but failed on: #{new_attr_name_two}"
- end
-
- # method_missing effects
-
- test "should not give method_missing for attribute only in schema" do
- new_attr_name = :another_new_schema_attribute
- new_attr_name_two = :another_new_schema_attribute
-
- assert Person.schema.blank?, "sanity check - should have a blank class schema"
-
- assert_raises(NoMethodError, "should not have found the attribute: #{new_attr_name} as a method") do
- Person.new.send(new_attr_name)
- end
- assert_raises(NoMethodError, "should not have found the attribute: #{new_attr_name_two} as a method") do
- Person.new.send(new_attr_name_two)
- end
-
- Person.schema = {new_attr_name.to_s => :float}
- Person.schema { string new_attr_name_two }
-
- assert_nothing_raised do
- Person.new.send(new_attr_name)
- Person.new.send(new_attr_name_two)
- end
- end
-
-
- ########
- # Known attributes
- #
- # Attributes can be known to be attributes even if they aren't actually
- # 'set' on a particular instance.
- # This will only differ from 'attributes' if a schema has been set.
-
- test "new model should have no known attributes" do
- assert Person.known_attributes.blank?, "should have no known attributes"
- assert Person.new.known_attributes.blank?, "should have no known attributes on a new instance"
- end
-
- test "setting schema should set known attributes on class and instance" do
- new_schema = {'age' => 'integer', 'name' => 'string',
- 'height' => 'float', 'bio' => 'text',
- 'weight' => 'decimal', 'photo' => 'binary',
- 'alive' => 'boolean', 'created_at' => 'timestamp',
- 'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
-
- assert_nothing_raised { Person.schema = new_schema }
-
- assert_equal new_schema.keys.sort, Person.known_attributes.sort
- assert_equal new_schema.keys.sort, Person.new.known_attributes.sort
- end
-
- test "known attributes on a fetched resource should return all the attributes of the instance" do
- p = Person.find(1)
- attrs = p.known_attributes
-
- assert attrs.present?, "should have found some attributes!"
-
- p.attributes.each do |the_attr, val|
- assert attrs.include?(the_attr), "should have found attr: #{the_attr} in known attributes, but only had: #{attrs.inspect}"
- end
- end
-
- test "with two instances, known attributes should match the attributes of the individual instances - even if they differ" do
- matz = Person.find(1)
- rick = Person.find(6)
-
- m_attrs = matz.attributes.keys.sort
- r_attrs = rick.attributes.keys.sort
-
- assert_not_equal m_attrs, r_attrs, "should have different attributes on each model"
-
- assert_not_equal matz.known_attributes, rick.known_attributes, "should have had different known attributes too"
- end
-
- test "setting schema then fetching should add schema attributes to the instance attributes" do
- # an attribute in common with fetched instance and one that isn't
- new_schema = {'age' => 'integer', 'name' => 'string',
- 'height' => 'float', 'bio' => 'text',
- 'weight' => 'decimal', 'photo' => 'binary',
- 'alive' => 'boolean', 'created_at' => 'timestamp',
- 'thetime' => 'time', 'thedate' => 'date', 'mydatetime' => 'datetime'}
-
- assert_nothing_raised { Person.schema = new_schema }
-
- matz = Person.find(1)
- known_attrs = matz.known_attributes
-
- matz.attributes.keys.each do |the_attr|
- assert known_attrs.include?(the_attr), "should have found instance attr: #{the_attr} in known attributes, but only had: #{known_attrs.inspect}"
- end
- new_schema.keys.each do |the_attr|
- assert known_attrs.include?(the_attr), "should have found schema attr: #{the_attr} in known attributes, but only had: #{known_attrs.inspect}"
- end
- end
-
-
-end
diff --git a/activeresource/test/cases/base_errors_test.rb b/activeresource/test/cases/base_errors_test.rb
deleted file mode 100644
index 5063916d10..0000000000
--- a/activeresource/test/cases/base_errors_test.rb
+++ /dev/null
@@ -1,107 +0,0 @@
-require 'abstract_unit'
-require "fixtures/person"
-
-class BaseErrorsTest < Test::Unit::TestCase
- def setup
- ActiveResource::HttpMock.respond_to do |mock|
- mock.post "/people.xml", {}, %q(<?xml version="1.0" encoding="UTF-8"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>), 422, {'Content-Type' => 'application/xml; charset=utf-8'}
- mock.post "/people.json", {}, %q({"errors":["Age can't be blank","Name can't be blank","Name must start with a letter","Person quota full for today."]}), 422, {'Content-Type' => 'application/json; charset=utf-8'}
- end
- end
-
- def test_should_mark_as_invalid
- [ :json, :xml ].each do |format|
- invalid_user_using_format(format) do
- assert !@person.valid?
- end
- end
- end
-
- def test_should_parse_json_and_xml_errors
- [ :json, :xml ].each do |format|
- invalid_user_using_format(format) do
- assert_kind_of ActiveResource::Errors, @person.errors
- assert_equal 4, @person.errors.size
- end
- end
- end
-
- def test_should_parse_json_errors_when_no_errors_key
- ActiveResource::HttpMock.respond_to do |mock|
- mock.post "/people.json", {}, '{}', 422, {'Content-Type' => 'application/json; charset=utf-8'}
- end
-
- invalid_user_using_format(:json) do
- assert_kind_of ActiveResource::Errors, @person.errors
- assert_equal 0, @person.errors.size
- end
- end
-
- def test_should_parse_errors_to_individual_attributes
- [ :json, :xml ].each do |format|
- invalid_user_using_format(format) do
- assert @person.errors[:name].any?
- assert_equal ["can't be blank"], @person.errors[:age]
- assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
- assert_equal ["Person quota full for today."], @person.errors[:base]
- end
- end
- end
-
- def test_should_iterate_over_errors
- [ :json, :xml ].each do |format|
- invalid_user_using_format(format) do
- errors = []
- @person.errors.each { |attribute, message| errors << [attribute, message] }
- assert errors.include?([:name, "can't be blank"])
- end
- end
- end
-
- def test_should_iterate_over_full_errors
- [ :json, :xml ].each do |format|
- invalid_user_using_format(format) do
- errors = []
- @person.errors.to_a.each { |message| errors << message }
- assert errors.include?("Name can't be blank")
- end
- end
- end
-
- def test_should_format_full_errors
- [ :json, :xml ].each do |format|
- invalid_user_using_format(format) do
- full = @person.errors.full_messages
- assert full.include?("Age can't be blank")
- assert full.include?("Name can't be blank")
- assert full.include?("Name must start with a letter")
- assert full.include?("Person quota full for today.")
- end
- end
- end
-
- def test_should_mark_as_invalid_when_content_type_is_unavailable_in_response_header
- ActiveResource::HttpMock.respond_to do |mock|
- mock.post "/people.xml", {}, %q(<?xml version="1.0" encoding="UTF-8"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>), 422, {}
- mock.post "/people.json", {}, %q({"errors":["Age can't be blank","Name can't be blank","Name must start with a letter","Person quota full for today."]}), 422, {}
- end
-
- [ :json, :xml ].each do |format|
- invalid_user_using_format(format) do
- assert !@person.valid?
- end
- end
- end
-
- private
- def invalid_user_using_format(mime_type_reference)
- previous_format = Person.format
- Person.format = mime_type_reference
- @person = Person.new(:name => '', :age => '')
- assert_equal false, @person.save
-
- yield
- ensure
- Person.format = previous_format
- end
-end
diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb
deleted file mode 100644
index 7b42f64a35..0000000000
--- a/activeresource/test/cases/base_test.rb
+++ /dev/null
@@ -1,1144 +0,0 @@
-require 'abstract_unit'
-require "fixtures/person"
-require "fixtures/customer"
-require "fixtures/street_address"
-require "fixtures/sound"
-require "fixtures/beast"
-require "fixtures/proxy"
-require "fixtures/address"
-require "fixtures/subscription_plan"
-require 'active_support/json'
-require 'active_support/ordered_hash'
-require 'active_support/core_ext/hash/conversions'
-require 'mocha'
-
-class BaseTest < Test::Unit::TestCase
- def setup
- setup_response # find me in abstract_unit
- @original_person_site = Person.site
- end
-
- def teardown
- Person.site = @original_person_site
- end
-
- ########################################################################
- # Tests relating to setting up the API-connection configuration
- ########################################################################
-
- def test_site_accessor_accepts_uri_or_string_argument
- site = URI.parse('http://localhost')
-
- assert_nothing_raised { Person.site = 'http://localhost' }
- assert_equal site, Person.site
-
- assert_nothing_raised { Person.site = site }
- assert_equal site, Person.site
- end
-
- def test_should_use_site_prefix_and_credentials
- assert_equal 'http://foo:bar@beast.caboo.se', Forum.site.to_s
- assert_equal 'http://foo:bar@beast.caboo.se/forums/:forum_id', Topic.site.to_s
- end
-
- def test_site_variable_can_be_reset
- actor = Class.new(ActiveResource::Base)
- assert_nil actor.site
- actor.site = 'http://localhost:31337'
- actor.site = nil
- assert_nil actor.site
- end
-
- def test_proxy_accessor_accepts_uri_or_string_argument
- proxy = URI.parse('http://localhost')
-
- assert_nothing_raised { Person.proxy = 'http://localhost' }
- assert_equal proxy, Person.proxy
-
- assert_nothing_raised { Person.proxy = proxy }
- assert_equal proxy, Person.proxy
- end
-
- def test_should_use_proxy_prefix_and_credentials
- assert_equal 'http://user:password@proxy.local:3000', ProxyResource.proxy.to_s
- end
-
- def test_proxy_variable_can_be_reset
- actor = Class.new(ActiveResource::Base)
- assert_nil actor.site
- actor.proxy = 'http://localhost:31337'
- actor.proxy = nil
- assert_nil actor.site
- end
-
- def test_should_accept_setting_user
- Forum.user = 'david'
- assert_equal('david', Forum.user)
- assert_equal('david', Forum.connection.user)
- end
-
- def test_should_accept_setting_password
- Forum.password = 'test123'
- assert_equal('test123', Forum.password)
- assert_equal('test123', Forum.connection.password)
- end
-
- def test_should_accept_setting_auth_type
- Forum.auth_type = :digest
- assert_equal(:digest, Forum.auth_type)
- assert_equal(:digest, Forum.connection.auth_type)
- end
-
- def test_should_accept_setting_timeout
- Forum.timeout = 5
- assert_equal(5, Forum.timeout)
- assert_equal(5, Forum.connection.timeout)
- end
-
- def test_should_accept_setting_ssl_options
- expected = {:verify => 1}
- Forum.ssl_options= expected
- assert_equal(expected, Forum.ssl_options)
- assert_equal(expected, Forum.connection.ssl_options)
- end
-
- def test_user_variable_can_be_reset
- actor = Class.new(ActiveResource::Base)
- actor.site = 'http://cinema'
- assert_nil actor.user
- actor.user = 'username'
- actor.user = nil
- assert_nil actor.user
- assert_nil actor.connection.user
- end
-
- def test_password_variable_can_be_reset
- actor = Class.new(ActiveResource::Base)
- actor.site = 'http://cinema'
- assert_nil actor.password
- actor.password = 'username'
- actor.password = nil
- assert_nil actor.password
- assert_nil actor.connection.password
- end
-
- def test_timeout_variable_can_be_reset
- actor = Class.new(ActiveResource::Base)
- actor.site = 'http://cinema'
- assert_nil actor.timeout
- actor.timeout = 5
- actor.timeout = nil
- assert_nil actor.timeout
- assert_nil actor.connection.timeout
- end
-
- def test_ssl_options_hash_can_be_reset
- actor = Class.new(ActiveResource::Base)
- actor.site = 'https://cinema'
- assert_nil actor.ssl_options
- actor.ssl_options = {:foo => 5}
- actor.ssl_options = nil
- assert_nil actor.ssl_options
- assert_nil actor.connection.ssl_options
- end
-
- def test_credentials_from_site_are_decoded
- actor = Class.new(ActiveResource::Base)
- actor.site = 'http://my%40email.com:%31%32%33@cinema'
- assert_equal("my@email.com", actor.user)
- assert_equal("123", actor.password)
- end
-
- def test_site_reader_uses_superclass_site_until_written
- # Superclass is Object so returns nil.
- assert_nil ActiveResource::Base.site
- assert_nil Class.new(ActiveResource::Base).site
-
- # Subclass uses superclass site.
- actor = Class.new(Person)
- assert_equal Person.site, actor.site
-
- # Subclass returns frozen superclass copy.
- assert !Person.site.frozen?
- assert actor.site.frozen?
-
- # Changing subclass site doesn't change superclass site.
- actor.site = 'http://localhost:31337'
- assert_not_equal Person.site, actor.site
-
- # Changed subclass site is not frozen.
- assert !actor.site.frozen?
-
- # Changing superclass site doesn't overwrite subclass site.
- Person.site = 'http://somewhere.else'
- assert_not_equal Person.site, actor.site
-
- # Changing superclass site after subclassing changes subclass site.
- jester = Class.new(actor)
- actor.site = 'http://nomad'
- assert_equal actor.site, jester.site
- assert jester.site.frozen?
-
- # Subclasses are always equal to superclass site when not overridden
- fruit = Class.new(ActiveResource::Base)
- apple = Class.new(fruit)
-
- fruit.site = 'http://market'
- assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class'
-
- fruit.site = 'http://supermarket'
- assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class'
- end
-
- def test_proxy_reader_uses_superclass_site_until_written
- # Superclass is Object so returns nil.
- assert_nil ActiveResource::Base.proxy
- assert_nil Class.new(ActiveResource::Base).proxy
-
- # Subclass uses superclass proxy.
- actor = Class.new(Person)
- assert_equal Person.proxy, actor.proxy
-
- # Subclass returns frozen superclass copy.
- assert !Person.proxy.frozen?
- assert actor.proxy.frozen?
-
- # Changing subclass proxy doesn't change superclass site.
- actor.proxy = 'http://localhost:31337'
- assert_not_equal Person.proxy, actor.proxy
-
- # Changed subclass proxy is not frozen.
- assert !actor.proxy.frozen?
-
- # Changing superclass proxy doesn't overwrite subclass site.
- Person.proxy = 'http://somewhere.else'
- assert_not_equal Person.proxy, actor.proxy
-
- # Changing superclass proxy after subclassing changes subclass site.
- jester = Class.new(actor)
- actor.proxy = 'http://nomad'
- assert_equal actor.proxy, jester.proxy
- assert jester.proxy.frozen?
-
- # Subclasses are always equal to superclass proxy when not overridden
- fruit = Class.new(ActiveResource::Base)
- apple = Class.new(fruit)
-
- fruit.proxy = 'http://market'
- assert_equal fruit.proxy, apple.proxy, 'subclass did not adopt changes from parent class'
-
- fruit.proxy = 'http://supermarket'
- assert_equal fruit.proxy, apple.proxy, 'subclass did not adopt changes from parent class'
- end
-
- def test_user_reader_uses_superclass_user_until_written
- # Superclass is Object so returns nil.
- assert_nil ActiveResource::Base.user
- assert_nil Class.new(ActiveResource::Base).user
- Person.user = 'anonymous'
-
- # Subclass uses superclass user.
- actor = Class.new(Person)
- assert_equal Person.user, actor.user
-
- # Subclass returns frozen superclass copy.
- assert !Person.user.frozen?
- assert actor.user.frozen?
-
- # Changing subclass user doesn't change superclass user.
- actor.user = 'david'
- assert_not_equal Person.user, actor.user
-
- # Changing superclass user doesn't overwrite subclass user.
- Person.user = 'john'
- assert_not_equal Person.user, actor.user
-
- # Changing superclass user after subclassing changes subclass user.
- jester = Class.new(actor)
- actor.user = 'john.doe'
- assert_equal actor.user, jester.user
-
- # Subclasses are always equal to superclass user when not overridden
- fruit = Class.new(ActiveResource::Base)
- apple = Class.new(fruit)
-
- fruit.user = 'manager'
- assert_equal fruit.user, apple.user, 'subclass did not adopt changes from parent class'
-
- fruit.user = 'client'
- assert_equal fruit.user, apple.user, 'subclass did not adopt changes from parent class'
- end
-
- def test_password_reader_uses_superclass_password_until_written
- # Superclass is Object so returns nil.
- assert_nil ActiveResource::Base.password
- assert_nil Class.new(ActiveResource::Base).password
- Person.password = 'my-password'
-
- # Subclass uses superclass password.
- actor = Class.new(Person)
- assert_equal Person.password, actor.password
-
- # Subclass returns frozen superclass copy.
- assert !Person.password.frozen?
- assert actor.password.frozen?
-
- # Changing subclass password doesn't change superclass password.
- actor.password = 'secret'
- assert_not_equal Person.password, actor.password
-
- # Changing superclass password doesn't overwrite subclass password.
- Person.password = 'super-secret'
- assert_not_equal Person.password, actor.password
-
- # Changing superclass password after subclassing changes subclass password.
- jester = Class.new(actor)
- actor.password = 'even-more-secret'
- assert_equal actor.password, jester.password
-
- # Subclasses are always equal to superclass password when not overridden
- fruit = Class.new(ActiveResource::Base)
- apple = Class.new(fruit)
-
- fruit.password = 'mega-secret'
- assert_equal fruit.password, apple.password, 'subclass did not adopt changes from parent class'
-
- fruit.password = 'ok-password'
- assert_equal fruit.password, apple.password, 'subclass did not adopt changes from parent class'
- end
-
- def test_timeout_reader_uses_superclass_timeout_until_written
- # Superclass is Object so returns nil.
- assert_nil ActiveResource::Base.timeout
- assert_nil Class.new(ActiveResource::Base).timeout
- Person.timeout = 5
-
- # Subclass uses superclass timeout.
- actor = Class.new(Person)
- assert_equal Person.timeout, actor.timeout
-
- # Changing subclass timeout doesn't change superclass timeout.
- actor.timeout = 10
- assert_not_equal Person.timeout, actor.timeout
-
- # Changing superclass timeout doesn't overwrite subclass timeout.
- Person.timeout = 15
- assert_not_equal Person.timeout, actor.timeout
-
- # Changing superclass timeout after subclassing changes subclass timeout.
- jester = Class.new(actor)
- actor.timeout = 20
- assert_equal actor.timeout, jester.timeout
-
- # Subclasses are always equal to superclass timeout when not overridden.
- fruit = Class.new(ActiveResource::Base)
- apple = Class.new(fruit)
-
- fruit.timeout = 25
- assert_equal fruit.timeout, apple.timeout, 'subclass did not adopt changes from parent class'
-
- fruit.timeout = 30
- assert_equal fruit.timeout, apple.timeout, 'subclass did not adopt changes from parent class'
- end
-
- def test_ssl_options_reader_uses_superclass_ssl_options_until_written
- # Superclass is Object so returns nil.
- assert_nil ActiveResource::Base.ssl_options
- assert_nil Class.new(ActiveResource::Base).ssl_options
- Person.ssl_options = {:foo => 'bar'}
-
- # Subclass uses superclass ssl_options.
- actor = Class.new(Person)
- assert_equal Person.ssl_options, actor.ssl_options
-
- # Changing subclass ssl_options doesn't change superclass ssl_options.
- actor.ssl_options = {:baz => ''}
- assert_not_equal Person.ssl_options, actor.ssl_options
-
- # Changing superclass ssl_options doesn't overwrite subclass ssl_options.
- Person.ssl_options = {:color => 'blue'}
- assert_not_equal Person.ssl_options, actor.ssl_options
-
- # Changing superclass ssl_options after subclassing changes subclass ssl_options.
- jester = Class.new(actor)
- actor.ssl_options = {:color => 'red'}
- assert_equal actor.ssl_options, jester.ssl_options
-
- # Subclasses are always equal to superclass ssl_options when not overridden.
- fruit = Class.new(ActiveResource::Base)
- apple = Class.new(fruit)
-
- fruit.ssl_options = {:alpha => 'betas'}
- assert_equal fruit.ssl_options, apple.ssl_options, 'subclass did not adopt changes from parent class'
-
- fruit.ssl_options = {:omega => 'moos'}
- assert_equal fruit.ssl_options, apple.ssl_options, 'subclass did not adopt changes from parent class'
- end
-
- def test_updating_baseclass_site_object_wipes_descendent_cached_connection_objects
- # Subclasses are always equal to superclass site when not overridden
- fruit = Class.new(ActiveResource::Base)
- apple = Class.new(fruit)
-
- fruit.site = 'http://market'
- assert_equal fruit.connection.site, apple.connection.site
- first_connection = apple.connection.object_id
-
- fruit.site = 'http://supermarket'
- assert_equal fruit.connection.site, apple.connection.site
- second_connection = apple.connection.object_id
- assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
- end
-
- def test_updating_baseclass_user_wipes_descendent_cached_connection_objects
- # Subclasses are always equal to superclass user when not overridden
- fruit = Class.new(ActiveResource::Base)
- apple = Class.new(fruit)
- fruit.site = 'http://market'
-
- fruit.user = 'david'
- assert_equal fruit.connection.user, apple.connection.user
- first_connection = apple.connection.object_id
-
- fruit.user = 'john'
- assert_equal fruit.connection.user, apple.connection.user
- second_connection = apple.connection.object_id
- assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
- end
-
- def test_updating_baseclass_password_wipes_descendent_cached_connection_objects
- # Subclasses are always equal to superclass password when not overridden
- fruit = Class.new(ActiveResource::Base)
- apple = Class.new(fruit)
- fruit.site = 'http://market'
-
- fruit.password = 'secret'
- assert_equal fruit.connection.password, apple.connection.password
- first_connection = apple.connection.object_id
-
- fruit.password = 'supersecret'
- assert_equal fruit.connection.password, apple.connection.password
- second_connection = apple.connection.object_id
- assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
- end
-
- def test_updating_baseclass_timeout_wipes_descendent_cached_connection_objects
- # Subclasses are always equal to superclass timeout when not overridden
- fruit = Class.new(ActiveResource::Base)
- apple = Class.new(fruit)
- fruit.site = 'http://market'
-
- fruit.timeout = 5
- assert_equal fruit.connection.timeout, apple.connection.timeout
- first_connection = apple.connection.object_id
-
- fruit.timeout = 10
- assert_equal fruit.connection.timeout, apple.connection.timeout
- second_connection = apple.connection.object_id
- assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
- end
-
-
- ########################################################################
- # Tests for setting up remote URLs for a given model (including adding
- # parameters appropriately)
- ########################################################################
- def test_collection_name
- assert_equal "people", Person.collection_name
- end
-
- def test_collection_path
- assert_equal '/people.json', Person.collection_path
- end
-
- def test_collection_path_with_parameters
- assert_equal '/people.json?gender=male', Person.collection_path(:gender => 'male')
- assert_equal '/people.json?gender=false', Person.collection_path(:gender => false)
- assert_equal '/people.json?gender=', Person.collection_path(:gender => nil)
-
- assert_equal '/people.json?gender=male', Person.collection_path('gender' => 'male')
-
- # Use includes? because ordering of param hash is not guaranteed
- assert Person.collection_path(:gender => 'male', :student => true).include?('/people.json?')
- assert Person.collection_path(:gender => 'male', :student => true).include?('gender=male')
- assert Person.collection_path(:gender => 'male', :student => true).include?('student=true')
-
- assert_equal '/people.json?name%5B%5D=bob&name%5B%5D=your+uncle%2Bme&name%5B%5D=&name%5B%5D=false', Person.collection_path(:name => ['bob', 'your uncle+me', nil, false])
-
- assert_equal '/people.json?struct%5Ba%5D%5B%5D=2&struct%5Ba%5D%5B%5D=1&struct%5Bb%5D=fred', Person.collection_path(:struct => ActiveSupport::OrderedHash[:a, [2,1], 'b', 'fred'])
- end
-
- def test_custom_element_path
- assert_equal '/people/1/addresses/1.json', StreetAddress.element_path(1, :person_id => 1)
- assert_equal '/people/1/addresses/1.json', StreetAddress.element_path(1, 'person_id' => 1)
- assert_equal '/people/Greg/addresses/1.json', StreetAddress.element_path(1, 'person_id' => 'Greg')
- assert_equal '/people/ann%20mary/addresses/ann%20mary.json', StreetAddress.element_path(:'ann mary', 'person_id' => 'ann mary')
- end
-
- def test_custom_element_path_without_required_prefix_param
- assert_raise ActiveResource::MissingPrefixParam do
- StreetAddress.element_path(1)
- end
- end
-
- def test_module_element_path
- assert_equal '/sounds/1.json', Asset::Sound.element_path(1)
- end
-
- def test_custom_element_path_with_redefined_to_param
- Person.module_eval do
- alias_method :original_to_param_element_path, :to_param
- def to_param
- name
- end
- end
-
- # Class method.
- assert_equal '/people/Greg.json', Person.element_path('Greg')
-
- # Protected Instance method.
- assert_equal '/people/Greg.json', Person.find('Greg').send(:element_path)
-
- ensure
- # revert back to original
- Person.module_eval do
- # save the 'new' to_param so we don't get a warning about discarding the method
- alias_method :element_path_to_param, :to_param
- alias_method :to_param, :original_to_param_element_path
- end
- end
-
- def test_custom_element_path_with_parameters
- assert_equal '/people/1/addresses/1.json?type=work', StreetAddress.element_path(1, :person_id => 1, :type => 'work')
- assert_equal '/people/1/addresses/1.json?type=work', StreetAddress.element_path(1, 'person_id' => 1, :type => 'work')
- assert_equal '/people/1/addresses/1.json?type=work', StreetAddress.element_path(1, :type => 'work', :person_id => 1)
- assert_equal '/people/1/addresses/1.json?type%5B%5D=work&type%5B%5D=play+time', StreetAddress.element_path(1, :person_id => 1, :type => ['work', 'play time'])
- end
-
- def test_custom_element_path_with_prefix_and_parameters
- assert_equal '/people/1/addresses/1.json?type=work', StreetAddress.element_path(1, {:person_id => 1}, {:type => 'work'})
- end
-
- def test_custom_collection_path_without_required_prefix_param
- assert_raise ActiveResource::MissingPrefixParam do
- StreetAddress.collection_path
- end
- end
-
- def test_custom_collection_path
- assert_equal '/people/1/addresses.json', StreetAddress.collection_path(:person_id => 1)
- assert_equal '/people/1/addresses.json', StreetAddress.collection_path('person_id' => 1)
- end
-
- def test_custom_collection_path_with_parameters
- assert_equal '/people/1/addresses.json?type=work', StreetAddress.collection_path(:person_id => 1, :type => 'work')
- assert_equal '/people/1/addresses.json?type=work', StreetAddress.collection_path('person_id' => 1, :type => 'work')
- end
-
- def test_custom_collection_path_with_prefix_and_parameters
- assert_equal '/people/1/addresses.json?type=work', StreetAddress.collection_path({:person_id => 1}, {:type => 'work'})
- end
-
- def test_custom_element_name
- assert_equal 'address', StreetAddress.element_name
- end
-
- def test_custom_collection_name
- assert_equal 'addresses', StreetAddress.collection_name
- end
-
- def test_prefix
- assert_equal "/", Person.prefix
- assert_equal Set.new, Person.__send__(:prefix_parameters)
- end
-
- def test_set_prefix
- SetterTrap.rollback_sets(Person) do |person_class|
- person_class.prefix = "the_prefix"
- assert_equal "the_prefix", person_class.prefix
- end
- end
-
- def test_set_prefix_with_inline_keys
- SetterTrap.rollback_sets(Person) do |person_class|
- person_class.prefix = "the_prefix:the_param"
- assert_equal "the_prefixthe_param_value", person_class.prefix(:the_param => "the_param_value")
- end
- end
-
- def test_set_prefix_twice_should_clear_params
- SetterTrap.rollback_sets(Person) do |person_class|
- person_class.prefix = "the_prefix/:the_param1"
- assert_equal Set.new([:the_param1]), person_class.prefix_parameters
- person_class.prefix = "the_prefix/:the_param2"
- assert_equal Set.new([:the_param2]), person_class.prefix_parameters
- person_class.prefix = "the_prefix/:the_param1/other_prefix/:the_param2"
- assert_equal Set.new([:the_param2, :the_param1]), person_class.prefix_parameters
- end
- end
-
- def test_set_prefix_with_default_value
- SetterTrap.rollback_sets(Person) do |person_class|
- person_class.set_prefix
- assert_equal "/", person_class.prefix
- end
- end
-
- def test_custom_prefix
- assert_equal '/people//', StreetAddress.prefix
- assert_equal '/people/1/', StreetAddress.prefix(:person_id => 1)
- assert_equal [:person_id].to_set, StreetAddress.__send__(:prefix_parameters)
- end
-
-
- ########################################################################
- # Tests basic CRUD functions (find/save/create etc)
- ########################################################################
- def test_respond_to
- matz = Person.find(1)
- assert_respond_to matz, :name
- assert_respond_to matz, :name=
- assert_respond_to matz, :name?
- assert !matz.respond_to?(:super_scalable_stuff)
- end
-
- def test_custom_header
- Person.headers['key'] = 'value'
- assert_raise(ActiveResource::ResourceNotFound) { Person.find(4) }
- ensure
- Person.headers.delete('key')
- end
-
- def test_save
- rick = Person.new
- assert rick.save
- assert_equal '5', rick.id
- end
-
- def test_save!
- rick = Person.new
- assert rick.save!
- assert_equal '5', rick.id
- end
-
- def test_id_from_response
- p = Person.new
- resp = {'Location' => '/foo/bar/1'}
- assert_equal '1', p.__send__(:id_from_response, resp)
-
- resp['Location'] << '.json'
- assert_equal '1', p.__send__(:id_from_response, resp)
- end
-
- def test_id_from_response_without_location
- p = Person.new
- resp = {}
- assert_nil p.__send__(:id_from_response, resp)
- end
-
- def test_not_persisted_with_no_body_and_positive_content_length
- resp = ActiveResource::Response.new(nil)
- resp['Content-Length'] = "100"
- Person.connection.expects(:post).returns(resp)
- assert !Person.create.persisted?
- end
-
- def test_not_persisted_with_body_and_zero_content_length
- resp = ActiveResource::Response.new(@rick)
- resp['Content-Length'] = "0"
- Person.connection.expects(:post).returns(resp)
- assert !Person.create.persisted?
- end
-
- # These response codes aren't allowed to have bodies per HTTP spec
- def test_not_persisted_with_empty_response_codes
- [100,101,204,304].each do |status_code|
- resp = ActiveResource::Response.new(@rick, status_code)
- Person.connection.expects(:post).returns(resp)
- assert !Person.create.persisted?
- end
- end
-
- # Content-Length is not required by HTTP 1.1, so we should read
- # the body anyway in its absence.
- def test_persisted_with_no_content_length
- resp = ActiveResource::Response.new(@rick)
- resp['Content-Length'] = nil
- Person.connection.expects(:post).returns(resp)
- assert Person.create.persisted?
- end
-
- def test_create_with_custom_prefix
- matzs_house = StreetAddress.new(:person_id => 1)
- matzs_house.save
- assert_equal '5', matzs_house.id
- end
-
- # Test that loading a resource preserves its prefix_options.
- def test_load_preserves_prefix_options
- address = StreetAddress.find(1, :params => { :person_id => 1 })
- ryan = Person.new(:id => 1, :name => 'Ryan', :address => address)
- assert_equal address.prefix_options, ryan.address.prefix_options
- end
-
- def test_reload_works_with_prefix_options
- address = StreetAddress.find(1, :params => { :person_id => 1 })
- assert_equal address, address.reload
- end
-
- def test_reload_with_redefined_to_param
- Person.module_eval do
- alias_method :original_to_param_reload, :to_param
- def to_param
- name
- end
- end
-
- person = Person.find('Greg')
- assert_equal person, person.reload
-
- ensure
- # revert back to original
- Person.module_eval do
- # save the 'new' to_param so we don't get a warning about discarding the method
- alias_method :reload_to_param, :to_param
- alias_method :to_param, :original_to_param_reload
- end
- end
-
- def test_reload_works_without_prefix_options
- person = Person.find(:first)
- assert_equal person, person.reload
- end
-
- def test_create
- rick = Person.create(:name => 'Rick')
- assert rick.valid?
- assert !rick.new?
- assert_equal '5', rick.id
-
- # test additional attribute returned on create
- assert_equal 25, rick.age
-
- # Test that save exceptions get bubbled up too
- ActiveResource::HttpMock.respond_to do |mock|
- mock.post "/people.json", {}, nil, 409
- end
- assert_raise(ActiveResource::ResourceConflict) { Person.create(:name => 'Rick') }
- end
-
- def test_create_without_location
- ActiveResource::HttpMock.respond_to do |mock|
- mock.post "/people.json", {}, nil, 201
- end
- person = Person.create(:name => 'Rick')
- assert_nil person.id
- end
-
- def test_clone
- matz = Person.find(1)
- matz_c = matz.clone
- assert matz_c.new?
- matz.attributes.each do |k, v|
- assert_equal v, matz_c.send(k) if k != Person.primary_key
- end
- end
-
- def test_nested_clone
- addy = StreetAddress.find(1, :params => {:person_id => 1})
- addy_c = addy.clone
- assert addy_c.new?
- addy.attributes.each do |k, v|
- assert_equal v, addy_c.send(k) if k != StreetAddress.primary_key
- end
- assert_equal addy.prefix_options, addy_c.prefix_options
- end
-
- def test_complex_clone
- matz = Person.find(1)
- matz.address = StreetAddress.find(1, :params => {:person_id => matz.id})
- matz.non_ar_hash = {:not => "an ARes instance"}
- matz.non_ar_arr = ["not", "ARes"]
- matz_c = matz.clone
- assert matz_c.new?
- assert_raise(NoMethodError) {matz_c.address}
- assert_equal matz.non_ar_hash, matz_c.non_ar_hash
- assert_equal matz.non_ar_arr, matz_c.non_ar_arr
-
- # Test that actual copy, not just reference copy
- matz.non_ar_hash[:not] = "changed"
- assert_not_equal matz.non_ar_hash, matz_c.non_ar_hash
- end
-
- def test_update
- matz = Person.find(:first)
- matz.name = "David"
- assert_kind_of Person, matz
- assert_equal "David", matz.name
- assert_equal true, matz.save
- end
-
- def test_update_with_custom_prefix_with_specific_id
- addy = StreetAddress.find(1, :params => { :person_id => 1 })
- addy.street = "54321 Street"
- assert_kind_of StreetAddress, addy
- assert_equal "54321 Street", addy.street
- addy.save
- end
-
- def test_update_with_custom_prefix_without_specific_id
- addy = StreetAddress.find(:first, :params => { :person_id => 1 })
- addy.street = "54321 Lane"
- assert_kind_of StreetAddress, addy
- assert_equal "54321 Lane", addy.street
- addy.save
- end
-
- def test_update_conflict
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/2.json", {}, @david
- mock.put "/people/2.json", @default_request_headers, nil, 409
- end
- assert_raise(ActiveResource::ResourceConflict) { Person.find(2).save }
- end
-
-
- ######
- # update_attribute(s)(!)
-
- def test_update_attribute_as_symbol
- matz = Person.first
- matz.expects(:save).returns(true)
-
- assert_equal "Matz", matz.name
- assert matz.update_attribute(:name, "David")
- assert_equal "David", matz.name
- end
-
- def test_update_attribute_as_string
- matz = Person.first
- matz.expects(:save).returns(true)
-
- assert_equal "Matz", matz.name
- assert matz.update_attribute('name', "David")
- assert_equal "David", matz.name
- end
-
-
- def test_update_attributes_as_symbols
- addy = StreetAddress.first(:params => {:person_id => 1})
- addy.expects(:save).returns(true)
-
- assert_equal "12345 Street", addy.street
- assert_equal "Australia", addy.country
- assert addy.update_attributes(:street => '54321 Street', :country => 'USA')
- assert_equal "54321 Street", addy.street
- assert_equal "USA", addy.country
- end
-
- def test_update_attributes_as_strings
- addy = StreetAddress.first(:params => {:person_id => 1})
- addy.expects(:save).returns(true)
-
- assert_equal "12345 Street", addy.street
- assert_equal "Australia", addy.country
- assert addy.update_attributes('street' => '54321 Street', 'country' => 'USA')
- assert_equal "54321 Street", addy.street
- assert_equal "USA", addy.country
- end
-
-
- #####
- # Mayhem and destruction
-
- def test_destroy
- assert Person.find(1).destroy
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.json", {}, nil, 404
- end
- assert_raise(ActiveResource::ResourceNotFound) { Person.find(1).destroy }
- end
-
- def test_destroy_with_custom_prefix
- assert StreetAddress.find(1, :params => { :person_id => 1 }).destroy
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1/addresses/1.json", {}, nil, 404
- end
- assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) }
- end
-
- def test_destroy_with_410_gone
- assert Person.find(1).destroy
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.json", {}, nil, 410
- end
- assert_raise(ActiveResource::ResourceGone) { Person.find(1).destroy }
- end
-
- def test_delete
- assert Person.delete(1)
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.json", {}, nil, 404
- end
- assert_raise(ActiveResource::ResourceNotFound) { Person.find(1) }
- end
-
- def test_delete_with_custom_prefix
- assert StreetAddress.delete(1, :person_id => 1)
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1/addresses/1.json", {}, nil, 404
- end
- assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) }
- end
-
- def test_delete_with_410_gone
- assert Person.delete(1)
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.json", {}, nil, 410
- end
- assert_raise(ActiveResource::ResourceGone) { Person.find(1) }
- end
-
- ########################################################################
- # Tests the more miscellaneous helper methods
- ########################################################################
- def test_exists
- # Class method.
- assert !Person.exists?(nil)
- assert Person.exists?(1)
- assert !Person.exists?(99)
-
- # Instance method.
- assert !Person.new.exists?
- assert Person.find(1).exists?
- assert !Person.new(:id => 99).exists?
-
- # Nested class method.
- assert StreetAddress.exists?(1, :params => { :person_id => 1 })
- assert !StreetAddress.exists?(1, :params => { :person_id => 2 })
- assert !StreetAddress.exists?(2, :params => { :person_id => 1 })
-
- # Nested instance method.
- assert StreetAddress.find(1, :params => { :person_id => 1 }).exists?
- assert !StreetAddress.new({:id => 1, :person_id => 2}).exists?
- assert !StreetAddress.new({:id => 2, :person_id => 1}).exists?
- end
-
- def test_exists_with_redefined_to_param
- Person.module_eval do
- alias_method :original_to_param_exists, :to_param
- def to_param
- name
- end
- end
-
- # Class method.
- assert Person.exists?('Greg')
-
- # Instance method.
- assert Person.find('Greg').exists?
-
- # Nested class method.
- assert StreetAddress.exists?(1, :params => { :person_id => Person.find('Greg').to_param })
-
- # Nested instance method.
- assert StreetAddress.find(1, :params => { :person_id => Person.find('Greg').to_param }).exists?
-
- ensure
- # revert back to original
- Person.module_eval do
- # save the 'new' to_param so we don't get a warning about discarding the method
- alias_method :exists_to_param, :to_param
- alias_method :to_param, :original_to_param_exists
- end
- end
-
- def test_exists_without_http_mock
- http = Net::HTTP.new(Person.site.host, Person.site.port)
- ActiveResource::Connection.any_instance.expects(:http).returns(http)
- http.expects(:request).returns(ActiveResource::Response.new(""))
-
- assert Person.exists?('not-mocked')
- end
-
- def test_exists_with_410_gone
- ActiveResource::HttpMock.respond_to do |mock|
- mock.head "/people/1.json", {}, nil, 410
- end
-
- assert !Person.exists?(1)
- end
-
- def test_to_xml
- Person.format = :xml
- matz = Person.find(1)
- encode = matz.encode
- xml = matz.to_xml
-
- assert_equal encode, xml
- assert xml.include?('<?xml version="1.0" encoding="UTF-8"?>')
- assert xml.include?('<name>Matz</name>')
- assert xml.include?('<id type="integer">1</id>')
- ensure
- Person.format = :json
- end
-
- def test_to_xml_with_element_name
- Person.format = :xml
- old_elem_name = Person.element_name
- matz = Person.find(1)
- Person.element_name = 'ruby_creator'
- encode = matz.encode
- xml = matz.to_xml
-
- assert_equal encode, xml
- assert xml.include?('<?xml version="1.0" encoding="UTF-8"?>')
- assert xml.include?('<ruby-creator>')
- assert xml.include?('<name>Matz</name>')
- assert xml.include?('<id type="integer">1</id>')
- assert xml.include?('</ruby-creator>')
- ensure
- Person.format = :json
- Person.element_name = old_elem_name
- end
-
- def test_to_xml_with_private_method_name_as_attribute
- Person.format = :xml
-
- customer = Customer.new(:foo => "foo")
- customer.singleton_class.class_eval do
- def foo
- "bar"
- end
- private :foo
- end
-
- assert !customer.to_xml.include?("<foo>bar</foo>")
- assert customer.to_xml.include?("<foo>foo</foo>")
- ensure
- Person.format = :json
- end
-
- def test_to_json
- Person.include_root_in_json = true
- joe = Person.find(6)
- encode = joe.encode
- json = joe.to_json
-
- assert_equal encode, json
- assert_match %r{^\{"person":\{}, json
- assert_match %r{"id":6}, json
- assert_match %r{"name":"Joe"}, json
- assert_match %r{\}\}$}, json
- end
-
- def test_to_json_with_element_name
- old_elem_name = Person.element_name
- Person.include_root_in_json = true
- joe = Person.find(6)
- Person.element_name = 'ruby_creator'
- encode = joe.encode
- json = joe.to_json
-
- assert_equal encode, json
- assert_match %r{^\{"ruby_creator":\{}, json
- assert_match %r{"id":6}, json
- assert_match %r{"name":"Joe"}, json
- assert_match %r{\}\}$}, json
- ensure
- Person.element_name = old_elem_name
- end
-
- def test_to_param_quacks_like_active_record
- new_person = Person.new
- assert_nil new_person.to_param
- matz = Person.find(1)
- assert_equal '1', matz.to_param
- end
-
- def test_to_key_quacks_like_active_record
- new_person = Person.new
- assert_nil new_person.to_key
- matz = Person.find(1)
- assert_equal [1], matz.to_key
- end
-
- def test_parse_deep_nested_resources
- luis = Customer.find(1)
- assert_kind_of Customer, luis
- luis.friends.each do |friend|
- assert_kind_of Customer::Friend, friend
- friend.brothers.each do |brother|
- assert_kind_of Customer::Friend::Brother, brother
- brother.children.each do |child|
- assert_kind_of Customer::Friend::Brother::Child, child
- end
- end
- end
- end
-
- def test_load_yaml_array
- assert_nothing_raised do
- Person.format = :xml
- marty = Person.find(5)
- assert_equal 3, marty.colors.size
- marty.colors.each do |color|
- assert_kind_of String, color
- end
- end
- ensure
- Person.format = :json
- end
-
- def test_with_custom_formatter
- addresses = [{ :id => "1", :street => "1 Infinite Loop", :city => "Cupertino", :state => "CA" }].to_xml(:root => :addresses)
-
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/addresses.xml", {}, addresses, 200
- end
-
- # late bind the site
- AddressResource.site = "http://localhost"
- addresses = AddressResource.find(:all)
-
- assert_equal "Cupertino, CA", addresses.first.city_state
- end
-
- def test_create_with_custom_primary_key
- silver_plan = { :plan => { :code => "silver", :price => 5.00 } }.to_json
-
- ActiveResource::HttpMock.respond_to do |mock|
- mock.post "/plans.json", {}, silver_plan, 201, 'Location' => '/plans/silver.json'
- end
-
- plan = SubscriptionPlan.new(:code => "silver", :price => 5.00)
- assert plan.new?
-
- plan.save!
- assert !plan.new?
- end
-
- def test_update_with_custom_primary_key
- silver_plan = { :plan => { :code => "silver", :price => 5.00 } }.to_json
- silver_plan_updated = { :plan => { :code => "silver", :price => 10.00 } }.to_json
-
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/plans/silver.json", {}, silver_plan
- mock.put "/plans/silver.json", {}, silver_plan_updated, 201, 'Location' => '/plans/silver.json'
- end
-
- plan = SubscriptionPlan.find("silver")
- assert !plan.new?
- assert_equal 5.00, plan.price
-
- # update price
- plan.price = 10.00
- plan.save!
- assert_equal 10.00, plan.price
- end
-
- def test_namespacing
- sound = Asset::Sound.find(1)
- assert_equal "Asset::Sound::Author", sound.author.class.to_s
- end
-end
diff --git a/activeresource/test/cases/connection_test.rb b/activeresource/test/cases/connection_test.rb
deleted file mode 100644
index 535107aeef..0000000000
--- a/activeresource/test/cases/connection_test.rb
+++ /dev/null
@@ -1,276 +0,0 @@
-require 'abstract_unit'
-
-class ConnectionTest < Test::Unit::TestCase
- ResponseCodeStub = Struct.new(:code)
- RedirectResponseStub = Struct.new(:code, :Location)
-
- def setup
- @conn = ActiveResource::Connection.new('http://localhost')
- matz = { :person => { :id => 1, :name => 'Matz' } }
- david = { :person => { :id => 2, :name => 'David' } }
- @people = { :people => [ matz, david ] }.to_json
- @people_single = { 'people-single-elements' => [ matz ] }.to_json
- @people_empty = { 'people-empty-elements' => [ ] }.to_json
- @matz = matz.to_json
- @david = david.to_json
- @header = { 'key' => 'value' }.freeze
-
- @default_request_headers = { 'Content-Type' => 'application/json' }
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/2.json", @header, @david
- mock.get "/people.json", {}, @people
- mock.get "/people_single_elements.json", {}, @people_single
- mock.get "/people_empty_elements.json", {}, @people_empty
- mock.get "/people/1.json", {}, @matz
- mock.put "/people/1.json", {}, nil, 204
- mock.put "/people/2.json", {}, @header, 204
- mock.delete "/people/1.json", {}, nil, 200
- mock.delete "/people/2.json", @header, nil, 200
- mock.post "/people.json", {}, nil, 201, 'Location' => '/people/5.json'
- mock.post "/members.json", {}, @header, 201, 'Location' => '/people/6.json'
- mock.head "/people/1.json", {}, nil, 200
- end
- end
-
- def test_handle_response
- # 2xx and 3xx are valid responses.
- [200, 299, 300, 399].each do |code|
- expected = ResponseCodeStub.new(code)
- assert_equal expected, handle_response(expected)
- end
-
- # 301 is moved permanently (redirect)
- assert_redirect_raises 301
-
- # 302 is found (redirect)
- assert_redirect_raises 302
-
- # 303 is see other (redirect)
- assert_redirect_raises 303
-
- # 307 is temporary redirect
- assert_redirect_raises 307
-
- # 400 is a bad request (e.g. malformed URI or missing request parameter)
- assert_response_raises ActiveResource::BadRequest, 400
-
- # 401 is an unauthorized request
- assert_response_raises ActiveResource::UnauthorizedAccess, 401
-
- # 403 is a forbidden request (and authorizing will not help)
- assert_response_raises ActiveResource::ForbiddenAccess, 403
-
- # 404 is a missing resource.
- assert_response_raises ActiveResource::ResourceNotFound, 404
-
- # 405 is a method not allowed error
- assert_response_raises ActiveResource::MethodNotAllowed, 405
-
- # 409 is an optimistic locking error
- assert_response_raises ActiveResource::ResourceConflict, 409
-
- # 410 is a removed resource
- assert_response_raises ActiveResource::ResourceGone, 410
-
- # 422 is a validation error
- assert_response_raises ActiveResource::ResourceInvalid, 422
-
- # 4xx are client errors.
- [402, 499].each do |code|
- assert_response_raises ActiveResource::ClientError, code
- end
-
- # 5xx are server errors.
- [500, 599].each do |code|
- assert_response_raises ActiveResource::ServerError, code
- end
-
- # Others are unknown.
- [199, 600].each do |code|
- assert_response_raises ActiveResource::ConnectionError, code
- end
- end
-
- ResponseHeaderStub = Struct.new(:code, :message, 'Allow')
- def test_should_return_allowed_methods_for_method_no_allowed_exception
- begin
- handle_response ResponseHeaderStub.new(405, "HTTP Failed...", "GET, POST")
- rescue ActiveResource::MethodNotAllowed => e
- assert_equal "Failed. Response code = 405. Response message = HTTP Failed....", e.message
- assert_equal [:get, :post], e.allowed_methods
- end
- end
-
- def test_initialize_raises_argument_error_on_missing_site
- assert_raise(ArgumentError) { ActiveResource::Connection.new(nil) }
- end
-
- def test_site_accessor_accepts_uri_or_string_argument
- site = URI.parse("http://localhost")
-
- assert_raise(URI::InvalidURIError) { @conn.site = nil }
-
- assert_nothing_raised { @conn.site = "http://localhost" }
- assert_equal site, @conn.site
-
- assert_nothing_raised { @conn.site = site }
- assert_equal site, @conn.site
- end
-
- def test_proxy_accessor_accepts_uri_or_string_argument
- proxy = URI.parse("http://proxy_user:proxy_password@proxy.local:4242")
-
- assert_nothing_raised { @conn.proxy = "http://proxy_user:proxy_password@proxy.local:4242" }
- assert_equal proxy, @conn.proxy
-
- assert_nothing_raised { @conn.proxy = proxy }
- assert_equal proxy, @conn.proxy
- end
-
- def test_timeout_accessor
- @conn.timeout = 5
- assert_equal 5, @conn.timeout
- end
-
- def test_get
- matz = decode(@conn.get("/people/1.json"))
- assert_equal "Matz", matz["name"]
- end
-
- def test_head
- response = @conn.head("/people/1.json")
- assert response.body.blank?
- assert_equal 200, response.code
- end
-
- def test_get_with_header
- david = decode(@conn.get("/people/2.json", @header))
- assert_equal "David", david["name"]
- end
-
- def test_get_collection
- people = decode(@conn.get("/people.json"))
- assert_equal "Matz", people[0]["person"]["name"]
- assert_equal "David", people[1]["person"]["name"]
- end
-
- def test_get_collection_single
- people = decode(@conn.get("/people_single_elements.json"))
- assert_equal "Matz", people[0]["person"]["name"]
- end
-
- def test_get_collection_empty
- people = decode(@conn.get("/people_empty_elements.json"))
- assert_equal [], people
- end
-
- def test_post
- response = @conn.post("/people.json")
- assert_equal "/people/5.json", response["Location"]
- end
-
- def test_post_with_header
- response = @conn.post("/members.json", @header)
- assert_equal "/people/6.json", response["Location"]
- end
-
- def test_put
- response = @conn.put("/people/1.json")
- assert_equal 204, response.code
- end
-
- def test_put_with_header
- response = @conn.put("/people/2.json", @header)
- assert_equal 204, response.code
- end
-
- def test_delete
- response = @conn.delete("/people/1.json")
- assert_equal 200, response.code
- end
-
- def test_delete_with_header
- response = @conn.delete("/people/2.json", @header)
- assert_equal 200, response.code
- end
-
- def test_timeout
- @http = mock('new Net::HTTP')
- @conn.expects(:http).returns(@http)
- @http.expects(:get).raises(Timeout::Error, 'execution expired')
- assert_raise(ActiveResource::TimeoutError) { @conn.get('/people_timeout.json') }
- end
-
- def test_setting_timeout
- http = Net::HTTP.new('')
-
- [10, 20].each do |timeout|
- @conn.timeout = timeout
- @conn.send(:configure_http, http)
- assert_equal timeout, http.open_timeout
- assert_equal timeout, http.read_timeout
- end
- end
-
- def test_accept_http_header
- @http = mock('new Net::HTTP')
- @conn.expects(:http).returns(@http)
- path = '/people/1.xml'
- @http.expects(:get).with(path, { 'Accept' => 'application/xhtml+xml' }).returns(ActiveResource::Response.new(@matz, 200, { 'Content-Type' => 'text/xhtml' }))
- assert_nothing_raised(Mocha::ExpectationError) { @conn.get(path, { 'Accept' => 'application/xhtml+xml' }) }
- end
-
- def test_ssl_options_get_applied_to_http
- http = Net::HTTP.new('')
- @conn.site="https://secure"
- @conn.ssl_options={:verify_mode => OpenSSL::SSL::VERIFY_PEER}
- @conn.timeout = 10 # prevent warning about uninitialized.
- @conn.send(:configure_http, http)
-
- assert http.use_ssl?
- assert_equal http.verify_mode, OpenSSL::SSL::VERIFY_PEER
- end
-
- def test_ssl_error
- http = Net::HTTP.new('')
- @conn.expects(:http).returns(http)
- http.expects(:get).raises(OpenSSL::SSL::SSLError, 'Expired certificate')
- assert_raise(ActiveResource::SSLError) { @conn.get('/people/1.json') }
- end
-
- def test_auth_type_can_be_string
- @conn.auth_type = 'digest'
- assert_equal(:digest, @conn.auth_type)
- end
-
- def test_auth_type_defaults_to_basic
- @conn.auth_type = nil
- assert_equal(:basic, @conn.auth_type)
- end
-
- def test_auth_type_ignores_nonsensical_values
- @conn.auth_type = :wibble
- assert_equal(:basic, @conn.auth_type)
- end
-
- protected
- def assert_response_raises(klass, code)
- assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
- handle_response ResponseCodeStub.new(code)
- end
- end
-
- def assert_redirect_raises(code)
- assert_raise(ActiveResource::Redirection, "Expected response code #{code} to raise ActiveResource::Redirection") do
- handle_response RedirectResponseStub.new(code, 'http://example.com/')
- end
- end
-
- def handle_response(response)
- @conn.__send__(:handle_response, response)
- end
-
- def decode(response)
- @conn.format.decode(response.body)
- end
-end
diff --git a/activeresource/test/cases/finder_test.rb b/activeresource/test/cases/finder_test.rb
deleted file mode 100644
index 5fbbfeef6e..0000000000
--- a/activeresource/test/cases/finder_test.rb
+++ /dev/null
@@ -1,139 +0,0 @@
-require 'abstract_unit'
-require "fixtures/person"
-require "fixtures/customer"
-require "fixtures/street_address"
-require "fixtures/beast"
-require "fixtures/proxy"
-require 'active_support/core_ext/hash/conversions'
-
-class FinderTest < Test::Unit::TestCase
- def setup
- setup_response # find me in abstract_unit
- end
-
- def test_find_by_id
- matz = Person.find(1)
- assert_kind_of Person, matz
- assert_equal "Matz", matz.name
- assert matz.name?
- end
-
- def test_find_by_id_with_custom_prefix
- addy = StreetAddress.find(1, :params => { :person_id => 1 })
- assert_kind_of StreetAddress, addy
- assert_equal '12345 Street', addy.street
- end
-
- def test_find_all
- all = Person.find(:all)
- assert_equal 2, all.size
- assert_kind_of Person, all.first
- assert_equal "Matz", all.first.name
- assert_equal "David", all.last.name
- end
-
- def test_all
- all = Person.all
- assert_equal 2, all.size
- assert_kind_of Person, all.first
- assert_equal "Matz", all.first.name
- assert_equal "David", all.last.name
- end
-
- def test_all_with_params
- all = StreetAddress.all(:params => { :person_id => 1 })
- assert_equal 1, all.size
- assert_kind_of StreetAddress, all.first
- end
-
- def test_find_first
- matz = Person.find(:first)
- assert_kind_of Person, matz
- assert_equal "Matz", matz.name
- end
-
- def test_first
- matz = Person.first
- assert_kind_of Person, matz
- assert_equal "Matz", matz.name
- end
-
- def test_first_with_params
- addy = StreetAddress.first(:params => { :person_id => 1 })
- assert_kind_of StreetAddress, addy
- assert_equal '12345 Street', addy.street
- end
-
- def test_find_last
- david = Person.find(:last)
- assert_kind_of Person, david
- assert_equal 'David', david.name
- end
-
- def test_last
- david = Person.last
- assert_kind_of Person, david
- assert_equal 'David', david.name
- end
-
- def test_last_with_params
- addy = StreetAddress.last(:params => { :person_id => 1 })
- assert_kind_of StreetAddress, addy
- assert_equal '12345 Street', addy.street
- end
-
- def test_find_by_id_not_found
- assert_raise(ActiveResource::ResourceNotFound) { Person.find(99) }
- assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(99, :params => {:person_id => 1}) }
- end
-
- def test_find_all_sub_objects
- all = StreetAddress.find(:all, :params => { :person_id => 1 })
- assert_equal 1, all.size
- assert_kind_of StreetAddress, all.first
- end
-
- def test_find_all_sub_objects_not_found
- assert_nothing_raised do
- StreetAddress.find(:all, :params => { :person_id => 2 })
- end
- end
-
- def test_find_all_by_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.json", {}, @people_david }
-
- people = Person.find(:all, :from => "/companies/1/people.json")
- assert_equal 1, people.size
- assert_equal "David", people.first.name
- end
-
- def test_find_all_by_from_with_options
- ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.json", {}, @people_david }
-
- people = Person.find(:all, :from => "/companies/1/people.json")
- assert_equal 1, people.size
- assert_equal "David", people.first.name
- end
-
- def test_find_all_by_symbol_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/people/managers.json", {}, @people_david }
-
- people = Person.find(:all, :from => :managers)
- assert_equal 1, people.size
- assert_equal "David", people.first.name
- end
-
- def test_find_single_by_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/manager.json", {}, @david }
-
- david = Person.find(:one, :from => "/companies/1/manager.json")
- assert_equal "David", david.name
- end
-
- def test_find_single_by_symbol_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/people/leader.json", {}, @david }
-
- david = Person.find(:one, :from => :leader)
- assert_equal "David", david.name
- end
-end
diff --git a/activeresource/test/cases/format_test.rb b/activeresource/test/cases/format_test.rb
deleted file mode 100644
index 174142ec52..0000000000
--- a/activeresource/test/cases/format_test.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-require 'abstract_unit'
-require "fixtures/person"
-require "fixtures/street_address"
-
-class FormatTest < Test::Unit::TestCase
- def setup
- @matz = { :id => 1, :name => 'Matz' }
- @david = { :id => 2, :name => 'David' }
-
- @programmers = [ @matz, @david ]
- end
-
- def test_http_format_header_name
- header_name = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:get]
- assert_equal 'Accept', header_name
-
- headers_names = [ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:put], ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[:post]]
- headers_names.each{ |name| assert_equal 'Content-Type', name }
- end
-
- def test_formats_on_single_element
- for format in [ :json, :xml ]
- using_format(Person, format) do
- ActiveResource::HttpMock.respond_to.get "/people/1.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@david)
- assert_equal @david[:name], Person.find(1).name
- end
- end
- end
-
- def test_formats_on_collection
- for format in [ :json, :xml ]
- using_format(Person, format) do
- ActiveResource::HttpMock.respond_to.get "/people.#{format}", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode(@programmers)
- remote_programmers = Person.find(:all)
- assert_equal 2, remote_programmers.size
- assert remote_programmers.find { |p| p.name == 'David' }
- end
- end
- end
-
- def test_formats_on_custom_collection_method
- for format in [ :json, :xml ]
- using_format(Person, format) do
- ActiveResource::HttpMock.respond_to.get "/people/retrieve.#{format}?name=David", {'Accept' => ActiveResource::Formats[format].mime_type}, ActiveResource::Formats[format].encode([@david])
- remote_programmers = Person.get(:retrieve, :name => 'David')
- assert_equal 1, remote_programmers.size
- assert_equal @david[:id], remote_programmers[0]['id']
- assert_equal @david[:name], remote_programmers[0]['name']
- end
- end
- end
-
- def test_formats_on_custom_element_method
- [:json, :xml].each do |format|
- using_format(Person, format) do
- david = (format == :json ? { :person => @david } : @david)
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/2.#{format}", { 'Accept' => ActiveResource::Formats[format].mime_type }, ActiveResource::Formats[format].encode(david)
- mock.get "/people/2/shallow.#{format}", { 'Accept' => ActiveResource::Formats[format].mime_type }, ActiveResource::Formats[format].encode(david)
- end
-
- remote_programmer = Person.find(2).get(:shallow)
- assert_equal @david[:id], remote_programmer['id']
- assert_equal @david[:name], remote_programmer['name']
- end
-
- ryan_hash = { :name => 'Ryan' }
- ryan_hash = (format == :json ? { :person => ryan_hash } : ryan_hash)
- ryan = ActiveResource::Formats[format].encode(ryan_hash)
- using_format(Person, format) do
- remote_ryan = Person.new(:name => 'Ryan')
- ActiveResource::HttpMock.respond_to.post "/people.#{format}", { 'Content-Type' => ActiveResource::Formats[format].mime_type}, ryan, 201, { 'Location' => "/people/5.#{format}" }
- remote_ryan.save
-
- remote_ryan = Person.new(:name => 'Ryan')
- ActiveResource::HttpMock.respond_to.post "/people/new/register.#{format}", { 'Content-Type' => ActiveResource::Formats[format].mime_type}, ryan, 201, { 'Location' => "/people/5.#{format}" }
- assert_equal ActiveResource::Response.new(ryan, 201, { 'Location' => "/people/5.#{format}" }), remote_ryan.post(:register)
- end
- end
- end
-
- def test_setting_format_before_site
- resource = Class.new(ActiveResource::Base)
- resource.format = :json
- resource.site = 'http://37s.sunrise.i:3000'
- assert_equal ActiveResource::Formats[:json], resource.connection.format
- end
-
- def test_serialization_of_nested_resource
- address = { :street => '12345 Street' }
- person = { :name => 'Rus', :address => address}
-
- [:json, :xml].each do |format|
- encoded_person = ActiveResource::Formats[format].encode(person)
- assert_match(/12345 Street/, encoded_person)
- remote_person = Person.new(person.update({:address => StreetAddress.new(address)}))
- assert_kind_of StreetAddress, remote_person.address
- using_format(Person, format) do
- ActiveResource::HttpMock.respond_to.post "/people.#{format}", {'Content-Type' => ActiveResource::Formats[format].mime_type}, encoded_person, 201, {'Location' => "/people/5.#{format}"}
- remote_person.save
- end
- end
- end
-
- private
- def using_format(klass, mime_type_reference)
- previous_format = klass.format
- klass.format = mime_type_reference
-
- yield
- ensure
- klass.format = previous_format
- end
-end
diff --git a/activeresource/test/cases/http_mock_test.rb b/activeresource/test/cases/http_mock_test.rb
deleted file mode 100644
index d2fd911314..0000000000
--- a/activeresource/test/cases/http_mock_test.rb
+++ /dev/null
@@ -1,202 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/object/inclusion'
-
-class HttpMockTest < ActiveSupport::TestCase
- setup do
- @http = ActiveResource::HttpMock.new("http://example.com")
- end
-
- FORMAT_HEADER = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES
-
- [:post, :put, :get, :delete, :head].each do |method|
- test "responds to simple #{method} request" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/json" }, "Response")
- end
-
- assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
- end
-
- test "adds format header by default to #{method} request" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(method, "/people/1", {}, "Response")
- end
-
- assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
- end
-
- test "respond only when headers match header by default to #{method} request" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(method, "/people/1", {"X-Header" => "X"}, "Response")
- end
-
- assert_equal "Response", request(method, "/people/1", "X-Header" => "X").body
- assert_raise(ActiveResource::InvalidRequestError) { request(method, "/people/1") }
- end
-
- test "does not overwrite format header to #{method} request" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(method, "/people/1", {FORMAT_HEADER[method] => "application/json"}, "Response")
- end
-
- assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
- end
-
- test "ignores format header when there is only one response to same url in a #{method} request" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(method, "/people/1", {}, "Response")
- end
-
- assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
- assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/xml").body
- end
-
- test "responds correctly when format header is given to #{method} request" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/xml" }, "XML")
- mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/json" }, "Json")
- end
-
- assert_equal "XML", request(method, "/people/1", FORMAT_HEADER[method] => "application/xml").body
- assert_equal "Json", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
- end
-
- test "raises InvalidRequestError if no response found for the #{method} request" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(method, "/people/1", { FORMAT_HEADER[method] => "application/json" }, "json")
- end
-
- assert_raise(::ActiveResource::InvalidRequestError) do
- request(method, "/people/1", FORMAT_HEADER[method] => "application/xml")
- end
- end
-
- end
-
- test "allows you to send in pairs directly to the respond_to method" do
- matz = { :person => { :id => 1, :name => "Matz" } }.to_json
-
- create_matz = ActiveResource::Request.new(:post, '/people.json', matz, {})
- created_response = ActiveResource::Response.new("", 201, { "Location" => "/people/1.json" })
- get_matz = ActiveResource::Request.new(:get, '/people/1.json', nil)
- ok_response = ActiveResource::Response.new(matz, 200, {})
-
- pairs = {create_matz => created_response, get_matz => ok_response}
-
- ActiveResource::HttpMock.respond_to(pairs)
- assert_equal 2, ActiveResource::HttpMock.responses.length
- assert_equal "", ActiveResource::HttpMock.responses.assoc(create_matz)[1].body
- assert_equal matz, ActiveResource::HttpMock.responses.assoc(get_matz)[1].body
- end
-
- test "resets all mocked responses on each call to respond_to with a block by default" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(:get, "/people/1", {}, "JSON1")
- end
- assert_equal 1, ActiveResource::HttpMock.responses.length
-
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(:get, "/people/2", {}, "JSON2")
- end
- assert_equal 1, ActiveResource::HttpMock.responses.length
- end
-
- test "resets all mocked responses on each call to respond_to by passing pairs by default" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(:get, "/people/1", {}, "JSON1")
- end
- assert_equal 1, ActiveResource::HttpMock.responses.length
-
- matz = { :person => { :id => 1, :name => "Matz" } }.to_json
- get_matz = ActiveResource::Request.new(:get, '/people/1.json', nil)
- ok_response = ActiveResource::Response.new(matz, 200, {})
- ActiveResource::HttpMock.respond_to({get_matz => ok_response})
-
- assert_equal 1, ActiveResource::HttpMock.responses.length
- end
-
- test "allows you to add new responses to the existing responses by calling a block" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(:get, "/people/1", {}, "JSON1")
- end
- assert_equal 1, ActiveResource::HttpMock.responses.length
-
- ActiveResource::HttpMock.respond_to(false) do |mock|
- mock.send(:get, "/people/2", {}, "JSON2")
- end
- assert_equal 2, ActiveResource::HttpMock.responses.length
- end
-
- test "allows you to add new responses to the existing responses by passing pairs" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(:get, "/people/1", {}, "JSON1")
- end
- assert_equal 1, ActiveResource::HttpMock.responses.length
-
- matz = { :person => { :id => 1, :name => "Matz" } }.to_json
- get_matz = ActiveResource::Request.new(:get, '/people/1.json', nil)
- ok_response = ActiveResource::Response.new(matz, 200, {})
- ActiveResource::HttpMock.respond_to({get_matz => ok_response}, false)
-
- assert_equal 2, ActiveResource::HttpMock.responses.length
- end
-
- test "allows you to replace the existing reponse with the same request by calling a block" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(:get, "/people/1", {}, "JSON1")
- end
- assert_equal 1, ActiveResource::HttpMock.responses.length
-
- ActiveResource::HttpMock.respond_to(false) do |mock|
- mock.send(:get, "/people/1", {}, "JSON2")
- end
- assert_equal 1, ActiveResource::HttpMock.responses.length
- end
-
- test "allows you to replace the existing reponse with the same request by passing pairs" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(:get, "/people/1", {}, "JSON1")
- end
- assert_equal 1, ActiveResource::HttpMock.responses.length
-
- matz = { :person => { :id => 1, :name => "Matz" } }.to_json
- get_matz = ActiveResource::Request.new(:get, '/people/1', nil)
- ok_response = ActiveResource::Response.new(matz, 200, {})
-
- ActiveResource::HttpMock.respond_to({get_matz => ok_response}, false)
- assert_equal 1, ActiveResource::HttpMock.responses.length
- end
-
- test "do not replace the response with the same path but different method by calling a block" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(:get, "/people/1", {}, "JSON1")
- end
- assert_equal 1, ActiveResource::HttpMock.responses.length
-
- ActiveResource::HttpMock.respond_to(false) do |mock|
- mock.send(:put, "/people/1", {}, "JSON2")
- end
- assert_equal 2, ActiveResource::HttpMock.responses.length
- end
-
- test "do not replace the response with the same path but different method by passing pairs" do
- ActiveResource::HttpMock.respond_to do |mock|
- mock.send(:get, "/people/1", {}, "JSON1")
- end
- assert_equal 1, ActiveResource::HttpMock.responses.length
-
- put_matz = ActiveResource::Request.new(:put, '/people/1', nil)
- ok_response = ActiveResource::Response.new("", 200, {})
-
- ActiveResource::HttpMock.respond_to({put_matz => ok_response}, false)
- assert_equal 2, ActiveResource::HttpMock.responses.length
- end
-
- def request(method, path, headers = {}, body = nil)
- if method.in?([:put, :post])
- @http.send(method, path, body, headers)
- else
- @http.send(method, path, headers)
- end
- end
-end
diff --git a/activeresource/test/cases/log_subscriber_test.rb b/activeresource/test/cases/log_subscriber_test.rb
deleted file mode 100644
index ab5c22a783..0000000000
--- a/activeresource/test/cases/log_subscriber_test.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require "abstract_unit"
-require "fixtures/person"
-require "active_support/log_subscriber/test_helper"
-require "active_resource/log_subscriber"
-require "active_support/core_ext/hash/conversions"
-
-class LogSubscriberTest < ActiveSupport::TestCase
- include ActiveSupport::LogSubscriber::TestHelper
-
- def setup
- super
-
- @matz = { :person => { :id => 1, :name => 'Matz' } }.to_json
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.json", {}, @matz
- end
-
- ActiveResource::LogSubscriber.attach_to :active_resource
- end
-
- def set_logger(logger)
- ActiveResource::Base.logger = logger
- end
-
- def test_request_notification
- Person.find(1)
- wait
- assert_equal 2, @logger.logged(:info).size
- assert_equal "GET http://37s.sunrise.i:3000/people/1.json", @logger.logged(:info)[0]
- assert_match(/\-\-\> 200 200 33/, @logger.logged(:info)[1])
- end
-end
diff --git a/activeresource/test/cases/observing_test.rb b/activeresource/test/cases/observing_test.rb
deleted file mode 100644
index 58d3d389ff..0000000000
--- a/activeresource/test/cases/observing_test.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-require 'abstract_unit'
-require 'fixtures/person'
-require 'active_support/core_ext/hash/conversions'
-
-class ObservingTest < Test::Unit::TestCase
- cattr_accessor :history
-
- class PersonObserver < ActiveModel::Observer
- observe :person
-
- %w( after_create after_destroy after_save after_update
- before_create before_destroy before_save before_update).each do |method|
- define_method(method) { |*| log method }
- end
-
- private
- def log(method)
- (ObservingTest.history ||= []) << method.to_sym
- end
- end
-
- def setup
- @matz = { 'person' => { :id => 1, :name => 'Matz' } }.to_json
-
- ActiveResource::HttpMock.respond_to do |mock|
- mock.get "/people/1.json", {}, @matz
- mock.post "/people.json", {}, @matz, 201, 'Location' => '/people/1.json'
- mock.put "/people/1.json", {}, nil, 204
- mock.delete "/people/1.json", {}, nil, 200
- end
-
- PersonObserver.instance
- end
-
- def teardown
- self.history = nil
- end
-
- def test_create_fires_save_and_create_notifications
- Person.create(:name => 'Rick')
- assert_equal [:before_save, :before_create, :after_create, :after_save], self.history
- end
-
- def test_update_fires_save_and_update_notifications
- person = Person.find(1)
- person.save
- assert_equal [:before_save, :before_update, :after_update, :after_save], self.history
- end
-
- def test_destroy_fires_destroy_notifications
- person = Person.find(1)
- person.destroy
- assert_equal [:before_destroy, :after_destroy], self.history
- end
-end
diff --git a/activeresource/test/cases/validations_test.rb b/activeresource/test/cases/validations_test.rb
deleted file mode 100644
index c6c6e1d786..0000000000
--- a/activeresource/test/cases/validations_test.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require 'abstract_unit'
-require 'fixtures/project'
-require 'active_support/core_ext/hash/conversions'
-
-# The validations are tested thoroughly under ActiveModel::Validations
-# This test case simply makes sure that they are all accessible by
-# Active Resource objects.
-class ValidationsTest < ActiveModel::TestCase
- VALID_PROJECT_HASH = { :name => "My Project", :description => "A project" }
- def setup
- @my_proj = { "person" => VALID_PROJECT_HASH }.to_json
- ActiveResource::HttpMock.respond_to do |mock|
- mock.post "/projects.json", {}, @my_proj, 201, 'Location' => '/projects/5.json'
- end
- end
-
- def test_validates_presence_of
- p = new_project(:name => nil)
- assert !p.valid?, "should not be a valid record without name"
- assert !p.save, "should not have saved an invalid record"
- assert_equal ["can't be blank"], p.errors[:name], "should have an error on name"
-
- p.name = "something"
-
- assert p.save, "should have saved after fixing the validation, but had: #{p.errors.inspect}"
- end
-
- def test_fails_save!
- p = new_project(:name => nil)
- assert_raise(ActiveResource::ResourceInvalid) { p.save! }
- end
-
- def test_save_without_validation
- p = new_project(:name => nil)
- assert !p.save
- assert p.save(:validate => false)
- end
-
- def test_validate_callback
- # we have a callback ensuring the description is longer than three letters
- p = new_project(:description => 'a')
- assert !p.valid?, "should not be a valid record when it fails a validation callback"
- assert !p.save, "should not have saved an invalid record"
- assert_equal ["must be greater than three letters long"], p.errors[:description], "should be an error on description"
-
- # should now allow this description
- p.description = 'abcd'
- assert p.save, "should have saved after fixing the validation, but had: #{p.errors.inspect}"
- end
-
- def test_client_side_validation_maximum
- project = Project.new(:description => '123456789012345')
- assert ! project.valid?
- assert_equal ['is too long (maximum is 10 characters)'], project.errors[:description]
- end
-
- protected
-
- # quickie helper to create a new project with all the required
- # attributes.
- # Pass in any params you specifically want to override
- def new_project(opts = {})
- Project.new(VALID_PROJECT_HASH.merge(opts))
- end
-
-end
-
diff --git a/activeresource/test/fixtures/address.rb b/activeresource/test/fixtures/address.rb
deleted file mode 100644
index 7a73ecb52a..0000000000
--- a/activeresource/test/fixtures/address.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# turns everything into the same object
-class AddressXMLFormatter
- include ActiveResource::Formats::XmlFormat
-
- def decode(xml)
- data = ActiveResource::Formats::XmlFormat.decode(xml)
- # process address fields
- data.each do |address|
- address['city_state'] = "#{address['city']}, #{address['state']}"
- end
- data
- end
-
-end
-
-class AddressResource < ActiveResource::Base
- self.element_name = "address"
- self.format = AddressXMLFormatter.new
-end \ No newline at end of file
diff --git a/activeresource/test/fixtures/beast.rb b/activeresource/test/fixtures/beast.rb
deleted file mode 100644
index e31ec58346..0000000000
--- a/activeresource/test/fixtures/beast.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-class BeastResource < ActiveResource::Base
- self.site = 'http://beast.caboo.se'
- site.user = 'foo'
- site.password = 'bar'
-end
-
-class Forum < BeastResource
- # taken from BeastResource
- # self.site = 'http://beast.caboo.se'
-end
-
-class Topic < BeastResource
- self.site += '/forums/:forum_id'
-end
diff --git a/activeresource/test/fixtures/customer.rb b/activeresource/test/fixtures/customer.rb
deleted file mode 100644
index 845d5d11cb..0000000000
--- a/activeresource/test/fixtures/customer.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Customer < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
-end
diff --git a/activeresource/test/fixtures/person.rb b/activeresource/test/fixtures/person.rb
deleted file mode 100644
index e88bb69310..0000000000
--- a/activeresource/test/fixtures/person.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Person < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
-end
diff --git a/activeresource/test/fixtures/project.rb b/activeresource/test/fixtures/project.rb
deleted file mode 100644
index 53de666601..0000000000
--- a/activeresource/test/fixtures/project.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# used to test validations
-class Project < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- schema do
- string :email
- string :name
- end
-
- validates :name, :presence => true
- validates :description, :presence => false, :length => {:maximum => 10}
- validate :description_greater_than_three_letters
-
- # to test the validate *callback* works
- def description_greater_than_three_letters
- errors.add :description, 'must be greater than three letters long' if description.length < 3 unless description.blank?
- end
-end
-
diff --git a/activeresource/test/fixtures/proxy.rb b/activeresource/test/fixtures/proxy.rb
deleted file mode 100644
index bb8e015df0..0000000000
--- a/activeresource/test/fixtures/proxy.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-class ProxyResource < ActiveResource::Base
- self.site = "http://localhost"
- self.proxy = "http://user:password@proxy.local:3000"
-end \ No newline at end of file
diff --git a/activeresource/test/fixtures/sound.rb b/activeresource/test/fixtures/sound.rb
deleted file mode 100644
index d9d2b99fcd..0000000000
--- a/activeresource/test/fixtures/sound.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-module Asset
- class Sound < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- end
-end
-
-# to test namespacing in a module
-class Author
-end \ No newline at end of file
diff --git a/activeresource/test/fixtures/street_address.rb b/activeresource/test/fixtures/street_address.rb
deleted file mode 100644
index 94a86702b0..0000000000
--- a/activeresource/test/fixtures/street_address.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-class StreetAddress < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000/people/:person_id/"
- self.element_name = 'address'
-end
diff --git a/activeresource/test/fixtures/subscription_plan.rb b/activeresource/test/fixtures/subscription_plan.rb
deleted file mode 100644
index e3c2dd9a74..0000000000
--- a/activeresource/test/fixtures/subscription_plan.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class SubscriptionPlan < ActiveResource::Base
- self.site = "http://37s.sunrise.i:3000"
- self.element_name = 'plan'
- self.primary_key = :code
-end
diff --git a/activeresource/test/setter_trap.rb b/activeresource/test/setter_trap.rb
deleted file mode 100644
index 7cfd9ca111..0000000000
--- a/activeresource/test/setter_trap.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-class SetterTrap < ActiveSupport::BasicObject
- class << self
- def rollback_sets(obj)
- trapped = new(obj)
- yield(trapped).tap { trapped.rollback_sets }
- end
- end
-
- def initialize(obj)
- @cache = {}
- @obj = obj
- end
-
- def respond_to?(method)
- @obj.respond_to?(method)
- end
-
- def method_missing(method, *args, &proc)
- @cache[method] ||= @obj.send($`) if method.to_s =~ /=$/
- @obj.send method, *args, &proc
- end
-
- def rollback_sets
- @cache.each { |k, v| @obj.send k, v }
- end
-end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
deleted file mode 100644
index 63f406cd9f..0000000000
--- a/activesupport/CHANGELOG
+++ /dev/null
@@ -1,1558 +0,0 @@
-*Rails 3.2.0 (unreleased)*
-
-* Added safe_constantize that constantizes a string but returns nil instead of an exception if the constant (or part of it) does not exist [Ryan Oblak]
-
-* ActiveSupport::OrderedHash is now marked as extractable when using Array#extract_options! [Prem Sichanugrist]
-
-* Added Array#prepend as an alias for Array#unshift and Array#append as an alias for Array#<< [DHH]
-
-* The definition of blank string for Ruby 1.9 has been extended to Unicode whitespace.
-Also, in 1.8 the ideographic space U+3000 is considered to be whitespace. [Akira Matsuda, Damien Mathieu]
-
-* The inflector understands acronyms. [dlee]
-
-* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim]
-
-* Added Time#all_day/week/quarter/year as a way of generating ranges (example: Event.where(created_at: Time.now.all_week)) [DHH]
-
-* Added instance_accessor: false as an option to Class#cattr_accessor and friends [DHH]
-
-* Removed ActiveSupport::SecureRandom in favor of SecureRandom from the standard library [Jon Leighton]
-
-* ActiveSupport::OrderedHash now has different behavior for #each and
-#each_pair when given a block accepting its parameters with a splat. [Andrew Radev]
-
-*Rails 3.1.0 (August 30, 2011)*
-
-* ActiveSupport::Dependencies#load and ActiveSupport::Dependencies#require now
-return the value from `super` [Aaron Patterson]
-
-* Fixed ActiveSupport::Gzip to work properly in Ruby 1.8 [Guillermo Iguaran]
-
-* Kernel.require_library_or_gem was deprecated and will be removed in Rails 3.2.0 [Josh Kalderimis]
-
-* ActiveSupport::Duration#duplicable? was fixed for Ruby 1.8 [thedarkone]
-
-* ActiveSupport::BufferedLogger set log encoding to BINARY, but still use text
-mode to output portable newlines. [fxn]
-
-* ActiveSupport::Dependencies now raises NameError if it finds an existing constant in load_missing_constant. This better reflects the nature of the error which is usually caused by calling constantize on a nested constant. [Andrew White]
-
-* Deprecated ActiveSupport::SecureRandom in favour of SecureRandom from the standard library [Jon Leighton]
-
-* New reporting method Kernel#quietly. [fxn]
-
-* Add String#inquiry as a convenience method for turning a string into a StringInquirer object [DHH]
-
-* 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.
-
-* ActiveSupport::Dependencies::ClassCache class has been introduced for
-holding references to reloadable classes.
-
-* ActiveSupport::Dependencies::Reference has been refactored to take direct
-advantage of the new ClassCache.
-
-* Backports Range#cover? as an alias for Range#include? in Ruby 1.8 [Diego Carrion, fxn]
-
-* Added weeks_ago and prev_week to Date/DateTime/Time. [Rob Zolkos, fxn]
-
-* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White]
-
-* JSON decoding now uses the multi_json gem which also vendors a json engine called OkJson. The yaml backend has been removed in favor of OkJson as a default engine for 1.8.x, while the built in 1.9.x json implementation will be used by default. [Josh Kalderimis]
-
-
-*Rails 3.0.7 (April 18, 2011)*
-
-* Hash.from_xml no longer loses attributes on tags containing only whitespace [André Arko]
-
-
-*Rails 3.0.6 (April 5, 2011)
-
-* No changes.
-
-
-*Rails 3.0.5 (February 26, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.4 (February 8, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.3 (November 16, 2010)*
-
-* No changes.
-
-
-*Rails 3.0.2 (November 15, 2010)*
-
-* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White]
-
-
-*Rails 3.0.1 (October 15, 2010)*
-
-* No Changes, just a version bump.
-
-
-*Rails 3.0.0 (August 29, 2010)*
-
-* Implemented String#strip_heredoc. [fxn]
-
-* Pluggable cache stores: setting config.cache_store = "custom_store" will require 'active_support/cache/custom_store' and look for the CustomStore constant. #5486 [Mike Perham]
-
-* Removed Object#returning, Object#tap should be used instead. [Santiago Pastorino]
-
-* Deprecation behavior is no longer hardcoded to the name of the environment.
- Instead, it is set via config.active_support.deprecation and can be one
- of :log, :stderr or :notify. :notify is a new style that sends the warning
- via ActiveSupport::Notifications, and is the new default for production
- [Yehuda Katz]
-
-* Renamed ActiveSupport::Dependecies.load_(once_)paths to autoload_(once_)paths. [fxn]
-
-* Added ActiveSupport::FileUpdateChecker to execute a block only if a set of files changed, used by Router and I18n locale files. [José Valim]
-
-* Added ActiveSupport::DescendantsTracker to track descendants with support to constants reloading. [José Valim]
-
-* ActiveSupport::OrderedHash#merge and #merge! accept a block. #4838 [Paul Mucur, fxn]
-
-* Date#since, #ago, #beginning_of_day, #end_of_day, and #xmlschema honor now the user time zone if set. [Geoff Buesing]
-
-* Extracted String#truncate from TextHelper#truncate [DHH]
-
-* Ruby 1.9: support UTF-8 case folding. #4595 [Norman Clarke]
-
-* Removes Array#rand and backports Array#sample from Ruby 1.9, thanks to Marc-Andre Lafortune. [fxn]
-
-* Ruby 1.9: Renames last_(month|year) to prev_(month|year) in Date and Time. [fxn]
-
-* Aliases Date#sunday to Date#end_of_week. [fxn]
-
-* Backports Date#>> from 1.9 so that calculations do the right thing around the calendar reform. [fxn]
-
-* Date#to_time handles properly years in the range 0..138. [fxn]
-
-* Deprecate {{}} as interpolation syntax for I18n in favor of %{} [José Valim]
-
-* Array#to_xml is more powerful and able to handle the same types as Hash#to_xml #4490 [Neeraj Singh]
-
-* Harmonize the caching API and refactor the backends. #4452 [Brian Durand]
- All caches:
- * Add default options to initializer that will be sent to all read, write, fetch, exist?, increment, and decrement
- * Add support for the :expires_in option to fetch and write for all caches. Cache entries are stored with the create timestamp and a ttl so that expiration can be handled independently of the implementation.
- * Add support for a :namespace option. This can be used to set a global prefix for cache entries.
- * Deprecate expand_cache_key on ActiveSupport::Cache and move it to ActionController::Caching and ActionDispatch::Http::Cache since the logic in the method used some Rails specific environment variables and was only used by ActionPack classes. Not very DRY but there didn't seem to be a good shared spot and ActiveSupport really shouldn't be Rails specific.
- * Add support for :race_condition_ttl to fetch. This setting can prevent race conditions on fetch calls where several processes try to regenerate a recently expired entry at once.
- * Add support for :compress option to fetch and write which will compress any data over a configurable threshold.
- * Nil values can now be stored in the cache and are distinct from cache misses for fetch.
- * Easier API to create new implementations. Just need to implement the methods read_entry, write_entry, and delete_entry instead of overwriting existing methods.
- * Since all cache implementations support storing objects, update the docs to state that ActiveCache::Cache::Store implementations should store objects. Keys, however, must be strings since some implementations require that.
- * Increase test coverage.
- * Document methods which are provided as convenience but which may not be universally available.
-
- MemoryStore:
- * MemoryStore can now safely be used as the cache for single server sites.
- * Make thread safe so that the default cache implementation used by Rails is thread safe. The overhead is minimal and it is still the fastest store available.
- * Provide :size initialization option indicating the maximum size of the cache in memory (defaults to 32Mb).
- * Add prune logic that removes the least recently used cache entries to keep the cache size from exceeding the max.
- * Deprecated SynchronizedMemoryStore since it isn't needed anymore.
-
- FileStore:
- * Escape key values so they will work as file names on all file systems, be consistent, and case sensitive
- * Use a hash algorithm to segment the cache into sub directories so that a large cache doesn't exceed file system limits.
- * FileStore can be slow so implement the LocalCache strategy to cache reads for the duration of a request.
- * Add cleanup method to keep the disk from filling up with expired entries.
- * Fix increment and decrement to use file system locks so they are consistent between processes.
-
- MemCacheStore:
- * Support all keys. Previously keys with spaces in them would fail
- * Deprecate CompressedMemCacheStore since it isn't needed anymore (use :compress => true)
-
-* JSON: encode objects that don't have a native JSON representation using to_hash, if available, instead of instance_values (the old fallback) or to_s (other encoders' default). Encode BigDecimal and Regexp encode as strings to conform with other encoders. Try to transcode non-UTF-8 strings. [Jeremy Kemper]
-
-* HashWithIndifferentAccess: remove inherited symbolize_keys! since its keys are always strings. [Santiago Pastorino]
-
-* Improve transliteration quality. #4374 [Norman Clarke]
-
-* Speed up and add Ruby 1.9 support for ActiveSupport::Multibyte::Chars#tidy_bytes. #4350 [Norman Clarke]
-
-* Reduced load time by deferring configuration of classes using
- ActiveSupport::on_load(:component_name) [YK]
-
-* Rename #metaclass to #singleton_class now that ruby-core has decided [JK]
-
-* New assertions assert_blank and assert_present. #4299 [Juanjo Bazan]
-
-* Use Object#singleton_class instead of #metaclass. Prefer Ruby's choice. [Jeremy Kemper]
-
-* JSON backend for YAJL. Preferred if available. #2666 [Brian Lopez]
-
-* Introduce class_attribute to declare inheritable class attributes. Writing an attribute on a subclass behaves just like overriding the superclass reader method. Unifies and replaces most usage of cattr_accessor, class_inheritable_attribute, superclass_delegating_attribute, and extlib_inheritable_attribute. [Jeremy Kemper, Yehuda Katz]
-
-* Time#- with a DateTime argument behaves the same as with a Time argument, i.e. returns the difference between self and arg as a Float #3476 [Geoff Buesing]
-
-* YAML serialization for OrderedHash. #3608 [Gregor Schmidt]
-
-* Update bundled TZInfo to v0.3.16 [Geoff Buesing]
-
-* Georgetown TimeZone is now mapped to "America/Guyana" instead of "America/Argentina/San_Juan" #1821 [Geoff Buesing, Reuben Sivan]
-
-* Changed the default ActiveSupport.use_standard_json_time_format from false to true and
-ActiveSupport.escape_html_entities_in_json from true to false to match previously announced Rails 3 defaults [DHH]
-
-* Added Object#presence that returns the object if it's #present? otherwise returns nil [DHH/Colin Kelley]
-
-* Add Enumerable#exclude? to bring parity to Enumerable#include? and avoid if !x.include?/else calls [DHH]
-
-* Update Edinburgh TimeZone to use "Europe/London" instead of "Europe/Dublin" #3310 [Phil Ross]
-
-* Update bundled TZInfo to v0.3.15 [Geoff Buesing]
-
-* JSON: +Object#to_json+ calls +as_json+ to coerce itself into something natively encodable like +Hash+, +Integer+, or +String+. Override +as_json+ instead of +to_json+ so you're JSON library agnostic. [Jeremy Kemper]
-
-* String #to_time and #to_datetime: handle fractional seconds #864 [Jason Frey]
-
-* Update bundled TZInfo to v0.3.13 [Geoff Buesing]
-
-* Allow MemCacheStore to be initialized with a MemCache-like object instead of addresses and options [Bryan Helmkamp]
-
-* Change spelling of Kyev timezone to Kyiv #2613 [Alexander Dymo]
-
-* Add ActiveSupport.parse_json_times to disable time parsing in JSON backends that don't support it or don't need it. [rick]
-
-* Add pluggable JSON backends with support for the JSON gem. [rick]
- Example: ActiveSupport::JSON.backend = "JSONGem"
-
- All internal Rails JSON encoding is now handled by ActiveSupport::JSON.encode(). Use of #to_json is not recommended, as it may clash with other libraries that overwrite it. However, you can recover Rails specific functionality
- if you really want to use #to_json.
-
- gem 'json'
- ActiveSupport::JSON.backend = "JSONGem"
-
- class ActiveRecord::Base
- alias to_json rails_to_json
- end
-
-* require 'active_support' no longer orders the whole menu of core extensions. Ask for just what you need: e.g. require 'active_support/core/time' to use timezones, durations, and stdlib date/time extensions. [Jeremy Kemper]
-
-* Removed rarely-used DRb cache store. [Jeremy Kemper]
-
-* TimeWithZone.name returns 'Time', to further thwart type checking [Geoff Buesing]
-
-* Time.local instances: Adding 24.hours across the DST boundary adds 24 hours instead of one day #2066 [Michael Curtis]
-
-
-*2.3.2 [Final] (March 15, 2009)*
-
-* XmlMini supports LibXML and Nokogiri backends. #2084, #2190 [Bart ten Brinke, Aaron Patterson]
- Example: XmlMini.backend = 'Nokogiri'
-
-* Vendorize i18n 0.1.3 gem (fixes issues with incompatible character encodings in Ruby 1.9) #2038 [Akira Matsuda]
-
-* Update bundled memcache-client from 1.5.0.5 to 1.6.4.99. See http://www.mikeperham.com/2009/02/15/memcache-client-performance/ [Mike Perham]
-
-* Ruby 1.9.1p0 fix: URI.unescape can decode multibyte chars. #2033 [MOROHASHI Kyosuke]
-
-* Time#to_s(:rfc822) uses #formatted_offset instead of unreliable and non-standard %z directive #1899 [Zachary Zolton]
-
-* Make TimeWithZone#to_formatted_s an alias to TimeWithZone#to_s #1796 [Levin Alexander]
-
-* Introduce Array.wrap(foo) to wrap the argument in an array unless it's already an array. Wraps nil as an empty array. Use instead of Array(foo) and foo.to_a since they treat String as Enumerable. [Jeremy Kemper]
-
-* TimeWithZone#xmlschema accepts optional fraction_digits argument [#1725 state:resolved] [Nicholas Dainty]
-
-* Object#tap shim for Ruby < 1.8.7. Similar to Object#returning, tap yields self then returns self. [Jeremy Kemper]
- array.select { ... }.tap(&:inspect).map { ... }
-
-* TimeWithZone#- gives correct result with wrapped DateTime, and with DateTime argument [Geoff Buesing]
-
-* Updated i18n gem to version 0.1.1 #1635 [Yaroslav Markin]
-
-* Add :allow_nil option to delegate. #1127 [Sergio Gil]
-
-* Add Benchmark.ms convenience method to benchmark realtime in milliseconds. [Jeremy Kemper]
-
-* Updated included memcache-client to the 1.5.0.5 version which includes fixes from fiveruns and 37signals to deal with failover and timeouts #1535 [Joshua Sierles]
-
-* Multibyte: add multibyte-safe Chars#ord rather than falling back to String#ord. #1483 [Jason Cheow]
-
-* I18n support for Array#to_sentence. Introduces support.array.words_connector, .two_words_connector, and .last_word_connector translation keys. #1397 [Akira Matsuda]
-
-* Added ActiveSupport::OrderedHash#each_key and ActiveSupport::OrderedHash#each_value #1410 [Christoffer Sawicki]
-
-* Added ActiveSupport::MessageVerifier and MessageEncryptor to aid users who need to store signed and/or encrypted messages. [Michael Koziarski]
-
-* Added ActiveSupport::BacktraceCleaner to cut down on backtrace noise according to filters and silencers [David Heinemeier Hansson]
-
-* Added Object#try. ( Taken from http://ozmm.org/posts/try.html ) [Chris Wanstrath]
-
-* Added Enumerable#none? to check that none of the elements match the block #1408 [Damian Janowski]
-
-* TimeZone offset tests: use current_period, to ensure TimeZone#utc_offset is up-to-date [Geoff Buesing]
-
-* Update bundled TZInfo to 0.3.12 [Geoff Buesing]
-
-* Added lambda merging to OptionMerger (especially useful with named_scope and with_options) #726 [Paweł Kondzior]
-
-
-*2.2.1 [RC2] (November 14th, 2008)*
-
-* Increment the version of our altered memcache-client to prevent confusion caused when the 1.5.0 gem is installed.
-
-* Fixed the option merging in Array#to_xml #1126 [Rudolf Gavlas]
-
-* Make I18n::Backend::Simple reload its translations in development mode [David Heinemeier Hansson/Sven Fuchs]
-
-
-*2.2.0 [RC1] (October 24th, 2008)*
-
-* TimeWithZone#freeze: preload instance variables so that we can actually freeze [Geoff Buesing]
-
-* Fix Brasilia timezone #1180 [Marcus Derencius, Kane]
-
-* Time#advance recognizes fractional days and weeks. Deprecate Durations of fractional months and years #970 [Tom Lea]
-
-* Add ActiveSupport::Rescuable module abstracting ActionController::Base rescue_from features. [Norbert Crombach, Pratik Naik]
-
-* Switch from String#chars to String#mb_chars for the unicode proxy. [Manfred Stienstra]
-
- This helps with 1.8.7 compatibility and also improves performance for some operations by reducing indirection.
-
-* TimeWithZone #wday, #yday and #to_date avoid trip through #method_missing [Geoff Buesing]
-
-* Added Time, Date, DateTime and TimeWithZone #past?, #future? and #today? #720 [Clemens Kofler, Geoff Buesing]
-
-* Fixed Sri Jayawardenepura time zone to map to Asia/Colombo [Jamis Buck]
-
-* Added Inflector#parameterize for easy slug generation ("Donald E. Knuth".parameterize => "donald-e-knuth") #713 [Matt Darby]
-
-* Changed cache benchmarking to be reported in milliseconds [David Heinemeier Hansson]
-
-* Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are now correctly unmarshaled with a utc zone instead of the system local zone [#900 state:resolved] [Luca Guidi, Geoff Buesing]
-
-* Add Array#in_groups which splits or iterates over the array in specified number of groups. #579. [Adrian Mugnolo] Example:
-
- a = (1..10).to_a
- a.in_groups(3) # => [[1, 2, 3, 4], [5, 6, 7, nil], [8, 9, 10, nil]]
- a.in_groups(3, false) # => [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
-
-* Fix TimeWithZone unmarshaling: coerce unmarshaled Time instances to utc, because Ruby's marshaling of Time instances doesn't respect the zone [Geoff Buesing]
-
-* Added Memoizable mixin for caching simple lazy loaded attributes [Josh Peek]
-
-* Move the test related core_ext stuff out of core_ext so it's only loaded by the test helpers. [Michael Koziarski]
-
-* Add Inflection rules for String#humanize. #535 [Dan Manges]
-
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.human(/_cnt$/i, '\1_count')
- end
-
- 'jargon_cnt'.humanize # => 'Jargon count'
-
-* TimeWithZone: when crossing DST boundary, treat Durations of days, months or years as variable-length, and all other values as absolute length. A time + 24.hours will advance exactly 24 hours, but a time + 1.day will advance 23-25 hours, depending on the day. Ensure consistent behavior across all advancing methods [Geoff Buesing]
-
-* Added TimeZone #=~, to support matching zones by regex in time_zone_select. #195 [Ernie Miller]
-
-* Added Array#second through Array#fifth as aliases for Array#[1] through Array#[4] + Array#forty_two as alias for Array[41] [David Heinemeier Hansson]
-
-* Added test/do declaration style testing to ActiveSupport::TestCase [DHH via Jay Fields]
-
-* Added Object#present? which is equivalent to !Object#blank? [David Heinemeier Hansson]
-
-* Added Enumberable#many? to encapsulate collection.size > 1 [David Heinemeier Hansson/Damian Janowski]
-
-* Add more standard Hash methods to ActiveSupport::OrderedHash [Steve Purcell]
-
-* Namespace Inflector, Dependencies, OrderedOptions, and TimeZone under ActiveSupport [Josh Peek]
-
-* Added StringInquirer for doing things like StringInquirer.new("production").production? # => true and StringInquirer.new("production").development? # => false [David Heinemeier Hansson]
-
-* Fixed Date#end_of_quarter to not blow up on May 31st [#289 state:resolved] (Danger)
-
-
-*2.1.0 (May 31st, 2008)*
-
-* TimeZone#to_s shows offset as GMT instead of UTC, because GMT will be more familiar to end users (see time zone selects used by Windows OS, google.com and yahoo.com.) Reverts [8370] [Geoff Buesing]
-
-* Hash.from_xml: datetime xml types overflow to Ruby DateTime class when out of range of Time. Adding tests for utc offsets [Geoff Buesing]
-
-* TimeWithZone #+ and #- : ensure overflow to DateTime with Numeric arg [Geoff Buesing]
-
-* Time#to_json: don't convert to utc before encoding. References #175 [Geoff Buesing]
-
-* Remove unused JSON::RESERVED_WORDS, JSON.valid_identifier? and JSON.reserved_word? methods. Resolves #164. [Cheah Chu Yeow]
-
-* Adding Date.current, which returns Time.zone.today if config.time_zone is set; otherwise returns Date.today [Geoff Buesing]
-
-* TimeWithZone: date part getter methods (#year #mon #day etc) are defined on class; no longer relying on method_missing [Geoff Buesing]
-
-* Time.zone.parse return nil for strings with no date information [Geoff Buesing]
-
-* Time.zone.parse respects offset information in string. Resolves #105. [Scott Fleckenstein, Geoff Buesing]
-
-* Added Ruby 1.8 implementation of Process.daemon
-
-* Duration #since and #ago with no argument (e.g., 5.days.ago) return TimeWithZone when config.time_zone is set. Introducing Time.current, which returns Time.zone.now if config.time_zone is set, otherwise just returns Time.now [Geoff Buesing]
-
-* Time#since behaves correctly when passed a Duration. Closes #11527 [kemiller]
-
-* Add #getutc alias for DateTime#utc [Geoff Buesing]
-
-* Refactor TimeWithZone: don't send #since, #ago, #+, #-, #advance through method_missing [Geoff Buesing]
-
-* TimeWithZone respects config.active_support.use_standard_json_time_format [Geoff Buesing]
-
-* Add config.active_support.escape_html_entities_in_json to allow disabling of html entity escaping. [Rick Olson]
-
-* Improve documentation. [Xavier Noria]
-
-* Modified ActiveSupport::Callbacks::Callback#call to accept multiple arguments.
-
-* Time #yesterday and #tomorrow behave correctly crossing DST boundary. Closes #7399 [sblackstone]
-
-* TimeWithZone: Adding tests for dst and leap day edge cases when advancing time [Geoff Buesing]
-
-* TimeWithZone#method_missing: send to utc to advance with dst correctness, otherwise send to time. Adding tests for time calculations methods [Geoff Buesing]
-
-* Add config.active_support.use_standard_json_time_format setting so that Times and Dates export to ISO 8601 dates. [Rick Olson]
-
-* TZInfo: Removing unneeded TimezoneProxy class [Geoff Buesing]
-
-* TZInfo: Removing unneeded TimezoneIndexDefinition, since we're not including Indexes::Timezones [Geoff Buesing]
-
-* Removing unnecessary uses_tzinfo helper from tests, given that TZInfo is now bundled [Geoff Buesing]
-
-* Bundling abbreviated version of TZInfo gem 0.3.8: only the classes and zone definitions required to support Rails time zone features are included. If a recent version of the full TZInfo gem is installed, this will take precedence over the bundled version [Geoff Buesing]
-
-* TimeWithZone#marshal_load does zone lookup via Time.get_zone, so that tzinfo/Olson identifiers are handled [Geoff Buesing]
-
-* Time.zone= accepts TZInfo::Timezone instances and Olson identifiers; wraps result in TimeZone instance [Geoff Buesing]
-
-* TimeWithZone time conversions don't need to be wrapped in TimeOrDateTime, because TZInfo does this internally [Geoff Buesing]
-
-* TimeWithZone#usec returns 0 instead of error when DateTime is wrapped [Geoff Buesing]
-
-* Improve documentation. [Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert]
-
-* Ensure that TimeWithZone#to_yaml works when passed a YAML::Emitter. [Rick Olson]
-
-* Ensure correct TimeWithZone#to_date [Geoff Buesing]
-
-* Make TimeWithZone work with tzinfo 0.2.x: use TZInfo::Timezone#zone_identifier alias for #abbreviation, silence warnings on tests. Raise LoadError when TZInfo version is < 0.2 by sniffing for TZInfo::TimeOrDateTime constant. Move all tzinfo-dependent TimeZone tests into uses_tzinfo block [Geoff Buesing]
-
-* Time, DateTime and TimeWithZone #in_time_zone defaults to Time.zone. Removing now unneeded #in_current_time_zone [Geoff Buesing]
-
-* TZInfo caches Timezone instances in its own internal hash cache, so TimeZone::MAPPING doesn't need to cache them as well [Geoff Buesing]
-
-* Adding TimeZone#parse [Geoff Buesing]
-
-* Adding TimeZone#at and DateTime#to_f [Geoff Buesing]
-
-* TimeWithZone responds to Ruby 1.9 weekday-named query methods [Geoff Buesing]
-
-* TimeWithZone caches TZInfo::TimezonePeriod used for time conversion so that it can be reused, and enforces DST rules correctly when instance is created from a local time [Geoff Buesing]
-
-* Fixed that BufferedLogger should create its own directory if one doesn't already exist #11285 [lotswholetime]
-
-* Fix Numeric time tests broken by DST change by anchoring them to fixed times instead of Time.now. Anchor TimeZone#now DST test to time specified with Time.at instead of Time.local to work around platform differences with Time.local and DST representation [Geoff Buesing]
-
-* Removing unneeded #change_time_zone method from Time, DateTime and TimeWithZone [Geoff Buesing]
-
-* TimeZone #local and #now correctly enforce DST rules [Geoff Buesing]
-
-* TimeWithZone instances correctly enforce DST rules. Adding TimeZone#period_for_utc [Geoff Buesing]
-
-* test_time_with_datetime_fallback expects DateTime.local_offset instead of DateTime.now.offset [Geoff Buesing]
-
-* Adding TimeWithZone #marshal_dump and #marshal_load [Geoff Buesing]
-
-* Add OrderedHash#to_hash [Josh Peek]
-
-* Adding Time#end_of_day, _quarter, _week, and _year. #9312 [Juanjo Bazan, Tarmo Tänav, BigTitus]
-
-* Adding TimeWithZone#between? [Geoff Buesing]
-
-* Time.=== returns true for TimeWithZone instances [Geoff Buesing]
-
-* TimeWithZone #+ and #- behave consistently with numeric arguments regardless of whether wrapped time is a Time or DateTime; consistenty answers false to #acts_like?(:date) [Geoff Buesing]
-
-* Add String#squish and String#squish! to remove consecutive chunks of whitespace. #11123 [Jordi Bunster, Henrik N]
-
-* Serialize BigDecimals as Floats when using to_yaml. #8746 [Ernesto Jimenez]
-
-* Adding TimeWithZone #to_yaml, #to_datetime, #eql? and method aliases for duck-typing compatibility with Time [Geoff Buesing]
-
-* TimeWithZone #in_time_zone returns +self+ if zone argument is the same as #time_zone [Geoff Buesing]
-
-* Adding TimeWithZone #to_a, #to_f, #to_i, #httpdate, #rfc2822 [Geoff Buesing]
-
-* Pruning unneeded TimeWithZone#change_time_zone_to_current [Geoff Buesing]
-
-* Time#zone=, #in_time_zone and #change_time_zone accept a Duration [Geoff Buesing]
-
-* Time#in_time_zone handles Time.local instances correctly [Geoff Buesing]
-
-* Pruning unneeded Time#change_time_zone_to_current. Enhanced docs to #change_time_zone to explain the difference between this method and #in_time_zone [Geoff Buesing]
-
-* TimeZone#new method renamed #local; when used with Time.zone, constructor now reads: Time.zone.local() [Geoff Buesing]
-
-* Added Base64.encode64s to encode values in base64 without the newlines. This makes the values immediately usable as URL parameters or memcache keys without further processing [David Heinemeier Hansson]
-
-* Remove :nodoc: entries around the ActiveSupport test/unit assertions. #10946 [dancroak, jamesh]
-
-* Add Time.zone_default accessor for setting the default time zone. Rails::Configuration.time_zone sets this. #10982 [Geoff Buesing]
-
-* cache.fetch(key, :force => true) to force a cache miss. [Jeremy Kemper]
-
-* Support retrieving TimeZones with a Duration. TimeZone[-28800] == TimeZone[-480.minutes]. [Rick Olson]
-
-* TimeWithZone#- added, so that #- can handle a Time or TimeWithZone argument correctly [Geoff Buesing]
-
-* with_timezone test helper renamed with_env_tz, to distinguish between setting ENV['TZ'] and setting Time.zone in tests [Geoff Buesing]
-
-* Time#- coerces TimeWithZone argument to a Time instance so that difference in seconds can be calculated. Closes #10914 [Geoff Buesing, yyyc514]
-
-* Adding UTC zone to TimeZone; TimeWithZone no longer has to fake UTC zone with nil [Geoff Buesing]
-
-* Time.get_zone refactored to private method, given that the encapsulated logic is only useful internally [Geoff Buesing]
-
-* Time.zone uses thread-local variable for thread safety. Adding Time.use_zone, for overriding Time.zone locally inside a block. Removing unneeded Time.zone_reset! [Geoff Buesing]
-
-* TimeZone#to_s uses UTC rather than GMT; reapplying change that was undone in [8679]. #1689 [Cheah Chu Yeow]
-
-* Time.days_in_month defaults to current year if no year is supplied as argument #10799 [Radar], uses Date.gregorian_leap? to determine leap year, and uses constant lookup to determine days in month [Geoff Buesing]
-
-* Adding Time and DateTime #compare_with_coercion, which layers behavior on #<=> so that any combination of Time, DateTime and ActiveSupport::TimeWithZone instances can be chronologically compared [Geoff Buesing]
-
-* TimeZone#now returns an ActiveSupport::TimeWithZone [Geoff Buesing]
-
-* Time #in_current_time_zone and #change_time_zone_to_current return self when Time.zone is nil [Geoff Buesing]
-
-* Remove unneeded #to_datetime_default_s alias for DateTime#to_s, given that we inherit a #to_default_s from Date that does exactly the same thing [Geoff Buesing]
-
-* Refactor Time and DateTime #to_formatted_s: use ternary instead of nested if/else [Geoff Buesing]
-
-* Adding Time and DateTime #formatted_offset, for outputting +HH:MM utc offset strings with cross-platform consistency [Geoff Buesing]
-
-* Adding alternate_utc_string option to TimeZone#formatted_offset. Removing unneeded TimeZone#offset. [Geoff Buesing]
-
-* Introduce ActiveSupport::TimeWithZone, for wrapping Time instances with a TimeZone. Introduce instance methods to Time for creating TimeWithZone instances, and class methods for managing a global time zone. [Geoff Buesing]
-
-* Replace non-dst-aware TimeZone class with dst-aware class from tzinfo_timezone plugin. TimeZone#adjust and #unadjust are no longer available; tzinfo gem must now be present in order to perform time zone calculations, via #local_to_utc and #utc_to_local methods. [Geoff Buesing]
-
-* Extract ActiveSupport::Callbacks from Active Record, test case setup and teardown, and ActionController::Dispatcher. #10727 [Josh Peek]
-
-* Introducing DateTime #utc, #utc? and #utc_offset, for duck-typing compatibility with Time. Closes #10002 [Geoff Buesing]
-
-* Time#to_json uses Numeric#to_utc_offset_s to output a cross-platform-consistent representation without having to convert to DateTime. References #9750 [Geoff Buesing]
-
-* Refactor number-to-HH:MM-string conversion logic from TimeZone#formatted_offset to a reusable Numeric#to_utc_offset_s method. [Geoff Buesing]
-
-* Continue evolution toward ActiveSupport::TestCase. #10679 [Josh Peek]
-
-* TestCase: introduce declared setup and teardown callbacks. Pass a list of methods and an optional block to call before setup or after teardown. Setup callbacks are run in the order declared; teardown callbacks are run in reverse. [Jeremy Kemper]
-
-* Added ActiveSupport::Gzip.decompress/compress(source) as an easy wrapper for Zlib [Tobias Lütke]
-
-* Included MemCache-Client to make the improved ActiveSupport::Cache::MemCacheStore work out of the box [Bob Cottrell, Eric Hodel]
-
-* Added ActiveSupport::Cache::* framework as an extraction from ActionController::Caching::Fragments::* [David Heinemeier Hansson]
-
-* Fixed String#titleize to work for strings with 's too #10571 [trek]
-
-* Changed the implementation of Enumerable#group_by to use a double array approach instead of a hash such that the insert order is honored [David Heinemeier Hansson/Marcel Molina Jr.]
-
-* remove multiple enumerations from ActiveSupport::JSON#convert_json_to_yaml when dealing with date/time values. [Rick Olson]
-
-* Hash#symbolize_keys skips keys that can't be symbolized. #10500 [Brad Greenlee]
-
-* Ruby 1.9 compatibility. #1689, #10466, #10468, #10554, #10594, #10632 [Cheah Chu Yeow, Pratik Naik, Jeremy Kemper, Dirkjan Bussink, Xavier Noria]
-
-* TimeZone#to_s uses UTC rather than GMT. #1689 [Cheah Chu Yeow]
-
-* Refactor of Hash#symbolize_keys! to use Hash#replace. Closes #10420 [ReinH]
-
-* Fix HashWithIndifferentAccess#to_options! so it doesn't clear the options hash. Closes #10419 [ReinH]
-
-
-*2.0.1* (December 7th, 2007)
-
-* Added Array#from and Array#to that behaves just from String#from and String#to [David Heinemeier Hansson]
-
-* Fix that empty collections should be treated as empty arrays regardless of whitespace for Hash#from_xml #10255 [adamj]
-
-* Time#time_with_datetime_fallback, Time#to_datetime, Date#to_datetime and String#to_datetime honor Ruby's default calendar reform setting. #10201 [Geoff Buesing]
-
-* Change Time and DateTime #end_of_month to return last second of month instead of beginning of last day of month. Closes #10200 [Geoff Buesing]
-
-* Speedup String#blank? [Jeremy Kemper, Michael Koziarski]
-
-* Add documentation for Hash#diff. Closes #9306 [Tarmo Tänav]
-
-* Add new superclass_delegating_accessors. Similar to class inheritable attributes but with subtly different semantics. [Michael Koziarski, Tarmo Tänav]
-
-* Change JSON to encode %w(< > &) as 4 digit hex codes to be in compliance with the JSON spec. Closes #9975 [Josh Peek, Cheah Chu Yeow, Tim Pope]
-
-* Fix JSON encoding/decoding bugs dealing with /'s. Closes #9990 [Rick Olson, theamazingrando]
-
-* Introduce a base class for all test cases used by rails applications. ActiveSupport::TestCase [Michael Koziarski]
-
- The intention is to use this to reduce the amount of monkeypatching / overriding that
- is done to test/unit's classes.
-
-* Document Enumerable and Hash #to_json. #9970 [Cheah Chu Yeow]
-
-* Hash#to_xml handles symbol values. #9954 [Assaf]
-
-* Hash#symbolize_keys behaves well with integer keys. #9890 [PotatoSalad]
-
-* Multibyte: String#slice supports regexp argument. #9646 [yob]
-
-* object.duplicable? returns true if object.dup is safe. False for nil, true, false, symbols, and numbers; true otherwise. #9333 [sur]
-
-* Time, Date and DateTime #advance accept :weeks option. #9866 [Geoff Buesing]
-
-* Fix Time#years_ago and #years_since from leap days. #9865 [Geoff Buesing]
-
-* Time and DateTime#advance accept :hours, :minutes, and :seconds options. #9825 [Geoff Buesing]
-
-* Fix Date#years_ago and #years_since from leap days. #9864 [Geoff Buesing]
-
-* Refactor Time and Date#months_since and #months_ago to use #advance. #9863 [Geoff Buesing]
-
-* Rebundle Builder 2.1.2 but prefer a newer RubyGem if available. [Jeremy Kemper]
-
-* Add Range#overlaps?(range), Range#include?(range), and Range#step without a block. [brandon]
-
-* Correct BufferedLogger#level? checks. #9806 [wildchild, Johan Sorensen]
-
-* String#to_xs uses Eric Wong's fast_xs extension, if available, for Builder speedup. http://bogomips.org/fast_xs/ [Jeremy Kemper]
-
-* Introduce BasicObject as Builder::BlankSlate for Ruby 1.9 forward compatibility. [Jeremy Kemper]
-
-* Unbundle Builder in favor of a gem dependency. [Jeremy Kemper]
-
-* Disambiguate Time, Date, and DateTime#to_json formatting. #9750 [Geoff Buesing, Cheah Chu Yeow]
-
-* Hash#to_json takes :only or :except options to specific or omit certain hash keys. Enumerable#to_json passes through its options to each element. #9751 [Cheah Chu Yeow]
-
-* BufferedLogger#auto_flushing = N flushes the log every N messages. Buffers with an array instead of string. Disabling auto_flushing still flushes when the buffer hits a maximum size, as a failsafe against memory-gobbling. [Jeremy Kemper]
-
-* Fixed Date#xmlschema for dates outside the range of what can be created with Time #9744 [Geoff Buesing]
-
-* Fixed that La Paz was included in -25200 and -14400 offsets when it should only be in -14400 #9735 [bermi]
-
-* Fixed JSON encoding to use quoted keys according to the JSON standard. #8762 [choonkat, Cheah Chu Yeow]
-
-* Alias Object#send to send! for Ruby 1.9 forward compatibility. [Jeremy Kemper]
-
-* Backport Object#instance_variable_defined? for Ruby < 1.8.6. [Jeremy Kemper]
-
-* BufferedLogger#add converts the message to a string. #9702, #9724 [eigentone, DrMark, Tom Ward]
-
-* Added ActiveSupport::BufferedLogger as a duck-typing alternative (albeit with no formatter) to the Ruby Logger, which provides a very nice speed bump (inspired by Ezra's buffered logger) [David Heinemeier Hansson]
-
-* Object#instance_exec produces fewer garbage methods. [Mauricio Fernandez]
-
-* Decode json strings as Dates/Times if they're using a YAML-compatible format. Closes #9614 [Rick Olson]
-
-* Fixed cache_page to use the request url instead of the routing options when picking a save path. #8614 [Josh Peek]
-
-* Object.subclasses_of includes anonymous subclasses. [Jeremy Kemper]
-
-* Fixed that pluralizing an empty string should return the same empty string, not "s". #7720 [Josh Peek]
-
-* Added call to inspect on non-string classes for the logger #8533 [Coda Hale]
-
-* Deprecation: remove deprecated :mday option from Time, Date, and DateTime#change. [Jeremy Kemper]
-
-* Fix JSON decoder with nested quotes and commas. #9579 [Zach Dennis]
-
-* Hash#to_xml doesn't double-unescape. #8806 [Ezran]
-
-* Added Array#rand #9170 [Norbert Crombach]. Examples:
-
- [].rand # => nil
- ['a'].rand # => 'a'
- [1,2,3].rand # => 1 or 2 or 3
-
-* Deprecation: removed Reloadable. [Jeremy Kemper]
-
-* Make the utf-handler return the correct value for non-matching regular expressions. Closes #9049 [Manfred Stienstra]
-
-* Add ljust, rjust and center to utf8-handler. Closes #9165 [Manfred Stienstra]
-
-* Fix Time#advance bug when trying to advance a year from leap day. Closes #8655 [Geoff Buesing]
-
-* Add support for []= on ActiveSupport::Multibyte::Chars. Closes #9142. [ewan, Manfred Stienstra]
-
-* Added Array#extract_options! to encapsulate the pattern of getting an options hash out of a variable number of parameters. #8759 [Norbert Crombach]
-
-* Let alias_attribute work with attributes with initial capital letters (legacy columns etc). Closes #8596 [mpalmer]
-
-* Added Hash#except which is the inverse of Hash#slice -- return the hash except the keys that are specified [David Heinemeier Hansson]
-
-* Added support for pluralization with a different starting letter than the singular version (cow/kine) #4929 [norri_b/Josh Susser]
-
-* Demote Hash#to_xml to use XmlSimple#xml_in_string so it can't read files or stdin. #8453 [candlerb, Jeremy Kemper]
-
-* Backport clean_logger changes to support ruby 1.8.2 [Mislav Marohnić]
-
-* Added proper handling of arrays #8537 [Josh Susser]
-
- Before:
- Hash.from_xml '<images></images>'
- # => {:images => nil}
-
- Hash.from_xml '<images><image>foo.jpg</image></images>'
- # => {:images => {:image => "foo.jpg"}}
-
- Hash.from_xml '<images><image>foo.jpg</image><image>bar.jpg</image></images>'
- # => {:images => {:image => ["foo.jpg", "bar.jpg"]}}
-
- After:
- Hash.from_xml '<images type="array"></images>'
- # => {:images => []}
-
- Hash.from_xml '<images type="array"><image>foo.jpg</image></images>'
- # => {:images => ["foo.jpg"]}
-
- Hash.from_xml '<images type="array"><image>foo.jpg</image><image>bar.jpg</image></images>'
- # => {:images => ["foo.jpg", "bar.jpg"]}
-
-* Improve Time and Date test coverage. #8646 [Josh Peek]
-
-* Add Date#since, ago, beginning_of_day, and end_of_day. Date + seconds works now. #8575 [Geoff Buesing]
-
-* String#to_time overflows to DateTime. Add String#to_datetime. #8572 [Geoff Buesing]
-
-* Date.yesterday and .tomorrow. #8571 [Geoff Buesing]
-
-* Readable Date and DateTime#inspect. #8570 [Geoff Buesing]
-
-* Move common DateTime calculations to Date. #8536 [Geoff Buesing]
-
-* Added Date#change (like Time#change) [David Heinemeier Hansson]
-
-* DateTime#to_time converts to Time unless out of range. #8512 [Geoff Buesing]
-
-* Date#to_datetime, #to_s(:rfc822). #8512 [Geoff Buesing]
-
-* Time durations use since instead of + for accuracy. #8513 [Geoff Buesing]
-
-* escape <'s and >'s in JSON strings. #8371 [Rick Olson]
-
-* Inflections: MatrixTest -> MatrixTests instead of MatricesTest. #8496 [jbwiv]
-
-* Multibyte strings respond_to the String methods they proxy so they can be duck-typed. #6549 [Tuxie]
-
-* Array#to_xml yields the builder just like Hash and ActiveRecord::Base. #8472 [seth]
-
-* Date, Time, and DateTime support formatting blocks in addition to strftime strings. Introduce :long_ordinal format, e.g. "February 21st, 2005". #8191 [Coda Hale]
-
-* Document Object#blank?. #6491 [Chris Mear]
-
-* Date, Time, and DateTime#to_json. #8399 [wycats]
-
-* Simplify API of assert_difference by passing in an expression that is evaluated before and after the passed in block. See documenation for examples of new API. [Marcel Molina Jr.]
-
-* Added assert_difference and assert_no_difference to test/unit assertions [Tobias Lütke]
-
-* Removed breakpointer and Binding.of_caller in favor of relying on ruby-debug by Kent Sibilev since the breakpointer has been broken since Ruby 1.8.4 and will not be coming back [David Heinemeier Hansson]
-
-* Added parsing of file type in Hash.xml_in so you can easily do file uploads with base64 from an API [David Heinemeier Hansson]
-
- <person>
- <name>David</name>
- <avatar type="file" name="me.jpg" content_type="image/jpg">R0lGODlhkACZAPUAAM5lcfjrtMQCG=\n</avatar>
- </person>
-
- ...becomes:
-
- attributes = { :person => { :name => "David", :avatar => #<StringIO> } }
- attributes[:person][:avatar].content_type # => "image/jpg"
- attributes[:person][:avatar].original_filename # => "me.jpg"
- attributes[:person][:avatar].read # => binary data of the file
-
- Which is duck-type compatible with the files that you get when doing multipart uploads through HTML.
-
-* Improved multibyte performance by relying less on exception raising #8159 [Blaine]
-
-* Use XSD-compatible type names for Hash#to_xml and make the converters extendable #8047 [Tim Pope]
-
-* Added yielding of builder in Hash#to_xml [David Heinemeier Hansson]
-
-* Hash#with_indifferent_access now also converts hashes kept in arrays to indifferent access (makes it easier to treat HTML and XML parameters the same) [David Heinemeier Hansson]
-
-* Hash#to_xml supports YAML attributes. #7502 [jonathan]
-
-* Refactor ActiveSupport::JSON to be less obtuse. Add support for JSON decoding by way of Syck with ActiveSupport::JSON.decode(json_string). Prevent hash keys that are JavaScript reserved words from being unquoted during encoding. [Sam Stephenson]
-
-* alias_method_chain preserves the original method's visibility. #7854 [Jonathan Viney]
-
-* Update Dependencies to ignore constants inherited from ancestors. Closes #6951. [Nicholas Seckar]
-
-* Array#to_query preserves its ordering. #7756 [Greg Spurrier]
-
-* Out-of-range Time calculations transparently overflow to DateTime. Introduce Time#to_datetime. #7706, #7715 [Geoff Buesing]
-
-* DateTime calculations analogous to the Date and Time extensions. #7693 [Geoff Buesing]
-
-* Give DateTime correct .to_s implementations, lets it play nice with ActiveRecord quoting. #7649 [Geoff Buesing]
-
-* Add File.atomic_write, allows you to write large files in an atomic manner, preventing users from seeing half written files. [Michael Koziarski]
-
-* Allow users to provide custom formatters to Logger. [Anthony Eden]
-
-* Hash#to_query CGI-escapes its keys. [Jeremy Kemper]
-
-* Optimize Class Inheritable Attributes so that unnecessary hashes are not created. Closes #7472 [Bruce Perens]
-
-* :db format for Date#to_s [Jeremy Kemper]
- Date.new(2007, 1, 27).to_s(:db) # => '2007-01-27'
-
-* Added :instance_writer option to #mattr_writer/accessor, #cattr_writer/accessor, and #class_inheritable_writer to skip the creation of the instance writer. [Rick Olson]
-
-* Added Hash#to_query to turn a hash of values into a form-encoded query string [Nicholas Seckar]
-
-* Increase test coverage for subclasses_of. Closes #7335. [Roman2K, Nicholas Seckar]
-
-* Remove unused code from Duration#inspect. Closes #7180. [Rich Collins]
-
-* Added test coverage for Inflector.inflections.clear. Closes #7179. [Rich Collins]
-
-* ActiveSupport::Multibyte::Handlers::UTF8Handler should raise when a range and an integer are passed in (just like the native implementation). Closes #7176 [Rich Collins]
-
-* A couple extra tests for #classify. Closes #7273. [Josh Susser]
-
-* Better docs for Object extensions [zackchandler, Jamis Buck]
-
-* Fix that Dates couldn't be subtracted from Dates after [5940]. [Sam Stephenson]
-
-* Add Object#acts_like? and Time#acts_like_time? and Date#acts_like_date? to facilitate duck-typing. [Jamis Buck]
-
-* Make 1.months and friends accurate by introducing a Duration class. #6835 [eventualbuddha]
-
-
-*1.4.2* (March 12th, 2007)
-
-* Ruby 1.8.6 and 1.9 define private Time#to_date and #to_datetime; make them
-public for compatibility. [Jeremy Kemper]
-
-* Deprecation: warn on stderr if RAILS_DEFAULT_LOGGER isn't set yet. [Jeremy Kemper]
-
-
-*1.4.1* (February 5th, 2007)
-
-* Optimize Class Inheritable Attributes so that unnecessary hashes are not created. Closes #7472 [Bruce Perens]
-
-* Added :instance_writer option to #mattr_writer/accessor, #cattr_writer/accessor, and #class_inheritable_writer to skip the creation of the instance writer. [Rick Olson]
-
-* Full test coverage for Inflector. #7228 [Dan Kubb]
-
-
-*1.4.0* (January 16th, 2007)
-
-* Document Inflector.ordinalize and merge docs from String inflections. #7023 [smeade]
-
-* Unbundle flexmock. [Jeremy Kemper]
-
-* Fix Dependencies.autoloaded? to ignore anonymous modules. Closes #6561. [Nicholas Seckar]
-
-* Update load once paths to prevent nested once constants from being detected and claimed by an external non-once load. [Nicholas Seckar]
-
-* Deprecation: silence warnings when reporting test errors. [Jeremy Kemper]
-
-* Hash#slice(*keys) returns a new hash with only the given keys. #slice! replaces the hash with only the given keys. Works with HashWithIndifferentAccess also. [Jeremy Kemper]
-
-* HashWithIndifferentAccess#to_hash converts to a Hash with String keys and the same default value. [Jeremy Kemper]
-
-* Fix remove_constant to correctly handle constant names of the form "::A::...". References #6720. [Nicholas Seckar]
-
-* Fixed Array#to_xml when it contains a series of hashes (each piece would get its own XML declaration) #6610 [thkarcher/cyu]
-
-* Added Time#to_s(:time) which will just return H:M, like 17:44 [David Heinemeier Hansson]
-
-* Add Module#attr_accessor_with_default to initialize value of attribute before setting it. Closes #6538. [Stuart Halloway, Marcel Molina Jr.]
-
-* Hash#to_xml handles keys with the same name as Kernel methods. #6613 [Jonathan del Strother]
-
-* Added Time#end_of_day to get 23:59:59 of that day [David Heinemeier Hansson]
-
-* Don't quote hash keys in Hash#to_json if they're valid JavaScript identifiers. Disable this with ActiveSupport::JSON.unquote_hash_key_identifiers = false if you need strict JSON compliance. [Sam Stephenson]
-
-* Lazily load the Unicode Database in the UTF-8 Handler [Rick Olson]
-
-* Update dependencies to delete partially loaded constants. [Nicholas Seckar]
-
-* Fix unicode JSON regexp for Onigurama compatibility. #6494 [whitley]
-
-* update XmlSimple to 1.0.10. Closes #6532. [Nick Sieger]
-
-* Update dependencies to allow constants to be defined alongside their siblings. A common case for this is AR model classes with STI; user.rb might define User, Administrator and Guest for example. [Nicholas Seckar]
-
-* next_week respects DST changes. #6483, #5617, #2353, #2509, #4551 [marclove, Rob Biedenharn, rails@roetzel.de, jsolson@damogran.org, drbrain@segment7.net]
-
-* Expose methods added to Enumerable in the documentation, such as group_by. Closes #6170. [sergeykojin@gmail.com, Marcel Molina Jr.]
-
-* Ensure Chars#tidy_bytes only tidies broken bytes. Closes #6397 [Manfred Stienstra]
-
-* Add 'unloadable', a method used to mark any constant as requiring an unload after each request. [Nicholas Seckar]
-
-* Make core_ext/string/access.rb multibyte safe. Closes #6388 [Manfred Stienstra]
-
-* Make String#chars slicing behaviour consistent with String. Closes #6387 [Manfred Stienstra]
-
-* Pull in latest multibyte patch. Closes #6346 [Manfred Stienstra]
-
-* Add ActiveSupport::Multibyte. Provides String#chars which lets you deal with strings as a sequence of chars, not of bytes. Closes #6242 [Julian Tarkhanov, Manfred Stienstra, Thijs van der Vossen & Jan Behrens]
-
-* Fix issue with #class_inheritable_accessor saving updates to the parent class when initialized with an Array or Hash [mojombo]
-
-* Hash#to_xml supports Bignum and BigDecimal. #6313 [edibiase]
-
-* Don't undefine #class in OptionMerger [Rick Olson]
-
-* Hash.create_from_xml has been renamed to Hash.from_xml, alias will exist until Rails 2.0 [David Heinemeier Hansson]
-
-* alias_method_chain works with accessor= methods also. #6153 [Caio Chassot]
-
-* Fix loadable_constants_for_path to handle load paths that do not end with a slash. [Nicholas Seckar]
-
-* Fix logic error in determining what was loaded by a given file. Closes #6039. [Nicholas Seckar]
-
-* Equate Kernel.const_missing with Object.const_missing. Fixes #5988. [Nicholas Seckar]
-
-* Add ApplicationController special case to Dependencies. [Nicholas Seckar]
-
-* Don't pad remaining places with in_groups_of if specified padding value is false. [Marcel Molina Jr.]
-
-* Fix cases where empty xml nodes weren't being translated to nil in Hash.create_from_xml [Rick Olso n]
-
- <written-on type="date"></written-on> # => { :type => 'date' } # WRONG
- <written-on type="date"></written-on> # => nil # RIGHT
-
-* Tighten rescue clauses. #5985 [james@grayproductions.net]
-
-* Inflections: don't singularize -ies plurals. [foamdino@gmail.com, Mark Van Holstyn]
-
-* Update Initializer to use load_once_paths to avoid plugin reloading. References #5852. [Nicholas Seckar]
-
-* Use Array#assoc in ActiveSupport::OrderedHash. [Mauricio Fernandez]
-
-* Greatly increased performance of String.to_json, which speeds up RJS considerably on large pages, fixes #3473 [Shugo Maeda]
-
-* Detect missing_constants calls from removed modules and fail accordingly. [Nicholas Seckar]
-
-* Stop using defined? in Dependencies.qualified_const_defined? since defined? may invoke const_missing. [Nicholas Seckar]
-
-* Dependencies can autoload directories of nested classes. [Jeremy Kemper]
- Example:
- invoice.rb class Invoice
- invoice/lineitem.rb class Invoice::Lineitem
-
-* Add Deprecation.silence so that Reloadable does not scold itself. [Nicholas Seckar]
-
-* Add debugging logging to Dependencies. Currently can be enabled with Dependencies.log_activity = true; adding to Initializer and documenting is forthcoming. [Nicholas Seckar]
-
-* Replace Reloadable with improvements to the Dependencies mechanism. [Nicholas Seckar]
-
-* DateTime#to_time gives hour/minute/second resolution. #5747 [jon.evans@pobox.com]
-
-* attr_internal to support namespacing and deprecation. Like attr_* except backed by internally-named instance variable. Set attr_internal_naming_format to change the format from the default '@_%s'. [Jeremy Kemper]
- # def foo() @foo__rofl end
- # def foo=(v) @foo__rofl = v end
- self.attr_internal_naming_format = '@%s__rofl'
- attr_internal :foo
-
-* Raise fully qualified names upon name errors. #5533 [Lars Pind, Nicholas Seckar]
-
-* Add extention to obtain the missing constant from NameError instances. [Nicholas Seckar]
-
-* Thoroughly document inflections. #5700 [petermichaux@gmail.com]
-
-* Added Module#alias_attribute [Jamis/David Heinemeier Hansson]. Example:
-
- class Content < ActiveRecord::Base
- # has a title attribute
- end
-
- class Email < ActiveRecord::Base
- alias_attribute :subject, :title
- end
-
- e = Email.find(1)
- e.title # => "Superstars"
- e.subject # => "Superstars"
- e.subject? # => true
- e.subject = "Megastars"
- e.title # => "Megastars"
-
-* Deprecation: easier to work with warning behavior as procs; default behaviors for each environment so users needn't update env.rb; and testing pleasure with assert_deprecated, assert_not_deprecated. [Jeremy Kemper]
- By default, test prints to $stderr, dev logs, production ignores.
- Provide your own per-environment in e.g. config/environments/development.rb:
- ActiveSupport::Deprecation.behavior = Proc.new { |message| raise message }
-
-* First cut of the Rails Deprecation system. [Michael Koziarski]
-
-* Strip boolean XML content before checking for 'true' [Rick Olson]
-
-* Customize default BigDecimal formatting. References #5672 [Dave Thomas]
-
-* Correctly convert <foo nil="true"> to nil when using Hash.create_from_xml. [Rick Olson]
-
-* Optional identity for Enumerable#sum defaults to zero. #5657 [gensym@mac.com]
-
-* HashWithIndifferentAccess shouldn't confuse false and nil. #5601 [Shugo Maeda]
-
-* Fixed HashWithIndifferentAccess#default #5586 [chris@seagul.co.uk]
-
-* More compatible Hash.create_from_xml. #5523 [nunemaker@gmail.com]
-
-* Added Enumerable#sum for calculating a sum from the elements [David Heinemeier Hansson, jonathan@daikini.com]. Examples:
-
- [1, 2, 3].sum
- payments.sum { |p| p.price * p.tax_rate }
- payments.sum(&:price)
-
- This is instead of payments.inject(0) { |sum, p| sum + p.price }
-
-* Correct and clarify Array#to_sentence docs. #5458 [brad@madriska.com]
-
-* alias_method_chain preserves method punctuation so foo, foo?, and foo! may be chained with the same feature. [Jeremy Kemper]
- Example:
- alias_method_chain :save!, :validation
- is equivalent to
- alias_method :save_without_validation!, :save!
- alias_method :save!, :save_with_validation!
-
-* Enhance Symbol#to_proc so it works with list objects, such as multi-dimensional arrays. Closes #5295 [nov@yo.rim.or.jp]. Example:
-
- {1 => "one", 2 => "two", 3 => "three"}.sort_by(&:first).map(&:last)
- # => ["one", "two", "three"]
-
-* Added Hash.create_from_xml(string) which will create a hash from a XML string and even typecast if possible [David Heinemeier Hansson]. Example:
-
- Hash.create_from_xml <<-EOT
- <note>
- <title>This is a note</title>
- <created-at type="date">2004-10-10</created-at>
- </note>
- EOT
-
- ...would return:
-
- { :note => { :title => "This is a note", :created_at => Date.new(2004, 10, 10) } }
-
-* Added Jim Weirich's excellent FlexMock class to vendor (Copyright 2003, 2004 by Jim Weirich (jim@weriichhouse.org)) -- it's not automatically required, though, so require 'flexmock' is still necessary [David Heinemeier Hansson]
-
-* Fixed that Module#alias_method_chain should work with both foo? foo! and foo at the same time #4954 [anna@wota.jp]
-
-* to_xml fixes, features, and speedup: introduce :dasherize option that converts updated_at to updated-at if true (the existing default); binary columns get encoding="base64" attribute; nil values get nil="true" attribute to distinguish empty values; add type information for float columns; allow arbitrarily deep :include; include SQL type information as the type attribute. #4989 [Blair Zajac <blair@orcaware.com>]
-
-* Add OrderedHash#values. [Sam Stephenson]
-
-* Added Array#to_s(:db) that'll produce a comma-separated list of ids [David Heinemeier Hansson]. Example:
-
- Purchase.find(:all, :conditions => "product_id IN (#{shops.products.to_s(:db)})"
-
-* Normalize classify's argument to a String so that it plays nice with Symbols. [Marcel Molina Jr.]
-
-* Strip out leading schema name in classify. References #5139. [Michael Schoen]
-
-* Remove Enumerable#first_match since break(value) handles the use case well enough. [Nicholas Seckar]
-
- Enumerable#first_match was like detect, but instead of returning the matching element, the yielded value returned. For example:
-
- user_xml = adapters(:from => User, :to => Xml).first_match do |adapter|
- adapter.adapt @user
- end
-
- But this is just as easily done with:
-
- user_xml = adapters(:from => User, :to => Xml).each do
- break adapter.adapt(@user)
- end
-
-* Make Array#in_groups_of just return the grouped collection if a block isn't given. [Marcel Molina Jr.]
-
-* Don't destroy a HashWithIndifferentAccess if symbolize_keys! or stringify_keys! is called on it. Closes #5076. [Marcel Molina Jr., guy.naor@famundo.com]
-
-* Document Module::delegate. #5002 [pergesu@gmail.com]
-
-* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
-
-* Strip out punctuation on predicates or bang methods being aliased with alias_method_chain since target?_without_feature is not a valid method name. Add tests for Module#alias_method_chain. [Marcel Molina Jr.]
-
-* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
-
-* Allow default options in with_options to be overridden. Closes #4480. [murphy@cYcnus.de]
-
-* Added Module#alias_method_chain [Jamis Buck]
-
-* Updated to Builder 2.0 [David Heinemeier Hansson]
-
-* Add Array#split for dividing arrays into one or more subarrays by value or block. [Sam Stephenson]
-
-*1.3.1* (April 6th, 2006)
-
-* Clean paths inside of exception messages and traces. [Nicholas Seckar]
-
-* Add Pathname.clean_within for cleaning all the paths inside of a string. [Nicholas Seckar]
-
-* provide an empty Dependencies::LoadingModule.load which prints deprecation warnings. Lets 1.0 applications function with .13-style environment.rb.
-
-
-*1.3.0* (March 27th, 2006)
-
-* When possible, avoid incorrectly obtaining constants from parent modules. Fixes #4221. [Nicholas Seckar]
-
-* Add more tests for dependencies; refactor existing cases. [Nicholas Seckar]
-
-* Move Module#parent and Module#as_load_path into core_ext. Add Module#parent. [Nicholas Seckar]
-
-* Add CachingTools::HashCaching to simplify the creation of nested, autofilling hashes. [Nicholas Seckar]
-
-* Remove a hack intended to avoid unloading the same class twice, but which would not work anyways. [Nicholas Seckar]
-
-* Update Object.subclasses_of to locate nested classes. This affects Object.remove_subclasses_of in that nested classes will now be unloaded. [Nicholas Seckar]
-
-* Update Object.remove_subclasses_of to use Class.remove_class, reducing duplication. [Nicholas Seckar]
-
-* Added Fixnum#seconds for consistency, so you can say 5.minutes + 30.seconds instead of 5.minutes + 30 #4389 [François Beausoleil]
-
-* Added option to String#camelize to generate lower-cased camel case by passing in :lower, like "super_man".camelize(:lower) # => "superMan" [David Heinemeier Hansson]
-
-* Added Hash#diff to show the difference between two hashes [Chris McGrath]
-
-* Added Time#advance to do precise time time calculations for cases where a month being approximated to 30 days won't do #1860 [Rick Olson]
-
-* Enhance Inflector.underscore to convert '-' into '_' (as the inverse of Inflector.dasherize) [Jamis Buck]
-
-* Switched to_xml to use the xml schema format for datetimes. This allows the encoding of time zones and should improve operability. [Michael Koziarski]
-
-* Added a note to the documentation for the Date related Numeric extensions to indicate that they're
-approximations and shouldn't be used for critical calculations. [Michael Koziarski]
-
-* Added Hash#to_xml and Array#to_xml that makes it much easier to produce XML from basic structures [David Heinemeier Hansson]. Examples:
-
- { :name => "David", :street_name => "Paulina", :age => 26, :moved_on => Date.new(2005, 11, 15) }.to_xml
-
- ...returns:
-
- <person>
- <street-name>Paulina</street-name>
- <name>David</name>
- <age type="integer">26</age>
- <moved-on type="date">2005-11-15</moved-on>
- </person>
-
-* Moved Jim Weirich's wonderful Builder from Action Pack to Active Support (it's simply too useful to be stuck in AP) [David Heinemeier Hansson]
-
-* Fixed that Array#to_sentence will return "" on an empty array instead of ", and" #3842, #4031 [rubyonrails@beautifulpixel.com]
-
-* Add Enumerable#group_by for grouping collections based on the result of some
- block. Useful, for example, for grouping records by date.
-
- ex.
-
- latest_transcripts.group_by(&:day).each do |day, transcripts|
- p "#{day} -> #{transcripts.map(&:class) * ', '}"
- end
- "2006-03-01 -> Transcript"
- "2006-02-28 -> Transcript"
- "2006-02-27 -> Transcript, Transcript"
- "2006-02-26 -> Transcript, Transcript"
-
- Add Array#in_groups_of, for iterating over an array in groups of a certain
- size.
-
- ex.
-
- %w(1 2 3 4 5 6 7).in_groups_of(3) {|g| p g}
- ["1", "2", "3"]
- ["4", "5", "6"]
- ["7", nil, nil]
-
- [Marcel Molina Jr., Sam Stephenson]
-
-* Added Kernel#daemonize to turn the current process into a daemon that can be killed with a TERM signal [David Heinemeier Hansson]
-
-* Add 'around' methods to Logger, to make it easy to log before and after messages for a given block as requested in #3809. [Michael Koziarski] Example:
-
- logger.around_info("Start rendering component (#{options.inspect}): ",
- "\n\nEnd of component rendering") { yield }
-
-* Added Time#beginning_of_quarter #3607 [cohen.jeff@gmail.com]
-
-* Fix Object.subclasses_of to only return currently defined objects [Jonathan Viney <jonathan@bluewire.net.nz>]
-
-* Fix constantize to properly handle names beginning with '::'. [Nicholas Seckar]
-
-* Make String#last return the string instead of nil when it is shorter than the limit [Scott Barron].
-
-* Added delegation support to Module that allows multiple delegations at once (unlike Forwardable in the stdlib) [David Heinemeier Hansson]. Example:
-
- class Account < ActiveRecord::Base
- has_one :subscription
- delegate :free?, :paying?, :to => :subscription
- delegate :overdue?, :to => "subscription.last_payment"
- end
-
- account.free? # => account.subscription.free?
- account.overdue? # => account.subscription.last_payment.overdue?
-
-* Fix Reloadable to handle the case where a class that has been 'removed' has not yet been garbage collected. [Nicholas Seckar]
-
-* Don't allow Reloadable to be included into Modules.
-
-* Remove LoadingModule. [Nicholas Seckar]
-
-* Add documentation for Reloadable::Subclasses. [Nicholas Seckar]
-
-* Add Reloadable::Subclasses which handles the common case where a base class should not be reloaded, but its subclasses should be. [Nicholas Seckar]
-
-* Further improvements to reloading code [Nicholas Seckar, Trevor Squires]
-
- - All classes/modules which include Reloadable can define reloadable? for fine grained control of reloading
- - Class.remove_class uses Module#parent to access the parent module
- - Class.remove_class expanded to handle multiple classes in a single call
- - LoadingModule.clear! has been removed as it is no longer required
- - Module#remove_classes_including has been removed in favor of Reloadable.reloadable_classes
-
-* Added reusable reloading support through the inclusion of the Relodable module that all subclasses of ActiveRecord::Base, ActiveRecord::Observer, ActiveController::Base, and ActionMailer::Base automatically gets. This means that these classes will be reloaded by the dispatcher when Dependencies.mechanism = :load. You can make your own models reloadable easily:
-
- class Setting
- include Reloadable
- end
-
- Reloading a class is done by removing its constant which will cause it to be loaded again on the next reference. [David Heinemeier Hansson]
-
-* Added auto-loading support for classes in modules, so Conductor::Migration will look for conductor/migration.rb and Conductor::Database::Settings will look for conductor/database/settings.rb [Nicholas Seckar]
-
-* Add Object#instance_exec, like instance_eval but passes its arguments to the block. (Active Support will not override the Ruby 1.9 implementation of this method.) [Sam Stephenson]
-
-* Add Proc#bind(object) for changing a proc or block's self by returning a Method bound to the given object. Based on why the lucky stiff's "cloaker" method. [Sam Stephenson]
-
-* Fix merge and dup for hashes with indifferent access #3404 [Ken Miller]
-
-* Fix the requires in option_merger_test to unbreak AS tests. [Sam Stephenson]
-
-* Make HashWithIndifferentAccess#update behave like Hash#update by returning the hash. #3419, #3425 [asnem@student.ethz.ch, JanPrill@blauton.de, Marcel Molina Jr.]
-
-* Add ActiveSupport::JSON and Object#to_json for converting Ruby objects to JSON strings. [Sam Stephenson]
-
-* Add Object#with_options for DRYing up multiple calls to methods having shared options. [Sam Stephenson] Example:
-
- ActionController::Routing::Routes.draw do |map|
- # Account routes
- map.with_options(:controller => 'account') do |account|
- account.home '', :action => 'dashboard'
- account.signup 'signup', :action => 'new'
- account.logout 'logout', :action => 'logout'
- end
- end
-
-* Introduce Dependencies.warnings_on_first_load setting. If true, enables warnings on first load of a require_dependency. Otherwise, loads without warnings. Disabled (set to false) by default. [Jeremy Kemper]
-
-* Active Support is warnings-safe. #1792 [Eric Hodel]
-
-* Introduce enable_warnings counterpart to silence_warnings. Turn warnings on when loading a file for the first time if Dependencies.mechanism == :load. Common mistakes such as redefined methods will print warnings to stderr. [Jeremy Kemper]
-
-* Add Symbol#to_proc, which allows for, e.g. [:foo, :bar].map(&:to_s). [Marcel Molina Jr.]
-
-* Added the following methods [Marcel Molina Jr., Sam Stephenson]:
- * Object#copy_instance_variables_from(object) to copy instance variables from one object to another
- * Object#extended_by to get an instance's included/extended modules
- * Object#extend_with_included_modules_from(object) to extend an instance with the modules from another instance
-
-*1.2.5* (December 13th, 2005)
-
-* Become part of Rails 1.0
-
-* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
-
-*1.2.3* (November 7th, 2005)
-
-* Change Inflector#constantize to use eval instead of const_get. [Nicholas Seckar]
-
-* Fix const_missing handler to ignore the trailing '.rb' on files when comparing paths. [Nicholas Seckar]
-
-* Define kernel.rb methods in "class Object" instead of "module Kernel" to work around a Windows peculiarity [Sam Stephenson]
-
-* Fix broken tests caused by incomplete loading of active support. [Nicholas Seckar]
-
-* Fix status pluralization bug so status_codes doesn't get pluralized as statuses_code. #2758 [keithm@infused.org]
-
-* Added Kernel#silence_stderr to silence stderr for the duration of the given block [Sam Stephenson]
-
-* Changed Kernel#` to print a message to stderr (like Unix) instead of raising Errno::ENOENT on Win32 [Sam Stephenson]
-
-* Changed 0.blank? to false rather than true since it violates everyone's expectation of blankness. #2518, #2705 [rails@jeffcole.net]
-
-* When loading classes using const_missing, raise a NameError if and only if the file we tried to load was not present. [Nicholas Seckar]
-
-* Added petabytes and exebytes to numeric extensions #2397 [timct@mac.com]
-
-* Added Time#end_of_month to accompany Time#beginning_of_month #2514 [Jens-Christian Fischer]
-
-
-*1.2.2* (October 26th, 2005)
-
-* Set Logger.silencer = false to disable Logger#silence. Useful for debugging fixtures.
-
-* Add title case method to String to do, e.g., 'action_web_service'.titlecase # => 'Action Web Service'. [Marcel Molina Jr.]
-
-
-*1.2.1* (October 19th, 2005)
-
-* Classify generated routing code as framework code to avoid appearing in application traces. [Nicholas Seckar]
-
-* Show all framework frames in the framework trace. [Nicholas Seckar]
-
-
-*1.2.0* (October 16th, 2005)
-
-* Update Exception extension to show the first few framework frames in an application trace. [Nicholas Seckar]
-
-* Added Exception extension to provide support for clean backtraces. [Nicholas Seckar]
-
-* Updated whiny nil to be more concise and useful. [Nicholas Seckar]
-
-* Added Enumerable#first_match [Nicholas Seckar]
-
-* Fixed that Time#change should also reset usec when also resetting minutes #2459 [ikeda@dream.big.or.jp]
-
-* Fix Logger compatibility for distributions that don't keep Ruby and its standard library in sync.
-
-* Replace '%e' from long and short time formats as Windows does not support it. #2344. [Tom Ward <tom@popdog.net>]
-
-* Added to_s(:db) to Range, so you can get "BETWEEN '2005-12-10' AND '2005-12-12'" from Date.new(2005, 12, 10)..Date.new(2005, 12, 12) (and likewise with Times)
-
-* Moved require_library_or_gem into Kernel. #1992 [Michael Schuerig <michael@schuerig.de>]
-
-* Add :rfc822 as an option for Time#to_s (to get rfc822-formatted times)
-
-* Chain the const_missing hook to any previously existing hook so rails can play nicely with rake
-
-* Clean logger is compatible with both 1.8.2 and 1.8.3 Logger. #2263 [Michael Schuerig <michael@schuerig.de>]
-
-* Added native, faster implementations of .blank? for the core types #2286 [skae]
-
-* Fixed clean logger to work with Ruby 1.8.3 Logger class #2245
-
-* Fixed memory leak with Active Record classes when Dependencies.mechanism = :load #1704 [Chris McGrath]
-
-* Fixed Inflector.underscore for use with acronyms, so HTML becomes html instead of htm_l #2173 [k@v2studio.com]
-
-* Fixed dependencies related infinite recursion bug when a controller file does not contain a controller class. Closes #1760. [rcolli2@tampabay.rr.com]
-
-* Fixed inflections for status, quiz, move #2056 [deirdre@deirdre.net]
-
-* Added Hash#reverse_merge, Hash#reverse_merge!, and Hash#reverse_update to ease the use of default options
-
-* Added Array#to_sentence that'll turn ['one', 'two', 'three'] into "one, two, and three" #2157 [Manfred Stienstra]
-
-* Added Kernel#silence_warnings to turn off warnings temporarily for the passed block
-
-* Added String#starts_with? and String#ends_with? #2118 [Thijs van der Vossen]
-
-* Added easy extendability to the inflector through Inflector.inflections (using the Inflector::Inflections singleton class). Examples:
-
- Inflector.inflections do |inflect|
- inflect.plural /^(ox)$/i, '\1\2en'
- inflect.singular /^(ox)en/i, '\1'
-
- inflect.irregular 'octopus', 'octopi'
-
- inflect.uncountable "equipment"
- end
-
-* Added String#at, String#from, String#to, String#first, String#last in ActiveSupport::CoreExtensions::String::Access to ease access to individual characters and substrings in a string serving basically as human names for range access.
-
-* Make Time#last_month work when invoked on the 31st of a month.
-
-* Add Time.days_in_month, and make Time#next_month work when invoked on the 31st of a month
-
-* Fixed that Time#midnight would have a non-zero usec on some platforms #1836
-
-* Fixed inflections of "index/indices" #1766 [damn_pepe@gmail.com]
-
-* Added stripping of _id to String#humanize, so "employee_id" becomes "Employee" #1574 [Justin French]
-
-* Factor Fixnum and Bignum extensions into Integer extensions [Nicholas Seckar]
-
-* Hooked #ordinalize into Fixnum and Bignum classes. [Nicholas Seckar, danp]
-
-* Added Fixnum#ordinalize to turn 1.ordinalize to "1st", 3.ordinalize to "3rd", and 10.ordinalize to "10th" and so on #1724 [paul@cnt.org]
-
-
-*1.1.1* (11 July, 2005)
-
-* Added more efficient implementation of the development mode reset of classes #1638 [Chris McGrath]
-
-
-*1.1.0* (6 July, 2005)
-
-* Fixed conflict with Glue gem #1606 [Rick Olson]
-
-* Added new rules to the Inflector to deal with more unusual plurals mouse/louse => mice/lice, information => information, ox => oxen, virus => viri, archive => archives #1571, #1583, #1490, #1599, #1608 [foamdino@gmail.com/others]
-
-* Fixed memory leak with Object#remove_subclasses_of, which inflicted a Rails application running in development mode with a ~20KB leak per request #1289 [Chris McGrath]
-
-* Made 1.year == 365.25.days to account for leap years. This allows you to do User.find(:all, :conditions => ['birthday > ?', 50.years.ago]) without losing a lot of days. #1488 [tuxie@dekadance.se]
-
-* Added an exception if calling id on nil to WhinyNil #584 [kevin-temp@writesoon.com]
-
-* Added Fix/Bignum#multiple_of? which returns true on 14.multiple_of?(7) and false on 16.multiple_of?(7) #1464 [Thomas Fuchs]
-
-* Added even? and odd? to work with Bignums in addition to Fixnums #1464 [Thomas Fuchs]
-
-* Fixed Time#at_beginning_of_week returned the next Monday instead of the previous one when called on a Sunday #1403 [jean.helou@gmail.com]
-
-* Increased the speed of indifferent hash access by using Hash#default. #1436 [Nicholas Seckar]
-
-* Added that " " is now also blank? (using strip if available)
-
-* Fixed Dependencies so all modules are able to load missing constants #1173 [Nicholas Seckar]
-
-* Fixed the Inflector to underscore strings containing numbers, so Area51Controller becomes area51_controller #1176 [Nicholas Seckar]
-
-* Fixed that HashWithIndifferentAccess stringified all keys including symbols, ints, objects, and arrays #1162 [Nicholas Seckar]
-
-* Fixed Time#last_year to go back in time, not forward #1278 [fabien@odilat.com]
-
-* Fixed the pluralization of analysis to analyses #1295 [seattle@rootimage.msu.edu]
-
-* Fixed that Time.local(2005,12).months_since(1) would raise "ArgumentError: argument out of range" #1311 [jhahn@niveon.com]
-
-* Added silencing to the default Logger class
-
-
-*1.0.4* (19th April, 2005)
-
-* Fixed that in some circumstances controllers outside of modules may have hidden ones inside modules. For example, admin/content might have been hidden by /content. #1075 [Nicholas Seckar]
-
-* Fixed inflection of perspectives and similar words #1045 [Thijs van der Vossen]
-
-* Added Fixnum#even? and Fixnum#odd?
-
-* Fixed problem with classes being required twice. Object#const_missing now uses require_dependency to load files. It used to use require_or_load which would cause models to be loaded twice, which was not good for validations and other class methods #971 [Nicholas Seckar]
-
-
-*1.0.3* (27th March, 2005)
-
-* Fixed Inflector.pluralize to handle capitalized words #932 [Jeremy Kemper]
-
-* Added Object#suppress which allows you to make a saner choice around with exceptions to swallow #980. Example:
-
- suppress(ZeroDivisionError) { 1/0 }
-
- ...instead of:
-
- 1/0 rescue nil # BAD, EVIL, DIRTY.
-
-
-*1.0.2* (22th March, 2005)
-
-* Added Kernel#returning -- a Ruby-ized realization of the K combinator, courtesy of Mikael Brockman.
-
- def foo
- returning values = [] do
- values << 'bar'
- values << 'baz'
- end
- end
-
- foo # => ['bar', 'baz']
-
-
-*1.0.1* (7th March, 2005)
-
-* Fixed Hash#indifferent_access to also deal with include? and fetch and nested hashes #726 [Nicholas Seckar]
-
-* Added Object#blank? -- see http://redhanded.hobix.com/inspect/objectBlank.html #783 [_why the lucky stiff]
-
-* Added inflection rules for "sh" words, like "wish" and "fish" #755 [phillip@pjbsoftware.com]
-
-* Fixed an exception when using Ajax based requests from Safari because Safari appends a \000 to the post body. Symbols can't have \000 in them so indifferent access would throw an exception in the constructor. Indifferent hashes now use strings internally instead. #746 [Tobias Lütke]
-
-* Added String#to_time and String#to_date for wrapping ParseDate
-
-
-*1.0.0* (24th February, 2005)
-
-* Added TimeZone as the first of a number of value objects that among others Active Record can use rich value objects using composed_of #688 [Jamis Buck]
-
-* Added Date::Conversions for getting dates in different convenient string representations and other objects
-
-* Added Time::Conversions for getting times in different convenient string representations and other objects
-
-* Added Time::Calculations to ask for things like Time.now.tomorrow, Time.now.yesterday, Time.now.months_ago(4) #580 [DP|Flurin]. Examples:
-
- "Later today" => now.in(3.hours),
- "Tomorrow morning" => now.tomorrow.change(:hour => 9),
- "Tomorrow afternoon" => now.tomorrow.change(:hour => 14),
- "In a couple of days" => now.tomorrow.tomorrow.change(:hour => 9),
- "Next monday" => now.next_week.change(:hour => 9),
- "In a month" => now.next_month.change(:hour => 9),
- "In 6 months" => now.months_since(6).change(:hour => 9),
- "In a year" => now.in(1.year).change(:hour => 9)
-
-* Upgraded to breakpoint 92 which fixes:
-
- * overload IRB.parse_opts(), fixes #443
- => breakpoints in tests work even when running them via rake
- * untaint handlers, might fix an issue discussed on the Rails ML
- * added verbose mode to breakpoint_client
- * less noise caused by breakpoint_client by default
- * ignored TerminateLineInput exception in signal handler
- => quiet exit on Ctrl-C
-
-* Fixed Inflector for words like "news" and "series" that are the same in plural and singular #603 [echion], #615 [marcenuc]
-
-* Added Hash#stringify_keys and Hash#stringify_keys!
-
-* Added IndifferentAccess as a way to wrap a hash by a symbol-based store that also can be accessed by string keys
-
-* Added Inflector.constantize to turn "Admin::User" into a reference for the constant Admin::User
-
-* Added that Inflector.camelize and Inflector.underscore can deal with modules like turning "Admin::User" into "admin/user" and back
-
-* Added Inflector.humanize to turn attribute names like employee_salary into "Employee salary". Used by automated error reporting in AR.
-
-* Added availability of class inheritable attributes to the masses #477 [Jeremy Kemper]
-
- class Foo
- class_inheritable_reader :read_me
- class_inheritable_writer :write_me
- class_inheritable_accessor :read_and_write_me
- class_inheritable_array :read_and_concat_me
- class_inheritable_hash :read_and_update_me
- end
-
- # Bar gets a clone of (not a reference to) Foo's attributes.
- class Bar < Foo
- end
-
- Bar.read_and_write_me == Foo.read_and_write_me
- Bar.read_and_write_me = 'bar'
- Bar.read_and_write_me != Foo.read_and_write_me
-
-* Added Inflections as an extension on String, so Inflector.pluralize(Inflector.classify(name)) becomes name.classify.pluralize #476 [Jeremy Kemper]
-
-* Added Byte operations to Numeric, so 5.5.megabytes + 200.kilobytes #461 [Marcel Molina Jr.]
-
-* Fixed that Dependencies.reload can't load the same file twice #420 [Kent Sibilev]
-
-* Added Fixnum#ago/until, Fixnum#since/from_now #450 [Jeremy Kemper]
-
-* Added that Inflector now accepts Symbols and Classes by calling .to_s on the word supplied
-
-* Added time unit extensions to Fixnum that'll return the period in seconds, like 2.days + 4.hours.
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
new file mode 100644
index 0000000000..79ea9c70ea
--- /dev/null
+++ b/activesupport/CHANGELOG.md
@@ -0,0 +1,1736 @@
+## Rails 4.0.0 (unreleased) ##
+
+* `constantize` now looks in the ancestor chain. *Marc-Andre Lafortune & Andrew White*
+
+* `Object#try` can't call private methods. *Vasiliy Ermolovich*
+
+* `AS::Callbacks#run_callbacks` remove `key` argument. *Francesco Rodriguez*
+
+* `deep_dup` works more expectedly now and duplicates also values in +Hash+ instances and elements in +Array+ instances. *Alexey Gaziev*
+
+* Inflector no longer applies ice -> ouse to words like slice, police, ets *Wes Morgan*
+
+* Add `ActiveSupport::Deprecations.behavior = :silence` to completely ignore Rails runtime deprecations *twinturbo*
+
+* Make Module#delegate stop using `send` - can no longer delegate to private methods. *dasch*
+
+* AS::Callbacks: deprecate `:rescuable` option. *Bogdan Gusiev*
+
+* Adds Integer#ordinal to get the ordinal suffix string of an integer. *Tim Gildea*
+
+* AS::Callbacks: `:per_key` option is no longer supported
+
+* `AS::Callbacks#define_callbacks`: add `:skip_after_callbacks_if_terminated` option.
+
+* Add html_escape_once to ERB::Util, and delegate escape_once tag helper to it. *Carlos Antonio da Silva*
+
+* Remove ActiveSupport::TestCase#pending method, use `skip` instead. *Carlos Antonio da Silva*
+
+* Deprecates the compatibility method Module#local_constant_names,
+ use Module#local_constants instead (which returns symbols). *fxn*
+
+* Deletes the compatibility method Module#method_names,
+ use Module#methods from now on (which returns symbols). *fxn*
+
+* Deletes the compatibility method Module#instance_method_names,
+ use Module#instance_methods from now on (which returns symbols). *fxn*
+
+* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger
+ from Ruby stdlib.
+
+* Unicode database updated to 6.1.0.
+
+* Adds `encode_big_decimal_as_string` option to force JSON serialization of BigDecimals as numeric instead
+ of wrapping them in strings for safety.
+
+
+## Rails 3.2.4 (unreleased) ##
+
+* Added #beginning_of_hour and #end_of_hour to Time and DateTime core
+ extensions. *Mark J. Titorenko*
+
+
+## Rails 3.2.3 (March 30, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.2 (March 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* Documentation fixes and improvements.
+
+* Update time zone offset information. *Ravil Bayramgalin*
+
+* The deprecated `ActiveSupport::Base64.decode64` calls `::Base64.decode64`
+ now. *Jonathan Viney*
+
+* Fixes uninitialized constant `ActiveSupport::TaggedLogging::ERROR`. *kennyj*
+
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* ActiveSupport::Base64 is deprecated in favor of ::Base64. *Sergey Nartimov*
+
+* Module#synchronize is deprecated with no replacement. Please use `monitor`
+ from ruby's standard library.
+
+* (Date|DateTime|Time)#beginning_of_week accept an optional argument to
+ be able to set the day at which weeks are assumed to start.
+
+* Deprecated ActiveSupport::MessageEncryptor#encrypt and decrypt. *José Valim*
+
+* ActiveSupport::Notifications.subscribed provides subscriptions to events while a block runs. *fxn*
+
+* Module#qualified_const_(defined?|get|set) are analogous to the corresponding methods
+ in the standard API, but accept qualified constant names. *fxn*
+
+* Added inflection #deconstantize which complements #demodulize. This inflection
+ removes the righmost segment in a qualified constant name. *fxn*
+
+* Added ActiveSupport:TaggedLogging that can wrap any standard Logger class to provide tagging capabilities *DHH*
+
+ Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
+ Logger.tagged("BCX") { Logger.info "Stuff" } # Logs "[BCX] Stuff"
+ Logger.tagged("BCX", "Jason") { Logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff"
+ Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"
+
+* Added safe_constantize that constantizes a string but returns nil instead of an exception if the constant (or part of it) does not exist *Ryan Oblak*
+
+* ActiveSupport::OrderedHash is now marked as extractable when using Array#extract_options! *Prem Sichanugrist*
+
+* Added Array#prepend as an alias for Array#unshift and Array#append as an alias for Array#<< *DHH*
+
+* The definition of blank string for Ruby 1.9 has been extended to Unicode whitespace.
+ Also, in 1.8 the ideographic space U+3000 is considered to be whitespace. *Akira Matsuda, Damien Mathieu*
+
+* The inflector understands acronyms. *dlee*
+
+* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern *José Valim*
+
+* Added Time#all_day/week/quarter/year as a way of generating ranges (example: Event.where(created_at: Time.now.all_week)) *DHH*
+
+* Added instance_accessor: false as an option to Class#cattr_accessor and friends *DHH*
+
+* Removed ActiveSupport::SecureRandom in favor of SecureRandom from the standard library *Jon Leighton*
+
+* ActiveSupport::OrderedHash now has different behavior for #each and
+ \#each_pair when given a block accepting its parameters with a splat. *Andrew Radev*
+
+* ActiveSupport::BufferedLogger#silence is deprecated. If you want to squelch
+ logs for a certain block, change the log level for that block.
+
+* ActiveSupport::BufferedLogger#open_log is deprecated. This method should
+ not have been public in the first place.
+
+* ActiveSupport::BufferedLogger's behavior of automatically creating the
+ directory for your log file is deprecated. Please make sure to create the
+ directory for your log file before instantiating.
+
+* ActiveSupport::BufferedLogger#auto_flushing is deprecated. Either set the
+ sync level on the underlying file handle like this:
+
+ f = File.open('foo.log', 'w')
+ f.sync = true
+ ActiveSupport::BufferedLogger.new f
+
+ Or tune your filesystem. The FS cache is now what controls flushing.
+
+* ActiveSupport::BufferedLogger#flush is deprecated. Set sync on your
+ filehandle, or tune your filesystem.
+
+
+## Rails 3.1.4 (March 1, 2012) ##
+
+* No changes
+
+
+## Rails 3.1.3 (November 20, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.2 (November 18, 2011) ##
+
+* No changes
+
+
+## Rails 3.1.1 (October 7, 2011) ##
+
+* ruby193: String#prepend is also unsafe *Akira Matsuda*
+
+* Fix obviously breakage of Time.=== for Time subclasses *jeremyevans*
+
+* Added fix so that file store does not raise an exception when cache dir does
+ not exist yet. This can happen if a delete_matched is called before anything
+ is saved in the cache. *Philippe Huibonhoa*
+
+* Fixed performance issue where TimeZone lookups would require tzinfo each time *Tim Lucas*
+
+* ActiveSupport::OrderedHash is now marked as extractable when using Array#extract_options! *Prem Sichanugrist*
+
+
+## Rails 3.1.0 (August 30, 2011) ##
+
+* ActiveSupport::Dependencies#load and ActiveSupport::Dependencies#require now
+ return the value from `super` *Aaron Patterson*
+
+* Fixed ActiveSupport::Gzip to work properly in Ruby 1.8 *Guillermo Iguaran*
+
+* Kernel.require_library_or_gem was deprecated and will be removed in Rails 3.2.0 *Josh Kalderimis*
+
+* ActiveSupport::Duration#duplicable? was fixed for Ruby 1.8 *thedarkone*
+
+* ActiveSupport::BufferedLogger set log encoding to BINARY, but still use text
+ mode to output portable newlines. *fxn*
+
+* ActiveSupport::Dependencies now raises NameError if it finds an existing constant in load_missing_constant. This better reflects the nature of the error which is usually caused by calling constantize on a nested constant. *Andrew White*
+
+* Deprecated ActiveSupport::SecureRandom in favour of SecureRandom from the standard library *Jon Leighton*
+
+* New reporting method Kernel#quietly. *fxn*
+
+* Add String#inquiry as a convenience method for turning a string into a StringInquirer object *DHH*
+
+* 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.
+
+* ActiveSupport::Dependencies::ClassCache class has been introduced for
+ holding references to reloadable classes.
+
+* ActiveSupport::Dependencies::Reference has been refactored to take direct
+ advantage of the new ClassCache.
+
+* Backports Range#cover? as an alias for Range#include? in Ruby 1.8 *Diego Carrion, fxn*
+
+* Added weeks_ago and prev_week to Date/DateTime/Time. *Rob Zolkos, fxn*
+
+* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! *Andrew White*
+
+* JSON decoding now uses the multi_json gem which also vendors a json engine called OkJson. The yaml backend has been removed in favor of OkJson as a default engine for 1.8.x, while the built in 1.9.x json implementation will be used by default. *Josh Kalderimis*
+
+
+## Rails 3.0.12 (March 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* Delayed backtrace scrubbing in `load_missing_constant` until we actually
+ raise the exception
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.7 (April 18, 2011) ##
+
+* Hash.from_xml no longer loses attributes on tags containing only whitespace *André Arko*
+
+
+## Rails 3.0.6 (April 5, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.5 (February 26, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.4 (February 8, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.3 (November 16, 2010) ##
+
+* No changes.
+
+
+## Rails 3.0.2 (November 15, 2010) ##
+
+* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! *Andrew White*
+
+
+## Rails 3.0.1 (October 15, 2010) ##
+
+* No Changes, just a version bump.
+
+
+## Rails 3.0.0 (August 29, 2010) ##
+
+* Implemented String#strip_heredoc. *fxn*
+
+* Pluggable cache stores: setting config.cache_store = "custom_store" will require 'active_support/cache/custom_store' and look for the CustomStore constant. #5486 *Mike Perham*
+
+* Removed Object#returning, Object#tap should be used instead. *Santiago Pastorino*
+
+* Deprecation behavior is no longer hardcoded to the name of the environment.
+ Instead, it is set via config.active_support.deprecation and can be one
+ of :log, :stderr or :notify. :notify is a new style that sends the warning
+ via ActiveSupport::Notifications, and is the new default for production
+ *Yehuda Katz*
+
+* Renamed ActiveSupport::Dependecies.load_(once_)paths to autoload_(once_)paths. *fxn*
+
+* Added ActiveSupport::FileUpdateChecker to execute a block only if a set of files changed, used by Router and I18n locale files. *José Valim*
+
+* Added ActiveSupport::DescendantsTracker to track descendants with support to constants reloading. *José Valim*
+
+* ActiveSupport::OrderedHash#merge and #merge! accept a block. #4838 *Paul Mucur, fxn*
+
+* Date#since, #ago, #beginning_of_day, #end_of_day, and #xmlschema honor now the user time zone if set. *Geoff Buesing*
+
+* Extracted String#truncate from TextHelper#truncate *DHH*
+
+* Ruby 1.9: support UTF-8 case folding. #4595 *Norman Clarke*
+
+* Removes Array#rand and backports Array#sample from Ruby 1.9, thanks to Marc-Andre Lafortune. *fxn*
+
+* Ruby 1.9: Renames last_(month|year) to prev_(month|year) in Date and Time. *fxn*
+
+* Aliases Date#sunday to Date#end_of_week. *fxn*
+
+* Backports Date#>> from 1.9 so that calculations do the right thing around the calendar reform. *fxn*
+
+* Date#to_time handles properly years in the range 0..138. *fxn*
+
+* Deprecate {{}} as interpolation syntax for I18n in favor of %{} *José Valim*
+
+* Array#to_xml is more powerful and able to handle the same types as Hash#to_xml #4490 *Neeraj Singh*
+
+* Harmonize the caching API and refactor the backends. #4452 *Brian Durand*
+ All caches:
+ * Add default options to initializer that will be sent to all read, write, fetch, exist?, increment, and decrement
+ * Add support for the :expires_in option to fetch and write for all caches. Cache entries are stored with the create timestamp and a ttl so that expiration can be handled independently of the implementation.
+ * Add support for a :namespace option. This can be used to set a global prefix for cache entries.
+ * Deprecate expand_cache_key on ActiveSupport::Cache and move it to ActionController::Caching and ActionDispatch::Http::Cache since the logic in the method used some Rails specific environment variables and was only used by ActionPack classes. Not very DRY but there didn't seem to be a good shared spot and ActiveSupport really shouldn't be Rails specific.
+ * Add support for :race_condition_ttl to fetch. This setting can prevent race conditions on fetch calls where several processes try to regenerate a recently expired entry at once.
+ * Add support for :compress option to fetch and write which will compress any data over a configurable threshold.
+ * Nil values can now be stored in the cache and are distinct from cache misses for fetch.
+ * Easier API to create new implementations. Just need to implement the methods read_entry, write_entry, and delete_entry instead of overwriting existing methods.
+ * Since all cache implementations support storing objects, update the docs to state that ActiveCache::Cache::Store implementations should store objects. Keys, however, must be strings since some implementations require that.
+ * Increase test coverage.
+ * Document methods which are provided as convenience but which may not be universally available.
+
+ MemoryStore:
+ * MemoryStore can now safely be used as the cache for single server sites.
+ * Make thread safe so that the default cache implementation used by Rails is thread safe. The overhead is minimal and it is still the fastest store available.
+ * Provide :size initialization option indicating the maximum size of the cache in memory (defaults to 32Mb).
+ * Add prune logic that removes the least recently used cache entries to keep the cache size from exceeding the max.
+ * Deprecated SynchronizedMemoryStore since it isn't needed anymore.
+
+ FileStore:
+ * Escape key values so they will work as file names on all file systems, be consistent, and case sensitive
+ * Use a hash algorithm to segment the cache into sub directories so that a large cache doesn't exceed file system limits.
+ * FileStore can be slow so implement the LocalCache strategy to cache reads for the duration of a request.
+ * Add cleanup method to keep the disk from filling up with expired entries.
+ * Fix increment and decrement to use file system locks so they are consistent between processes.
+
+ MemCacheStore:
+ * Support all keys. Previously keys with spaces in them would fail
+ * Deprecate CompressedMemCacheStore since it isn't needed anymore (use :compress => true)
+
+* JSON: encode objects that don't have a native JSON representation using to_hash, if available, instead of instance_values (the old fallback) or to_s (other encoders' default). Encode BigDecimal and Regexp encode as strings to conform with other encoders. Try to transcode non-UTF-8 strings. *Jeremy Kemper*
+
+* HashWithIndifferentAccess: remove inherited symbolize_keys! since its keys are always strings. *Santiago Pastorino*
+
+* Improve transliteration quality. #4374 *Norman Clarke*
+
+* Speed up and add Ruby 1.9 support for ActiveSupport::Multibyte::Chars#tidy_bytes. #4350 *Norman Clarke*
+
+* Reduced load time by deferring configuration of classes using
+ ActiveSupport::on_load(:component_name) *YK*
+
+* Rename #metaclass to #singleton_class now that ruby-core has decided *JK*
+
+* New assertions assert_blank and assert_present. #4299 *Juanjo Bazan*
+
+* Use Object#singleton_class instead of #metaclass. Prefer Ruby's choice. *Jeremy Kemper*
+
+* JSON backend for YAJL. Preferred if available. #2666 *Brian Lopez*
+
+* Introduce class_attribute to declare inheritable class attributes. Writing an attribute on a subclass behaves just like overriding the superclass reader method. Unifies and replaces most usage of cattr_accessor, class_inheritable_attribute, superclass_delegating_attribute, and extlib_inheritable_attribute. *Jeremy Kemper, Yehuda Katz*
+
+* Time#- with a DateTime argument behaves the same as with a Time argument, i.e. returns the difference between self and arg as a Float #3476 *Geoff Buesing*
+
+* YAML serialization for OrderedHash. #3608 *Gregor Schmidt*
+
+* Update bundled TZInfo to v0.3.16 *Geoff Buesing*
+
+* Georgetown TimeZone is now mapped to "America/Guyana" instead of "America/Argentina/San_Juan" #1821 *Geoff Buesing, Reuben Sivan*
+
+* Changed the default ActiveSupport.use_standard_json_time_format from false to true and
+ ActiveSupport.escape_html_entities_in_json from true to false to match previously announced Rails 3 defaults *DHH*
+
+* Added Object#presence that returns the object if it's #present? otherwise returns nil *DHH/Colin Kelley*
+
+* Add Enumerable#exclude? to bring parity to Enumerable#include? and avoid if !x.include?/else calls *DHH*
+
+* Update Edinburgh TimeZone to use "Europe/London" instead of "Europe/Dublin" #3310 *Phil Ross*
+
+* Update bundled TZInfo to v0.3.15 *Geoff Buesing*
+
+* JSON: +Object#to_json+ calls +as_json+ to coerce itself into something natively encodable like +Hash+, +Integer+, or +String+. Override +as_json+ instead of +to_json+ so you're JSON library agnostic. *Jeremy Kemper*
+
+* String #to_time and #to_datetime: handle fractional seconds #864 *Jason Frey*
+
+* Update bundled TZInfo to v0.3.13 *Geoff Buesing*
+
+* Allow MemCacheStore to be initialized with a MemCache-like object instead of addresses and options *Bryan Helmkamp*
+
+* Change spelling of Kyev timezone to Kyiv #2613 *Alexander Dymo*
+
+* Add ActiveSupport.parse_json_times to disable time parsing in JSON backends that don't support it or don't need it. *rick*
+
+* Add pluggable JSON backends with support for the JSON gem. *rick*
+ Example: ActiveSupport::JSON.backend = "JSONGem"
+
+ All internal Rails JSON encoding is now handled by ActiveSupport::JSON.encode(). Use of #to_json is not recommended, as it may clash with other libraries that overwrite it. However, you can recover Rails specific functionality
+ if you really want to use #to_json.
+
+ gem 'json'
+ ActiveSupport::JSON.backend = "JSONGem"
+
+ class ActiveRecord::Base
+ alias to_json rails_to_json
+ end
+
+* require 'active_support' no longer orders the whole menu of core extensions. Ask for just what you need: e.g. require 'active_support/core/time' to use timezones, durations, and stdlib date/time extensions. *Jeremy Kemper*
+
+* Removed rarely-used DRb cache store. *Jeremy Kemper*
+
+* TimeWithZone.name returns 'Time', to further thwart type checking *Geoff Buesing*
+
+* Time.local instances: Adding 24.hours across the DST boundary adds 24 hours instead of one day #2066 *Michael Curtis*
+
+
+## 2.3.2 Final (March 15, 2009) ##
+
+* XmlMini supports LibXML and Nokogiri backends. #2084, #2190 *Bart ten Brinke, Aaron Patterson*
+ Example: XmlMini.backend = 'Nokogiri'
+
+* Vendorize i18n 0.1.3 gem (fixes issues with incompatible character encodings in Ruby 1.9) #2038 *Akira Matsuda*
+
+* Update bundled memcache-client from 1.5.0.5 to 1.6.4.99. See http://www.mikeperham.com/2009/02/15/memcache-client-performance/ *Mike Perham*
+
+* Ruby 1.9.1p0 fix: URI.unescape can decode multibyte chars. #2033 *MOROHASHI Kyosuke*
+
+* Time#to_s(:rfc822) uses #formatted_offset instead of unreliable and non-standard %z directive #1899 *Zachary Zolton*
+
+* Make TimeWithZone#to_formatted_s an alias to TimeWithZone#to_s #1796 *Levin Alexander*
+
+* Introduce Array.wrap(foo) to wrap the argument in an array unless it's already an array. Wraps nil as an empty array. Use instead of Array(foo) and foo.to_a since they treat String as Enumerable. *Jeremy Kemper*
+
+* TimeWithZone#xmlschema accepts optional fraction_digits argument [#1725 state:resolved] *Nicholas Dainty*
+
+* Object#tap shim for Ruby < 1.8.7. Similar to Object#returning, tap yields self then returns self. *Jeremy Kemper*
+ array.select { ... }.tap(&:inspect).map { ... }
+
+* TimeWithZone#- gives correct result with wrapped DateTime, and with DateTime argument *Geoff Buesing*
+
+* Updated i18n gem to version 0.1.1 #1635 *Yaroslav Markin*
+
+* Add :allow_nil option to delegate. #1127 *Sergio Gil*
+
+* Add Benchmark.ms convenience method to benchmark realtime in milliseconds. *Jeremy Kemper*
+
+* Updated included memcache-client to the 1.5.0.5 version which includes fixes from fiveruns and 37signals to deal with failover and timeouts #1535 *Joshua Sierles*
+
+* Multibyte: add multibyte-safe Chars#ord rather than falling back to String#ord. #1483 *Jason Cheow*
+
+* I18n support for Array#to_sentence. Introduces support.array.words_connector, .two_words_connector, and .last_word_connector translation keys. #1397 *Akira Matsuda*
+
+* Added ActiveSupport::OrderedHash#each_key and ActiveSupport::OrderedHash#each_value #1410 *Christoffer Sawicki*
+
+* Added ActiveSupport::MessageVerifier and MessageEncryptor to aid users who need to store signed and/or encrypted messages. *Michael Koziarski*
+
+* Added ActiveSupport::BacktraceCleaner to cut down on backtrace noise according to filters and silencers *David Heinemeier Hansson*
+
+* Added Object#try. ( Taken from http://ozmm.org/posts/try.html ) *Chris Wanstrath*
+
+* Added Enumerable#none? to check that none of the elements match the block #1408 *Damian Janowski*
+
+* TimeZone offset tests: use current_period, to ensure TimeZone#utc_offset is up-to-date *Geoff Buesing*
+
+* Update bundled TZInfo to 0.3.12 *Geoff Buesing*
+
+* Added lambda merging to OptionMerger (especially useful with named_scope and with_options) #726 *Paweł Kondzior*
+
+
+## 2.2.1 RC2 (November 14th, 2008) ##
+
+* Increment the version of our altered memcache-client to prevent confusion caused when the 1.5.0 gem is installed.
+
+* Fixed the option merging in Array#to_xml #1126 *Rudolf Gavlas*
+
+* Make I18n::Backend::Simple reload its translations in development mode *David Heinemeier Hansson/Sven Fuchs*
+
+
+## 2.2.0 RC1 (October 24th, 2008) ##
+
+* TimeWithZone#freeze: preload instance variables so that we can actually freeze *Geoff Buesing*
+
+* Fix Brasilia timezone #1180 *Marcus Derencius, Kane*
+
+* Time#advance recognizes fractional days and weeks. Deprecate Durations of fractional months and years #970 *Tom Lea*
+
+* Add ActiveSupport::Rescuable module abstracting ActionController::Base rescue_from features. *Norbert Crombach, Pratik Naik*
+
+* Switch from String#chars to String#mb_chars for the unicode proxy. *Manfred Stienstra*
+
+ This helps with 1.8.7 compatibility and also improves performance for some operations by reducing indirection.
+
+* TimeWithZone #wday, #yday and #to_date avoid trip through #method_missing *Geoff Buesing*
+
+* Added Time, Date, DateTime and TimeWithZone #past?, #future? and #today? #720 *Clemens Kofler, Geoff Buesing*
+
+* Fixed Sri Jayawardenepura time zone to map to Asia/Colombo *Jamis Buck*
+
+* Added Inflector#parameterize for easy slug generation ("Donald E. Knuth".parameterize => "donald-e-knuth") #713 *Matt Darby*
+
+* Changed cache benchmarking to be reported in milliseconds *David Heinemeier Hansson*
+
+* Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are now correctly unmarshaled with a utc zone instead of the system local zone [#900 state:resolved] *Luca Guidi, Geoff Buesing*
+
+* Add Array#in_groups which splits or iterates over the array in specified number of groups. #579. [Adrian Mugnolo] Example:
+
+ a = (1..10).to_a
+ a.in_groups(3) # => [[1, 2, 3, 4], [5, 6, 7, nil], [8, 9, 10, nil]]
+ a.in_groups(3, false) # => [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
+
+* Fix TimeWithZone unmarshaling: coerce unmarshaled Time instances to utc, because Ruby's marshaling of Time instances doesn't respect the zone *Geoff Buesing*
+
+* Added Memoizable mixin for caching simple lazy loaded attributes *Josh Peek*
+
+* Move the test related core_ext stuff out of core_ext so it's only loaded by the test helpers. *Michael Koziarski*
+
+* Add Inflection rules for String#humanize. #535 *Dan Manges*
+
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.human(/_cnt$/i, '\1_count')
+ end
+
+ 'jargon_cnt'.humanize # => 'Jargon count'
+
+* TimeWithZone: when crossing DST boundary, treat Durations of days, months or years as variable-length, and all other values as absolute length. A time + 24.hours will advance exactly 24 hours, but a time + 1.day will advance 23-25 hours, depending on the day. Ensure consistent behavior across all advancing methods *Geoff Buesing*
+
+* Added TimeZone #=~, to support matching zones by regex in time_zone_select. #195 *Ernie Miller*
+
+* Added Array#second through Array#fifth as aliases for Array#[1] through Array#[4] + Array#forty_two as alias for Array[41] *David Heinemeier Hansson*
+
+* Added test/do declaration style testing to ActiveSupport::TestCase *DHH via Jay Fields*
+
+* Added Object#present? which is equivalent to !Object#blank? *David Heinemeier Hansson*
+
+* Added Enumberable#many? to encapsulate collection.size > 1 *David Heinemeier Hansson/Damian Janowski*
+
+* Add more standard Hash methods to ActiveSupport::OrderedHash *Steve Purcell*
+
+* Namespace Inflector, Dependencies, OrderedOptions, and TimeZone under ActiveSupport *Josh Peek*
+
+* Added StringInquirer for doing things like StringInquirer.new("production").production? # => true and StringInquirer.new("production").development? # => false *David Heinemeier Hansson*
+
+* Fixed Date#end_of_quarter to not blow up on May 31st [#289 state:resolved] (Danger)
+
+
+## 2.1.0 (May 31st, 2008) ##
+
+* TimeZone#to_s shows offset as GMT instead of UTC, because GMT will be more familiar to end users (see time zone selects used by Windows OS, google.com and yahoo.com.) Reverts [8370] *Geoff Buesing*
+
+* Hash.from_xml: datetime xml types overflow to Ruby DateTime class when out of range of Time. Adding tests for utc offsets *Geoff Buesing*
+
+* TimeWithZone #+ and #- : ensure overflow to DateTime with Numeric arg *Geoff Buesing*
+
+* Time#to_json: don't convert to utc before encoding. References #175 *Geoff Buesing*
+
+* Remove unused JSON::RESERVED_WORDS, JSON.valid_identifier? and JSON.reserved_word? methods. Resolves #164. *Cheah Chu Yeow*
+
+* Adding Date.current, which returns Time.zone.today if config.time_zone is set; otherwise returns Date.today *Geoff Buesing*
+
+* TimeWithZone: date part getter methods (#year #mon #day etc) are defined on class; no longer relying on method_missing *Geoff Buesing*
+
+* Time.zone.parse return nil for strings with no date information *Geoff Buesing*
+
+* Time.zone.parse respects offset information in string. Resolves #105. *Scott Fleckenstein, Geoff Buesing*
+
+* Added Ruby 1.8 implementation of Process.daemon
+
+* Duration #since and #ago with no argument (e.g., 5.days.ago) return TimeWithZone when config.time_zone is set. Introducing Time.current, which returns Time.zone.now if config.time_zone is set, otherwise just returns Time.now *Geoff Buesing*
+
+* Time#since behaves correctly when passed a Duration. Closes #11527 *kemiller*
+
+* Add #getutc alias for DateTime#utc *Geoff Buesing*
+
+* Refactor TimeWithZone: don't send #since, #ago, #+, #-, #advance through method_missing *Geoff Buesing*
+
+* TimeWithZone respects config.active_support.use_standard_json_time_format *Geoff Buesing*
+
+* Add config.active_support.escape_html_entities_in_json to allow disabling of html entity escaping. *Rick Olson*
+
+* Improve documentation. *Xavier Noria*
+
+* Modified ActiveSupport::Callbacks::Callback#call to accept multiple arguments.
+
+* Time #yesterday and #tomorrow behave correctly crossing DST boundary. Closes #7399 *sblackstone*
+
+* TimeWithZone: Adding tests for dst and leap day edge cases when advancing time *Geoff Buesing*
+
+* TimeWithZone#method_missing: send to utc to advance with dst correctness, otherwise send to time. Adding tests for time calculations methods *Geoff Buesing*
+
+* Add config.active_support.use_standard_json_time_format setting so that Times and Dates export to ISO 8601 dates. *Rick Olson*
+
+* TZInfo: Removing unneeded TimezoneProxy class *Geoff Buesing*
+
+* TZInfo: Removing unneeded TimezoneIndexDefinition, since we're not including Indexes::Timezones *Geoff Buesing*
+
+* Removing unnecessary uses_tzinfo helper from tests, given that TZInfo is now bundled *Geoff Buesing*
+
+* Bundling abbreviated version of TZInfo gem 0.3.8: only the classes and zone definitions required to support Rails time zone features are included. If a recent version of the full TZInfo gem is installed, this will take precedence over the bundled version *Geoff Buesing*
+
+* TimeWithZone#marshal_load does zone lookup via Time.get_zone, so that tzinfo/Olson identifiers are handled *Geoff Buesing*
+
+* Time.zone= accepts TZInfo::Timezone instances and Olson identifiers; wraps result in TimeZone instance *Geoff Buesing*
+
+* TimeWithZone time conversions don't need to be wrapped in TimeOrDateTime, because TZInfo does this internally *Geoff Buesing*
+
+* TimeWithZone#usec returns 0 instead of error when DateTime is wrapped *Geoff Buesing*
+
+* Improve documentation. *Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert*
+
+* Ensure that TimeWithZone#to_yaml works when passed a YAML::Emitter. *Rick Olson*
+
+* Ensure correct TimeWithZone#to_date *Geoff Buesing*
+
+* Make TimeWithZone work with tzinfo 0.2.x: use TZInfo::Timezone#zone_identifier alias for #abbreviation, silence warnings on tests. Raise LoadError when TZInfo version is < 0.2 by sniffing for TZInfo::TimeOrDateTime constant. Move all tzinfo-dependent TimeZone tests into uses_tzinfo block *Geoff Buesing*
+
+* Time, DateTime and TimeWithZone #in_time_zone defaults to Time.zone. Removing now unneeded #in_current_time_zone *Geoff Buesing*
+
+* TZInfo caches Timezone instances in its own internal hash cache, so TimeZone::MAPPING doesn't need to cache them as well *Geoff Buesing*
+
+* Adding TimeZone#parse *Geoff Buesing*
+
+* Adding TimeZone#at and DateTime#to_f *Geoff Buesing*
+
+* TimeWithZone responds to Ruby 1.9 weekday-named query methods *Geoff Buesing*
+
+* TimeWithZone caches TZInfo::TimezonePeriod used for time conversion so that it can be reused, and enforces DST rules correctly when instance is created from a local time *Geoff Buesing*
+
+* Fixed that BufferedLogger should create its own directory if one doesn't already exist #11285 *lotswholetime*
+
+* Fix Numeric time tests broken by DST change by anchoring them to fixed times instead of Time.now. Anchor TimeZone#now DST test to time specified with Time.at instead of Time.local to work around platform differences with Time.local and DST representation *Geoff Buesing*
+
+* Removing unneeded #change_time_zone method from Time, DateTime and TimeWithZone *Geoff Buesing*
+
+* TimeZone #local and #now correctly enforce DST rules *Geoff Buesing*
+
+* TimeWithZone instances correctly enforce DST rules. Adding TimeZone#period_for_utc *Geoff Buesing*
+
+* test_time_with_datetime_fallback expects DateTime.local_offset instead of DateTime.now.offset *Geoff Buesing*
+
+* Adding TimeWithZone #marshal_dump and #marshal_load *Geoff Buesing*
+
+* Add OrderedHash#to_hash *Josh Peek*
+
+* Adding Time#end_of_day, _quarter, _week, and _year. #9312 *Juanjo Bazan, Tarmo Tänav, BigTitus*
+
+* Adding TimeWithZone#between? *Geoff Buesing*
+
+* Time.=== returns true for TimeWithZone instances *Geoff Buesing*
+
+* TimeWithZone #+ and #- behave consistently with numeric arguments regardless of whether wrapped time is a Time or DateTime; consistenty answers false to #acts_like?(:date) *Geoff Buesing*
+
+* Add String#squish and String#squish! to remove consecutive chunks of whitespace. #11123 *Jordi Bunster, Henrik N*
+
+* Serialize BigDecimals as Floats when using to_yaml. #8746 *Ernesto Jimenez*
+
+* Adding TimeWithZone #to_yaml, #to_datetime, #eql? and method aliases for duck-typing compatibility with Time *Geoff Buesing*
+
+* TimeWithZone #in_time_zone returns +self+ if zone argument is the same as #time_zone *Geoff Buesing*
+
+* Adding TimeWithZone #to_a, #to_f, #to_i, #httpdate, #rfc2822 *Geoff Buesing*
+
+* Pruning unneeded TimeWithZone#change_time_zone_to_current *Geoff Buesing*
+
+* Time#zone=, #in_time_zone and #change_time_zone accept a Duration *Geoff Buesing*
+
+* Time#in_time_zone handles Time.local instances correctly *Geoff Buesing*
+
+* Pruning unneeded Time#change_time_zone_to_current. Enhanced docs to #change_time_zone to explain the difference between this method and #in_time_zone *Geoff Buesing*
+
+* TimeZone#new method renamed #local; when used with Time.zone, constructor now reads: Time.zone.local() *Geoff Buesing*
+
+* Added Base64.encode64s to encode values in base64 without the newlines. This makes the values immediately usable as URL parameters or memcache keys without further processing *David Heinemeier Hansson*
+
+* Remove :nodoc: entries around the ActiveSupport test/unit assertions. #10946 *dancroak, jamesh*
+
+* Add Time.zone_default accessor for setting the default time zone. Rails::Configuration.time_zone sets this. #10982 *Geoff Buesing*
+
+* cache.fetch(key, :force => true) to force a cache miss. *Jeremy Kemper*
+
+* Support retrieving TimeZones with a Duration. TimeZone[-28800] == TimeZone[-480.minutes]. *Rick Olson*
+
+* TimeWithZone#- added, so that #- can handle a Time or TimeWithZone argument correctly *Geoff Buesing*
+
+* with_timezone test helper renamed with_env_tz, to distinguish between setting ENV['TZ'] and setting Time.zone in tests *Geoff Buesing*
+
+* Time#- coerces TimeWithZone argument to a Time instance so that difference in seconds can be calculated. Closes #10914 *Geoff Buesing, yyyc514*
+
+* Adding UTC zone to TimeZone; TimeWithZone no longer has to fake UTC zone with nil *Geoff Buesing*
+
+* Time.get_zone refactored to private method, given that the encapsulated logic is only useful internally *Geoff Buesing*
+
+* Time.zone uses thread-local variable for thread safety. Adding Time.use_zone, for overriding Time.zone locally inside a block. Removing unneeded Time.zone_reset! *Geoff Buesing*
+
+* TimeZone#to_s uses UTC rather than GMT; reapplying change that was undone in [8679]. #1689 *Cheah Chu Yeow*
+
+* Time.days_in_month defaults to current year if no year is supplied as argument #10799 [Radar], uses Date.gregorian_leap? to determine leap year, and uses constant lookup to determine days in month *Geoff Buesing*
+
+* Adding Time and DateTime #compare_with_coercion, which layers behavior on #<=> so that any combination of Time, DateTime and ActiveSupport::TimeWithZone instances can be chronologically compared *Geoff Buesing*
+
+* TimeZone#now returns an ActiveSupport::TimeWithZone *Geoff Buesing*
+
+* Time #in_current_time_zone and #change_time_zone_to_current return self when Time.zone is nil *Geoff Buesing*
+
+* Remove unneeded #to_datetime_default_s alias for DateTime#to_s, given that we inherit a #to_default_s from Date that does exactly the same thing *Geoff Buesing*
+
+* Refactor Time and DateTime #to_formatted_s: use ternary instead of nested if/else *Geoff Buesing*
+
+* Adding Time and DateTime #formatted_offset, for outputting +HH:MM utc offset strings with cross-platform consistency *Geoff Buesing*
+
+* Adding alternate_utc_string option to TimeZone#formatted_offset. Removing unneeded TimeZone#offset. *Geoff Buesing*
+
+* Introduce ActiveSupport::TimeWithZone, for wrapping Time instances with a TimeZone. Introduce instance methods to Time for creating TimeWithZone instances, and class methods for managing a global time zone. *Geoff Buesing*
+
+* Replace non-dst-aware TimeZone class with dst-aware class from tzinfo_timezone plugin. TimeZone#adjust and #unadjust are no longer available; tzinfo gem must now be present in order to perform time zone calculations, via #local_to_utc and #utc_to_local methods. *Geoff Buesing*
+
+* Extract ActiveSupport::Callbacks from Active Record, test case setup and teardown, and ActionController::Dispatcher. #10727 *Josh Peek*
+
+* Introducing DateTime #utc, #utc? and #utc_offset, for duck-typing compatibility with Time. Closes #10002 *Geoff Buesing*
+
+* Time#to_json uses Numeric#to_utc_offset_s to output a cross-platform-consistent representation without having to convert to DateTime. References #9750 *Geoff Buesing*
+
+* Refactor number-to-HH:MM-string conversion logic from TimeZone#formatted_offset to a reusable Numeric#to_utc_offset_s method. *Geoff Buesing*
+
+* Continue evolution toward ActiveSupport::TestCase. #10679 *Josh Peek*
+
+* TestCase: introduce declared setup and teardown callbacks. Pass a list of methods and an optional block to call before setup or after teardown. Setup callbacks are run in the order declared; teardown callbacks are run in reverse. *Jeremy Kemper*
+
+* Added ActiveSupport::Gzip.decompress/compress(source) as an easy wrapper for Zlib *Tobias Lütke*
+
+* Included MemCache-Client to make the improved ActiveSupport::Cache::MemCacheStore work out of the box *Bob Cottrell, Eric Hodel*
+
+## Added ActiveSupport::Cache:: framework as an extraction from ActionController::Caching::Fragments:: David Heinemeier Hansson ##
+
+* Fixed String#titleize to work for strings with 's too #10571 *trek*
+
+* Changed the implementation of Enumerable#group_by to use a double array approach instead of a hash such that the insert order is honored *David Heinemeier Hansson/Marcel Molina Jr.*
+
+* remove multiple enumerations from ActiveSupport::JSON#convert_json_to_yaml when dealing with date/time values. *Rick Olson*
+
+* Hash#symbolize_keys skips keys that can't be symbolized. #10500 *Brad Greenlee*
+
+* Ruby 1.9 compatibility. #1689, #10466, #10468, #10554, #10594, #10632 *Cheah Chu Yeow, Pratik Naik, Jeremy Kemper, Dirkjan Bussink, Xavier Noria*
+
+* TimeZone#to_s uses UTC rather than GMT. #1689 *Cheah Chu Yeow*
+
+* Refactor of Hash#symbolize_keys! to use Hash#replace. Closes #10420 *ReinH*
+
+* Fix HashWithIndifferentAccess#to_options! so it doesn't clear the options hash. Closes #10419 *ReinH*
+
+
+## 2.0.1 (December 7th, 2007) ##
+
+* Added Array#from and Array#to that behaves just from String#from and String#to *David Heinemeier Hansson*
+
+* Fix that empty collections should be treated as empty arrays regardless of whitespace for Hash#from_xml #10255 *adamj*
+
+* Time#time_with_datetime_fallback, Time#to_datetime, Date#to_datetime and String#to_datetime honor Ruby's default calendar reform setting. #10201 *Geoff Buesing*
+
+* Change Time and DateTime #end_of_month to return last second of month instead of beginning of last day of month. Closes #10200 *Geoff Buesing*
+
+* Speedup String#blank? *Jeremy Kemper, Michael Koziarski*
+
+* Add documentation for Hash#diff. Closes #9306 *Tarmo Tänav*
+
+* Add new superclass_delegating_accessors. Similar to class inheritable attributes but with subtly different semantics. *Michael Koziarski, Tarmo Tänav*
+
+* Change JSON to encode %w(< > &) as 4 digit hex codes to be in compliance with the JSON spec. Closes #9975 *Josh Peek, Cheah Chu Yeow, Tim Pope*
+
+* Fix JSON encoding/decoding bugs dealing with /'s. Closes #9990 *Rick Olson, theamazingrando*
+
+* Introduce a base class for all test cases used by rails applications. ActiveSupport::TestCase *Michael Koziarski*
+
+ The intention is to use this to reduce the amount of monkeypatching / overriding that
+ is done to test/unit's classes.
+
+* Document Enumerable and Hash #to_json. #9970 *Cheah Chu Yeow*
+
+* Hash#to_xml handles symbol values. #9954 *Assaf*
+
+* Hash#symbolize_keys behaves well with integer keys. #9890 *PotatoSalad*
+
+* Multibyte: String#slice supports regexp argument. #9646 *yob*
+
+* object.duplicable? returns true if object.dup is safe. False for nil, true, false, symbols, and numbers; true otherwise. #9333 *sur*
+
+* Time, Date and DateTime #advance accept :weeks option. #9866 *Geoff Buesing*
+
+* Fix Time#years_ago and #years_since from leap days. #9865 *Geoff Buesing*
+
+* Time and DateTime#advance accept :hours, :minutes, and :seconds options. #9825 *Geoff Buesing*
+
+* Fix Date#years_ago and #years_since from leap days. #9864 *Geoff Buesing*
+
+* Refactor Time and Date#months_since and #months_ago to use #advance. #9863 *Geoff Buesing*
+
+* Rebundle Builder 2.1.2 but prefer a newer RubyGem if available. *Jeremy Kemper*
+
+* Add Range#overlaps?(range), Range#include?(range), and Range#step without a block. *brandon*
+
+* Correct BufferedLogger#level? checks. #9806 *wildchild, Johan Sorensen*
+
+* String#to_xs uses Eric Wong's fast_xs extension, if available, for Builder speedup. http://bogomips.org/fast_xs/ *Jeremy Kemper*
+
+* Introduce BasicObject as Builder::BlankSlate for Ruby 1.9 forward compatibility. *Jeremy Kemper*
+
+* Unbundle Builder in favor of a gem dependency. *Jeremy Kemper*
+
+* Disambiguate Time, Date, and DateTime#to_json formatting. #9750 *Geoff Buesing, Cheah Chu Yeow*
+
+* Hash#to_json takes :only or :except options to specific or omit certain hash keys. Enumerable#to_json passes through its options to each element. #9751 *Cheah Chu Yeow*
+
+* BufferedLogger#auto_flushing = N flushes the log every N messages. Buffers with an array instead of string. Disabling auto_flushing still flushes when the buffer hits a maximum size, as a failsafe against memory-gobbling. *Jeremy Kemper*
+
+* Fixed Date#xmlschema for dates outside the range of what can be created with Time #9744 *Geoff Buesing*
+
+* Fixed that La Paz was included in -25200 and -14400 offsets when it should only be in -14400 #9735 *bermi*
+
+* Fixed JSON encoding to use quoted keys according to the JSON standard. #8762 *choonkat, Cheah Chu Yeow*
+
+* Alias Object#send to send! for Ruby 1.9 forward compatibility. *Jeremy Kemper*
+
+* Backport Object#instance_variable_defined? for Ruby < 1.8.6. *Jeremy Kemper*
+
+* BufferedLogger#add converts the message to a string. #9702, #9724 *eigentone, DrMark, Tom Ward*
+
+* Added ActiveSupport::BufferedLogger as a duck-typing alternative (albeit with no formatter) to the Ruby Logger, which provides a very nice speed bump (inspired by Ezra's buffered logger) *David Heinemeier Hansson*
+
+* Object#instance_exec produces fewer garbage methods. *Mauricio Fernandez*
+
+* Decode json strings as Dates/Times if they're using a YAML-compatible format. Closes #9614 *Rick Olson*
+
+* Fixed cache_page to use the request url instead of the routing options when picking a save path. #8614 *Josh Peek*
+
+* Object.subclasses_of includes anonymous subclasses. *Jeremy Kemper*
+
+* Fixed that pluralizing an empty string should return the same empty string, not "s". #7720 *Josh Peek*
+
+* Added call to inspect on non-string classes for the logger #8533 *Coda Hale*
+
+* Deprecation: remove deprecated :mday option from Time, Date, and DateTime#change. *Jeremy Kemper*
+
+* Fix JSON decoder with nested quotes and commas. #9579 *Zach Dennis*
+
+* Hash#to_xml doesn't double-unescape. #8806 *Ezran*
+
+* Added Array#rand #9170 [Norbert Crombach]. Examples:
+
+ [].rand # => nil
+ ['a'].rand # => 'a'
+ [1,2,3].rand # => 1 or 2 or 3
+
+* Deprecation: removed Reloadable. *Jeremy Kemper*
+
+* Make the utf-handler return the correct value for non-matching regular expressions. Closes #9049 *Manfred Stienstra*
+
+* Add ljust, rjust and center to utf8-handler. Closes #9165 *Manfred Stienstra*
+
+* Fix Time#advance bug when trying to advance a year from leap day. Closes #8655 *Geoff Buesing*
+
+* Add support for []= on ActiveSupport::Multibyte::Chars. Closes #9142. *ewan, Manfred Stienstra*
+
+* Added Array#extract_options! to encapsulate the pattern of getting an options hash out of a variable number of parameters. #8759 *Norbert Crombach*
+
+* Let alias_attribute work with attributes with initial capital letters (legacy columns etc). Closes #8596 *mpalmer*
+
+* Added Hash#except which is the inverse of Hash#slice -- return the hash except the keys that are specified *David Heinemeier Hansson*
+
+* Added support for pluralization with a different starting letter than the singular version (cow/kine) #4929 *norri_b/Josh Susser*
+
+* Demote Hash#to_xml to use XmlSimple#xml_in_string so it can't read files or stdin. #8453 *candlerb, Jeremy Kemper*
+
+* Backport clean_logger changes to support ruby 1.8.2 *Mislav Marohnić*
+
+* Added proper handling of arrays #8537 *Josh Susser*
+
+ Before:
+ Hash.from_xml '<images></images>'
+ # => {:images => nil}
+
+ Hash.from_xml '<images><image>foo.jpg</image></images>'
+ # => {:images => {:image => "foo.jpg"}}
+
+ Hash.from_xml '<images><image>foo.jpg</image><image>bar.jpg</image></images>'
+ # => {:images => {:image => ["foo.jpg", "bar.jpg"]}}
+
+ After:
+ Hash.from_xml '<images type="array"></images>'
+ # => {:images => []}
+
+ Hash.from_xml '<images type="array"><image>foo.jpg</image></images>'
+ # => {:images => ["foo.jpg"]}
+
+ Hash.from_xml '<images type="array"><image>foo.jpg</image><image>bar.jpg</image></images>'
+ # => {:images => ["foo.jpg", "bar.jpg"]}
+
+* Improve Time and Date test coverage. #8646 *Josh Peek*
+
+* Add Date#since, ago, beginning_of_day, and end_of_day. Date + seconds works now. #8575 *Geoff Buesing*
+
+* String#to_time overflows to DateTime. Add String#to_datetime. #8572 *Geoff Buesing*
+
+* Date.yesterday and .tomorrow. #8571 *Geoff Buesing*
+
+* Readable Date and DateTime#inspect. #8570 *Geoff Buesing*
+
+* Move common DateTime calculations to Date. #8536 *Geoff Buesing*
+
+* Added Date#change (like Time#change) *David Heinemeier Hansson*
+
+* DateTime#to_time converts to Time unless out of range. #8512 *Geoff Buesing*
+
+* Date#to_datetime, #to_s(:rfc822). #8512 *Geoff Buesing*
+
+* Time durations use since instead of + for accuracy. #8513 *Geoff Buesing*
+
+* escape <'s and >'s in JSON strings. #8371 *Rick Olson*
+
+* Inflections: MatrixTest -> MatrixTests instead of MatricesTest. #8496 *jbwiv*
+
+* Multibyte strings respond_to the String methods they proxy so they can be duck-typed. #6549 *Tuxie*
+
+* Array#to_xml yields the builder just like Hash and ActiveRecord::Base. #8472 *seth*
+
+* Date, Time, and DateTime support formatting blocks in addition to strftime strings. Introduce :long_ordinal format, e.g. "February 21st, 2005". #8191 *Coda Hale*
+
+* Document Object#blank?. #6491 *Chris Mear*
+
+* Date, Time, and DateTime#to_json. #8399 *wycats*
+
+* Simplify API of assert_difference by passing in an expression that is evaluated before and after the passed in block. See documenation for examples of new API. *Marcel Molina Jr.*
+
+* Added assert_difference and assert_no_difference to test/unit assertions *Tobias Lütke*
+
+* Removed breakpointer and Binding.of_caller in favor of relying on ruby-debug by Kent Sibilev since the breakpointer has been broken since Ruby 1.8.4 and will not be coming back *David Heinemeier Hansson*
+
+* Added parsing of file type in Hash.xml_in so you can easily do file uploads with base64 from an API *David Heinemeier Hansson*
+
+ <person>
+ <name>David</name>
+ <avatar type="file" name="me.jpg" content_type="image/jpg">R0lGODlhkACZAPUAAM5lcfjrtMQCG=\n</avatar>
+ </person>
+
+ ...becomes:
+
+ attributes = { :person => { :name => "David", :avatar => #<StringIO> } }
+ attributes[:person][:avatar].content_type # => "image/jpg"
+ attributes[:person][:avatar].original_filename # => "me.jpg"
+ attributes[:person][:avatar].read # => binary data of the file
+
+ Which is duck-type compatible with the files that you get when doing multipart uploads through HTML.
+
+* Improved multibyte performance by relying less on exception raising #8159 *Blaine*
+
+* Use XSD-compatible type names for Hash#to_xml and make the converters extendable #8047 *Tim Pope*
+
+* Added yielding of builder in Hash#to_xml *David Heinemeier Hansson*
+
+* Hash#with_indifferent_access now also converts hashes kept in arrays to indifferent access (makes it easier to treat HTML and XML parameters the same) *David Heinemeier Hansson*
+
+* Hash#to_xml supports YAML attributes. #7502 *jonathan*
+
+* Refactor ActiveSupport::JSON to be less obtuse. Add support for JSON decoding by way of Syck with ActiveSupport::JSON.decode(json_string). Prevent hash keys that are JavaScript reserved words from being unquoted during encoding. *Sam Stephenson*
+
+* alias_method_chain preserves the original method's visibility. #7854 *Jonathan Viney*
+
+* Update Dependencies to ignore constants inherited from ancestors. Closes #6951. *Nicholas Seckar*
+
+* Array#to_query preserves its ordering. #7756 *Greg Spurrier*
+
+* Out-of-range Time calculations transparently overflow to DateTime. Introduce Time#to_datetime. #7706, #7715 *Geoff Buesing*
+
+* DateTime calculations analogous to the Date and Time extensions. #7693 *Geoff Buesing*
+
+* Give DateTime correct .to_s implementations, lets it play nice with ActiveRecord quoting. #7649 *Geoff Buesing*
+
+* Add File.atomic_write, allows you to write large files in an atomic manner, preventing users from seeing half written files. *Michael Koziarski*
+
+* Allow users to provide custom formatters to Logger. *Anthony Eden*
+
+* Hash#to_query CGI-escapes its keys. *Jeremy Kemper*
+
+* Optimize Class Inheritable Attributes so that unnecessary hashes are not created. Closes #7472 *Bruce Perens*
+
+* :db format for Date#to_s *Jeremy Kemper*
+ Date.new(2007, 1, 27).to_s(:db) # => '2007-01-27'
+
+* Added :instance_writer option to #mattr_writer/accessor, #cattr_writer/accessor, and #class_inheritable_writer to skip the creation of the instance writer. *Rick Olson*
+
+* Added Hash#to_query to turn a hash of values into a form-encoded query string *Nicholas Seckar*
+
+* Increase test coverage for subclasses_of. Closes #7335. *Roman2K, Nicholas Seckar*
+
+* Remove unused code from Duration#inspect. Closes #7180. *Rich Collins*
+
+* Added test coverage for Inflector.inflections.clear. Closes #7179. *Rich Collins*
+
+* ActiveSupport::Multibyte::Handlers::UTF8Handler should raise when a range and an integer are passed in (just like the native implementation). Closes #7176 *Rich Collins*
+
+* A couple extra tests for #classify. Closes #7273. *Josh Susser*
+
+* Better docs for Object extensions *zackchandler, Jamis Buck*
+
+* Fix that Dates couldn't be subtracted from Dates after [5940]. *Sam Stephenson*
+
+* Add Object#acts_like? and Time#acts_like_time? and Date#acts_like_date? to facilitate duck-typing. *Jamis Buck*
+
+* Make 1.months and friends accurate by introducing a Duration class. #6835 *eventualbuddha*
+
+
+## 1.4.2 (March 12th, 2007) ##
+
+* Ruby 1.8.6 and 1.9 define private Time#to_date and #to_datetime; make them
+ public for compatibility. *Jeremy Kemper*
+
+* Deprecation: warn on stderr if RAILS_DEFAULT_LOGGER isn't set yet. *Jeremy Kemper*
+
+
+## 1.4.1 (February 5th, 2007) ##
+
+* Optimize Class Inheritable Attributes so that unnecessary hashes are not created. Closes #7472 *Bruce Perens*
+
+* Added :instance_writer option to #mattr_writer/accessor, #cattr_writer/accessor, and #class_inheritable_writer to skip the creation of the instance writer. *Rick Olson*
+
+* Full test coverage for Inflector. #7228 *Dan Kubb*
+
+
+## 1.4.0 (January 16th, 2007) ##
+
+* Document Inflector.ordinalize and merge docs from String inflections. #7023 *smeade*
+
+* Unbundle flexmock. *Jeremy Kemper*
+
+* Fix Dependencies.autoloaded? to ignore anonymous modules. Closes #6561. *Nicholas Seckar*
+
+* Update load once paths to prevent nested once constants from being detected and claimed by an external non-once load. *Nicholas Seckar*
+
+* Deprecation: silence warnings when reporting test errors. *Jeremy Kemper*
+
+* Hash#slice(*keys) returns a new hash with only the given keys. #slice! replaces the hash with only the given keys. Works with HashWithIndifferentAccess also. *Jeremy Kemper*
+
+* HashWithIndifferentAccess#to_hash converts to a Hash with String keys and the same default value. *Jeremy Kemper*
+
+* Fix remove_constant to correctly handle constant names of the form "::A::...". References #6720. *Nicholas Seckar*
+
+* Fixed Array#to_xml when it contains a series of hashes (each piece would get its own XML declaration) #6610 *thkarcher/cyu*
+
+* Added Time#to_s(:time) which will just return H:M, like 17:44 *David Heinemeier Hansson*
+
+* Add Module#attr_accessor_with_default to initialize value of attribute before setting it. Closes #6538. *Stuart Halloway, Marcel Molina Jr.*
+
+* Hash#to_xml handles keys with the same name as Kernel methods. #6613 *Jonathan del Strother*
+
+* Added Time#end_of_day to get 23:59:59 of that day *David Heinemeier Hansson*
+
+* Don't quote hash keys in Hash#to_json if they're valid JavaScript identifiers. Disable this with ActiveSupport::JSON.unquote_hash_key_identifiers = false if you need strict JSON compliance. *Sam Stephenson*
+
+* Lazily load the Unicode Database in the UTF-8 Handler *Rick Olson*
+
+* Update dependencies to delete partially loaded constants. *Nicholas Seckar*
+
+* Fix unicode JSON regexp for Onigurama compatibility. #6494 *whitley*
+
+* update XmlSimple to 1.0.10. Closes #6532. *Nick Sieger*
+
+* Update dependencies to allow constants to be defined alongside their siblings. A common case for this is AR model classes with STI; user.rb might define User, Administrator and Guest for example. *Nicholas Seckar*
+
+* next_week respects DST changes. #6483, #5617, #2353, #2509, #4551 *marclove, Rob Biedenharn, rails@roetzel.de, jsolson@damogran.org, drbrain@segment7.net*
+
+* Expose methods added to Enumerable in the documentation, such as group_by. Closes #6170. *sergeykojin@gmail.com, Marcel Molina Jr.*
+
+* Ensure Chars#tidy_bytes only tidies broken bytes. Closes #6397 *Manfred Stienstra*
+
+* Add 'unloadable', a method used to mark any constant as requiring an unload after each request. *Nicholas Seckar*
+
+* Make core_ext/string/access.rb multibyte safe. Closes #6388 *Manfred Stienstra*
+
+* Make String#chars slicing behaviour consistent with String. Closes #6387 *Manfred Stienstra*
+
+* Pull in latest multibyte patch. Closes #6346 *Manfred Stienstra*
+
+* Add ActiveSupport::Multibyte. Provides String#chars which lets you deal with strings as a sequence of chars, not of bytes. Closes #6242 *Julian Tarkhanov, Manfred Stienstra, Thijs van der Vossen & Jan Behrens*
+
+* Fix issue with #class_inheritable_accessor saving updates to the parent class when initialized with an Array or Hash *mojombo*
+
+* Hash#to_xml supports Bignum and BigDecimal. #6313 *edibiase*
+
+* Don't undefine #class in OptionMerger *Rick Olson*
+
+* Hash.create_from_xml has been renamed to Hash.from_xml, alias will exist until Rails 2.0 *David Heinemeier Hansson*
+
+* alias_method_chain works with accessor= methods also. #6153 *Caio Chassot*
+
+* Fix loadable_constants_for_path to handle load paths that do not end with a slash. *Nicholas Seckar*
+
+* Fix logic error in determining what was loaded by a given file. Closes #6039. *Nicholas Seckar*
+
+* Equate Kernel.const_missing with Object.const_missing. Fixes #5988. *Nicholas Seckar*
+
+* Add ApplicationController special case to Dependencies. *Nicholas Seckar*
+
+* Don't pad remaining places with in_groups_of if specified padding value is false. *Marcel Molina Jr.*
+
+* Fix cases where empty xml nodes weren't being translated to nil in Hash.create_from_xml *Rick Olso n*
+
+ <written-on type="date"></written-on> # => { :type => 'date' } # WRONG
+ <written-on type="date"></written-on> # => nil # RIGHT
+
+* Tighten rescue clauses. #5985 *james@grayproductions.net*
+
+* Inflections: don't singularize -ies plurals. *foamdino@gmail.com, Mark Van Holstyn*
+
+* Update Initializer to use load_once_paths to avoid plugin reloading. References #5852. *Nicholas Seckar*
+
+* Use Array#assoc in ActiveSupport::OrderedHash. *Mauricio Fernandez*
+
+* Greatly increased performance of String.to_json, which speeds up RJS considerably on large pages, fixes #3473 *Shugo Maeda*
+
+* Detect missing_constants calls from removed modules and fail accordingly. *Nicholas Seckar*
+
+* Stop using defined? in Dependencies.qualified_const_defined? since defined? may invoke const_missing. *Nicholas Seckar*
+
+* Dependencies can autoload directories of nested classes. *Jeremy Kemper*
+ Example:
+ invoice.rb class Invoice
+ invoice/lineitem.rb class Invoice::Lineitem
+
+* Add Deprecation.silence so that Reloadable does not scold itself. *Nicholas Seckar*
+
+* Add debugging logging to Dependencies. Currently can be enabled with Dependencies.log_activity = true; adding to Initializer and documenting is forthcoming. *Nicholas Seckar*
+
+* Replace Reloadable with improvements to the Dependencies mechanism. *Nicholas Seckar*
+
+* DateTime#to_time gives hour/minute/second resolution. #5747 *jon.evans@pobox.com*
+
+* attr_internal to support namespacing and deprecation. Like attr_* except backed by internally-named instance variable. Set attr_internal_naming_format to change the format from the default '@_%s'. *Jeremy Kemper*
+ # def foo() @foo__rofl end
+ # def foo=(v) @foo__rofl = v end
+ self.attr_internal_naming_format = '@%s__rofl'
+ attr_internal :foo
+
+* Raise fully qualified names upon name errors. #5533 *Lars Pind, Nicholas Seckar*
+
+* Add extention to obtain the missing constant from NameError instances. *Nicholas Seckar*
+
+* Thoroughly document inflections. #5700 *petermichaux@gmail.com*
+
+* Added Module#alias_attribute [Jamis/David Heinemeier Hansson]. Example:
+
+ class Content < ActiveRecord::Base
+ # has a title attribute
+ end
+
+ class Email < ActiveRecord::Base
+ alias_attribute :subject, :title
+ end
+
+ e = Email.find(1)
+ e.title # => "Superstars"
+ e.subject # => "Superstars"
+ e.subject? # => true
+ e.subject = "Megastars"
+ e.title # => "Megastars"
+
+* Deprecation: easier to work with warning behavior as procs; default behaviors for each environment so users needn't update env.rb; and testing pleasure with assert_deprecated, assert_not_deprecated. *Jeremy Kemper*
+ By default, test prints to $stderr, dev logs, production ignores.
+ Provide your own per-environment in e.g. config/environments/development.rb:
+ ActiveSupport::Deprecation.behavior = Proc.new { |message| raise message }
+
+* First cut of the Rails Deprecation system. *Michael Koziarski*
+
+* Strip boolean XML content before checking for 'true' *Rick Olson*
+
+* Customize default BigDecimal formatting. References #5672 *Dave Thomas*
+
+* Correctly convert <foo nil="true"> to nil when using Hash.create_from_xml. *Rick Olson*
+
+* Optional identity for Enumerable#sum defaults to zero. #5657 *gensym@mac.com*
+
+* HashWithIndifferentAccess shouldn't confuse false and nil. #5601 *Shugo Maeda*
+
+* Fixed HashWithIndifferentAccess#default #5586 *chris@seagul.co.uk*
+
+* More compatible Hash.create_from_xml. #5523 *nunemaker@gmail.com*
+
+* Added Enumerable#sum for calculating a sum from the elements [David Heinemeier Hansson, jonathan@daikini.com]. Examples:
+
+ [1, 2, 3].sum
+ payments.sum { |p| p.price * p.tax_rate }
+ payments.sum(&:price)
+
+ This is instead of payments.inject(0) { |sum, p| sum + p.price }
+
+* Correct and clarify Array#to_sentence docs. #5458 *brad@madriska.com*
+
+* alias_method_chain preserves method punctuation so foo, foo?, and foo! may be chained with the same feature. *Jeremy Kemper*
+ Example:
+ alias_method_chain :save!, :validation
+ is equivalent to
+ alias_method :save_without_validation!, :save!
+ alias_method :save!, :save_with_validation!
+
+* Enhance Symbol#to_proc so it works with list objects, such as multi-dimensional arrays. Closes #5295 [nov@yo.rim.or.jp]. Example:
+
+ {1 => "one", 2 => "two", 3 => "three"}.sort_by(&:first).map(&:last)
+ # => ["one", "two", "three"]
+
+* Added Hash.create_from_xml(string) which will create a hash from a XML string and even typecast if possible [David Heinemeier Hansson]. Example:
+
+ Hash.create_from_xml <<-EOT
+ <note>
+ <title>This is a note</title>
+ <created-at type="date">2004-10-10</created-at>
+ </note>
+ EOT
+
+ ...would return:
+
+ { :note => { :title => "This is a note", :created_at => Date.new(2004, 10, 10) } }
+
+* Added Jim Weirich's excellent FlexMock class to vendor (Copyright 2003, 2004 by Jim Weirich (jim@weriichhouse.org)) -- it's not automatically required, though, so require 'flexmock' is still necessary *David Heinemeier Hansson*
+
+* Fixed that Module#alias_method_chain should work with both foo? foo! and foo at the same time #4954 *anna@wota.jp*
+
+* to_xml fixes, features, and speedup: introduce :dasherize option that converts updated_at to updated-at if true (the existing default); binary columns get encoding="base64" attribute; nil values get nil="true" attribute to distinguish empty values; add type information for float columns; allow arbitrarily deep :include; include SQL type information as the type attribute. #4989 *Blair Zajac <blair@orcaware.com>*
+
+* Add OrderedHash#values. *Sam Stephenson*
+
+* Added Array#to_s(:db) that'll produce a comma-separated list of ids [David Heinemeier Hansson]. Example:
+
+ Purchase.find(:all, :conditions => "product_id IN (#{shops.products.to_s(:db)})"
+
+* Normalize classify's argument to a String so that it plays nice with Symbols. *Marcel Molina Jr.*
+
+* Strip out leading schema name in classify. References #5139. *Michael Schoen*
+
+* Remove Enumerable#first_match since break(value) handles the use case well enough. *Nicholas Seckar*
+
+ Enumerable#first_match was like detect, but instead of returning the matching element, the yielded value returned. For example:
+
+ user_xml = adapters(:from => User, :to => Xml).first_match do |adapter|
+ adapter.adapt @user
+ end
+
+ But this is just as easily done with:
+
+ user_xml = adapters(:from => User, :to => Xml).each do
+ break adapter.adapt(@user)
+ end
+
+* Make Array#in_groups_of just return the grouped collection if a block isn't given. *Marcel Molina Jr.*
+
+* Don't destroy a HashWithIndifferentAccess if symbolize_keys! or stringify_keys! is called on it. Closes #5076. *Marcel Molina Jr., guy.naor@famundo.com*
+
+* Document Module::delegate. #5002 *pergesu@gmail.com*
+
+* Replace alias method chaining with Module#alias_method_chain. *Marcel Molina Jr.*
+
+* Strip out punctuation on predicates or bang methods being aliased with alias_method_chain since target?_without_feature is not a valid method name. Add tests for Module#alias_method_chain. *Marcel Molina Jr.*
+
+* Replace Ruby's deprecated append_features in favor of included. *Marcel Molina Jr.*
+
+* Allow default options in with_options to be overridden. Closes #4480. *murphy@cYcnus.de*
+
+* Added Module#alias_method_chain *Jamis Buck*
+
+* Updated to Builder 2.0 *David Heinemeier Hansson*
+
+* Add Array#split for dividing arrays into one or more subarrays by value or block. *Sam Stephenson*
+
+## 1.3.1 (April 6th, 2006) ##
+
+* Clean paths inside of exception messages and traces. *Nicholas Seckar*
+
+* Add Pathname.clean_within for cleaning all the paths inside of a string. *Nicholas Seckar*
+
+* provide an empty Dependencies::LoadingModule.load which prints deprecation warnings. Lets 1.0 applications function with .13-style environment.rb.
+
+
+## 1.3.0 (March 27th, 2006) ##
+
+* When possible, avoid incorrectly obtaining constants from parent modules. Fixes #4221. *Nicholas Seckar*
+
+* Add more tests for dependencies; refactor existing cases. *Nicholas Seckar*
+
+* Move Module#parent and Module#as_load_path into core_ext. Add Module#parent. *Nicholas Seckar*
+
+* Add CachingTools::HashCaching to simplify the creation of nested, autofilling hashes. *Nicholas Seckar*
+
+* Remove a hack intended to avoid unloading the same class twice, but which would not work anyways. *Nicholas Seckar*
+
+* Update Object.subclasses_of to locate nested classes. This affects Object.remove_subclasses_of in that nested classes will now be unloaded. *Nicholas Seckar*
+
+* Update Object.remove_subclasses_of to use Class.remove_class, reducing duplication. *Nicholas Seckar*
+
+* Added Fixnum#seconds for consistency, so you can say 5.minutes + 30.seconds instead of 5.minutes + 30 #4389 *François Beausoleil*
+
+* Added option to String#camelize to generate lower-cased camel case by passing in :lower, like "super_man".camelize(:lower) # => "superMan" *David Heinemeier Hansson*
+
+* Added Hash#diff to show the difference between two hashes *Chris McGrath*
+
+* Added Time#advance to do precise time time calculations for cases where a month being approximated to 30 days won't do #1860 *Rick Olson*
+
+* Enhance Inflector.underscore to convert '-' into '_' (as the inverse of Inflector.dasherize) *Jamis Buck*
+
+* Switched to_xml to use the xml schema format for datetimes. This allows the encoding of time zones and should improve operability. *Michael Koziarski*
+
+* Added a note to the documentation for the Date related Numeric extensions to indicate that they're
+ approximations and shouldn't be used for critical calculations. *Michael Koziarski*
+
+* Added Hash#to_xml and Array#to_xml that makes it much easier to produce XML from basic structures [David Heinemeier Hansson]. Examples:
+
+ { :name => "David", :street_name => "Paulina", :age => 26, :moved_on => Date.new(2005, 11, 15) }.to_xml
+
+ ...returns:
+
+ <person>
+ <street-name>Paulina</street-name>
+ <name>David</name>
+ <age type="integer">26</age>
+ <moved-on type="date">2005-11-15</moved-on>
+ </person>
+
+* Moved Jim Weirich's wonderful Builder from Action Pack to Active Support (it's simply too useful to be stuck in AP) *David Heinemeier Hansson*
+
+* Fixed that Array#to_sentence will return "" on an empty array instead of ", and" #3842, #4031 *rubyonrails@beautifulpixel.com*
+
+* Add Enumerable#group_by for grouping collections based on the result of some
+ block. Useful, for example, for grouping records by date.
+
+ ex.
+
+ latest_transcripts.group_by(&:day).each do |day, transcripts|
+ p "#{day} -> #{transcripts.map(&:class) * ', '}"
+ end
+ "2006-03-01 -> Transcript"
+ "2006-02-28 -> Transcript"
+ "2006-02-27 -> Transcript, Transcript"
+ "2006-02-26 -> Transcript, Transcript"
+
+ Add Array#in_groups_of, for iterating over an array in groups of a certain
+ size.
+
+ ex.
+
+ %w(1 2 3 4 5 6 7).in_groups_of(3) {|g| p g}
+ ["1", "2", "3"]
+ ["4", "5", "6"]
+ ["7", nil, nil]
+
+ *Marcel Molina Jr., Sam Stephenson*
+
+* Added Kernel#daemonize to turn the current process into a daemon that can be killed with a TERM signal *David Heinemeier Hansson*
+
+* Add 'around' methods to Logger, to make it easy to log before and after messages for a given block as requested in #3809. [Michael Koziarski] Example:
+
+ logger.around_info("Start rendering component (#{options.inspect}): ",
+ "\n\nEnd of component rendering") { yield }
+
+* Added Time#beginning_of_quarter #3607 *cohen.jeff@gmail.com*
+
+* Fix Object.subclasses_of to only return currently defined objects *Jonathan Viney <jonathan@bluewire.net.nz>*
+
+* Fix constantize to properly handle names beginning with '::'. *Nicholas Seckar*
+
+* Make String#last return the string instead of nil when it is shorter than the limit [Scott Barron].
+
+* Added delegation support to Module that allows multiple delegations at once (unlike Forwardable in the stdlib) [David Heinemeier Hansson]. Example:
+
+ class Account < ActiveRecord::Base
+ has_one :subscription
+ delegate :free?, :paying?, :to => :subscription
+ delegate :overdue?, :to => "subscription.last_payment"
+ end
+
+ account.free? # => account.subscription.free?
+ account.overdue? # => account.subscription.last_payment.overdue?
+
+* Fix Reloadable to handle the case where a class that has been 'removed' has not yet been garbage collected. *Nicholas Seckar*
+
+* Don't allow Reloadable to be included into Modules.
+
+* Remove LoadingModule. *Nicholas Seckar*
+
+* Add documentation for Reloadable::Subclasses. *Nicholas Seckar*
+
+* Add Reloadable::Subclasses which handles the common case where a base class should not be reloaded, but its subclasses should be. *Nicholas Seckar*
+
+* Further improvements to reloading code *Nicholas Seckar, Trevor Squires*
+
+ - All classes/modules which include Reloadable can define reloadable? for fine grained control of reloading
+ - Class.remove_class uses Module#parent to access the parent module
+ - Class.remove_class expanded to handle multiple classes in a single call
+ - LoadingModule.clear! has been removed as it is no longer required
+ - Module#remove_classes_including has been removed in favor of Reloadable.reloadable_classes
+
+* Added reusable reloading support through the inclusion of the Relodable module that all subclasses of ActiveRecord::Base, ActiveRecord::Observer, ActiveController::Base, and ActionMailer::Base automatically gets. This means that these classes will be reloaded by the dispatcher when Dependencies.mechanism = :load. You can make your own models reloadable easily:
+
+ class Setting
+ include Reloadable
+ end
+
+ Reloading a class is done by removing its constant which will cause it to be loaded again on the next reference. *David Heinemeier Hansson*
+
+* Added auto-loading support for classes in modules, so Conductor::Migration will look for conductor/migration.rb and Conductor::Database::Settings will look for conductor/database/settings.rb *Nicholas Seckar*
+
+* Add Object#instance_exec, like instance_eval but passes its arguments to the block. (Active Support will not override the Ruby 1.9 implementation of this method.) *Sam Stephenson*
+
+* Add Proc#bind(object) for changing a proc or block's self by returning a Method bound to the given object. Based on why the lucky stiff's "cloaker" method. *Sam Stephenson*
+
+* Fix merge and dup for hashes with indifferent access #3404 *Ken Miller*
+
+* Fix the requires in option_merger_test to unbreak AS tests. *Sam Stephenson*
+
+* Make HashWithIndifferentAccess#update behave like Hash#update by returning the hash. #3419, #3425 *asnem@student.ethz.ch, JanPrill@blauton.de, Marcel Molina Jr.*
+
+* Add ActiveSupport::JSON and Object#to_json for converting Ruby objects to JSON strings. *Sam Stephenson*
+
+* Add Object#with_options for DRYing up multiple calls to methods having shared options. [Sam Stephenson] Example:
+
+ ActionController::Routing::Routes.draw do |map|
+ # Account routes
+ map.with_options(:controller => 'account') do |account|
+ account.home '', :action => 'dashboard'
+ account.signup 'signup', :action => 'new'
+ account.logout 'logout', :action => 'logout'
+ end
+ end
+
+* Introduce Dependencies.warnings_on_first_load setting. If true, enables warnings on first load of a require_dependency. Otherwise, loads without warnings. Disabled (set to false) by default. *Jeremy Kemper*
+
+* Active Support is warnings-safe. #1792 *Eric Hodel*
+
+* Introduce enable_warnings counterpart to silence_warnings. Turn warnings on when loading a file for the first time if Dependencies.mechanism == :load. Common mistakes such as redefined methods will print warnings to stderr. *Jeremy Kemper*
+
+* Add Symbol#to_proc, which allows for, e.g. [:foo, :bar].map(&:to_s). *Marcel Molina Jr.*
+
+* Added the following methods [Marcel Molina Jr., Sam Stephenson]:
+ * Object#copy_instance_variables_from(object) to copy instance variables from one object to another
+ * Object#extended_by to get an instance's included/extended modules
+ * Object#extend_with_included_modules_from(object) to extend an instance with the modules from another instance
+
+## 1.2.5 (December 13th, 2005) ##
+
+* Become part of Rails 1.0
+
+* Rename Version constant to VERSION. #2802 *Marcel Molina Jr.*
+
+## 1.2.3 (November 7th, 2005) ##
+
+* Change Inflector#constantize to use eval instead of const_get. *Nicholas Seckar*
+
+* Fix const_missing handler to ignore the trailing '.rb' on files when comparing paths. *Nicholas Seckar*
+
+* Define kernel.rb methods in "class Object" instead of "module Kernel" to work around a Windows peculiarity *Sam Stephenson*
+
+* Fix broken tests caused by incomplete loading of active support. *Nicholas Seckar*
+
+* Fix status pluralization bug so status_codes doesn't get pluralized as statuses_code. #2758 *keithm@infused.org*
+
+* Added Kernel#silence_stderr to silence stderr for the duration of the given block *Sam Stephenson*
+
+* Changed Kernel#` to print a message to stderr (like Unix) instead of raising Errno::ENOENT on Win32 *Sam Stephenson*
+
+* Changed 0.blank? to false rather than true since it violates everyone's expectation of blankness. #2518, #2705 *rails@jeffcole.net*
+
+* When loading classes using const_missing, raise a NameError if and only if the file we tried to load was not present. *Nicholas Seckar*
+
+* Added petabytes and exebytes to numeric extensions #2397 *timct@mac.com*
+
+* Added Time#end_of_month to accompany Time#beginning_of_month #2514 *Jens-Christian Fischer*
+
+
+## 1.2.2 (October 26th, 2005) ##
+
+* Set Logger.silencer = false to disable Logger#silence. Useful for debugging fixtures.
+
+* Add title case method to String to do, e.g., 'action_web_service'.titlecase # => 'Action Web Service'. *Marcel Molina Jr.*
+
+
+## 1.2.1 (October 19th, 2005) ##
+
+* Classify generated routing code as framework code to avoid appearing in application traces. *Nicholas Seckar*
+
+* Show all framework frames in the framework trace. *Nicholas Seckar*
+
+
+## 1.2.0 (October 16th, 2005) ##
+
+* Update Exception extension to show the first few framework frames in an application trace. *Nicholas Seckar*
+
+* Added Exception extension to provide support for clean backtraces. *Nicholas Seckar*
+
+* Updated whiny nil to be more concise and useful. *Nicholas Seckar*
+
+* Added Enumerable#first_match *Nicholas Seckar*
+
+* Fixed that Time#change should also reset usec when also resetting minutes #2459 *ikeda@dream.big.or.jp*
+
+* Fix Logger compatibility for distributions that don't keep Ruby and its standard library in sync.
+
+* Replace '%e' from long and short time formats as Windows does not support it. #2344. *Tom Ward <tom@popdog.net>*
+
+* Added to_s(:db) to Range, so you can get "BETWEEN '2005-12-10' AND '2005-12-12'" from Date.new(2005, 12, 10)..Date.new(2005, 12, 12) (and likewise with Times)
+
+* Moved require_library_or_gem into Kernel. #1992 *Michael Schuerig <michael@schuerig.de>*
+
+* Add :rfc822 as an option for Time#to_s (to get rfc822-formatted times)
+
+* Chain the const_missing hook to any previously existing hook so rails can play nicely with rake
+
+* Clean logger is compatible with both 1.8.2 and 1.8.3 Logger. #2263 *Michael Schuerig <michael@schuerig.de>*
+
+* Added native, faster implementations of .blank? for the core types #2286 *skae*
+
+* Fixed clean logger to work with Ruby 1.8.3 Logger class #2245
+
+* Fixed memory leak with Active Record classes when Dependencies.mechanism = :load #1704 *Chris McGrath*
+
+* Fixed Inflector.underscore for use with acronyms, so HTML becomes html instead of htm_l #2173 *k@v2studio.com*
+
+* Fixed dependencies related infinite recursion bug when a controller file does not contain a controller class. Closes #1760. *rcolli2@tampabay.rr.com*
+
+* Fixed inflections for status, quiz, move #2056 *deirdre@deirdre.net*
+
+* Added Hash#reverse_merge, Hash#reverse_merge!, and Hash#reverse_update to ease the use of default options
+
+* Added Array#to_sentence that'll turn ['one', 'two', 'three'] into "one, two, and three" #2157 *Manfred Stienstra*
+
+* Added Kernel#silence_warnings to turn off warnings temporarily for the passed block
+
+* Added String#starts_with? and String#ends_with? #2118 *Thijs van der Vossen*
+
+* Added easy extendability to the inflector through Inflector.inflections (using the Inflector::Inflections singleton class). Examples:
+
+ Inflector.inflections do |inflect|
+ inflect.plural /^(ox)$/i, '\1\2en'
+ inflect.singular /^(ox)en/i, '\1'
+
+ inflect.irregular 'octopus', 'octopi'
+
+ inflect.uncountable "equipment"
+ end
+
+* Added String#at, String#from, String#to, String#first, String#last in ActiveSupport::CoreExtensions::String::Access to ease access to individual characters and substrings in a string serving basically as human names for range access.
+
+* Make Time#last_month work when invoked on the 31st of a month.
+
+* Add Time.days_in_month, and make Time#next_month work when invoked on the 31st of a month
+
+* Fixed that Time#midnight would have a non-zero usec on some platforms #1836
+
+* Fixed inflections of "index/indices" #1766 *damn_pepe@gmail.com*
+
+* Added stripping of _id to String#humanize, so "employee_id" becomes "Employee" #1574 *Justin French*
+
+* Factor Fixnum and Bignum extensions into Integer extensions *Nicholas Seckar*
+
+* Hooked #ordinalize into Fixnum and Bignum classes. *Nicholas Seckar, danp*
+
+* Added Fixnum#ordinalize to turn 1.ordinalize to "1st", 3.ordinalize to "3rd", and 10.ordinalize to "10th" and so on #1724 *paul@cnt.org*
+
+
+## 1.1.1 (11 July, 2005) ##
+
+* Added more efficient implementation of the development mode reset of classes #1638 *Chris McGrath*
+
+
+## 1.1.0 (6 July, 2005) ##
+
+* Fixed conflict with Glue gem #1606 *Rick Olson*
+
+* Added new rules to the Inflector to deal with more unusual plurals mouse/louse => mice/lice, information => information, ox => oxen, virus => viri, archive => archives #1571, #1583, #1490, #1599, #1608 *foamdino@gmail.com/others*
+
+* Fixed memory leak with Object#remove_subclasses_of, which inflicted a Rails application running in development mode with a ~20KB leak per request #1289 *Chris McGrath*
+
+* Made 1.year == 365.25.days to account for leap years. This allows you to do User.find(:all, :conditions => ['birthday > ?', 50.years.ago]) without losing a lot of days. #1488 *tuxie@dekadance.se*
+
+* Added an exception if calling id on nil to WhinyNil #584 *kevin-temp@writesoon.com*
+
+* Added Fix/Bignum#multiple_of? which returns true on 14.multiple_of?(7) and false on 16.multiple_of?(7) #1464 *Thomas Fuchs*
+
+* Added even? and odd? to work with Bignums in addition to Fixnums #1464 *Thomas Fuchs*
+
+* Fixed Time#at_beginning_of_week returned the next Monday instead of the previous one when called on a Sunday #1403 *jean.helou@gmail.com*
+
+* Increased the speed of indifferent hash access by using Hash#default. #1436 *Nicholas Seckar*
+
+* Added that " " is now also blank? (using strip if available)
+
+* Fixed Dependencies so all modules are able to load missing constants #1173 *Nicholas Seckar*
+
+* Fixed the Inflector to underscore strings containing numbers, so Area51Controller becomes area51_controller #1176 *Nicholas Seckar*
+
+* Fixed that HashWithIndifferentAccess stringified all keys including symbols, ints, objects, and arrays #1162 *Nicholas Seckar*
+
+* Fixed Time#last_year to go back in time, not forward #1278 *fabien@odilat.com*
+
+* Fixed the pluralization of analysis to analyses #1295 *seattle@rootimage.msu.edu*
+
+* Fixed that Time.local(2005,12).months_since(1) would raise "ArgumentError: argument out of range" #1311 *jhahn@niveon.com*
+
+* Added silencing to the default Logger class
+
+
+## 1.0.4 (19th April, 2005) ##
+
+* Fixed that in some circumstances controllers outside of modules may have hidden ones inside modules. For example, admin/content might have been hidden by /content. #1075 *Nicholas Seckar*
+
+* Fixed inflection of perspectives and similar words #1045 *Thijs van der Vossen*
+
+* Added Fixnum#even? and Fixnum#odd?
+
+* Fixed problem with classes being required twice. Object#const_missing now uses require_dependency to load files. It used to use require_or_load which would cause models to be loaded twice, which was not good for validations and other class methods #971 *Nicholas Seckar*
+
+
+## 1.0.3 (27th March, 2005) ##
+
+* Fixed Inflector.pluralize to handle capitalized words #932 *Jeremy Kemper*
+
+* Added Object#suppress which allows you to make a saner choice around with exceptions to swallow #980. Example:
+
+ suppress(ZeroDivisionError) { 1/0 }
+
+ ...instead of:
+
+ 1/0 rescue nil # BAD, EVIL, DIRTY.
+
+
+## 1.0.2 (22th March, 2005) ##
+
+* Added Kernel#returning -- a Ruby-ized realization of the K combinator, courtesy of Mikael Brockman.
+
+ def foo
+ returning values = [] do
+ values << 'bar'
+ values << 'baz'
+ end
+ end
+
+ foo # => ['bar', 'baz']
+
+
+## 1.0.1 (7th March, 2005) ##
+
+* Fixed Hash#indifferent_access to also deal with include? and fetch and nested hashes #726 *Nicholas Seckar*
+
+* Added Object#blank? -- see http://redhanded.hobix.com/inspect/objectBlank.html #783 *_why the lucky stiff*
+
+* Added inflection rules for "sh" words, like "wish" and "fish" #755 *phillip@pjbsoftware.com*
+
+* Fixed an exception when using Ajax based requests from Safari because Safari appends a \000 to the post body. Symbols can't have \000 in them so indifferent access would throw an exception in the constructor. Indifferent hashes now use strings internally instead. #746 *Tobias Lütke*
+
+* Added String#to_time and String#to_date for wrapping ParseDate
+
+
+## 1.0.0 (24th February, 2005) ##
+
+* Added TimeZone as the first of a number of value objects that among others Active Record can use rich value objects using composed_of #688 *Jamis Buck*
+
+* Added Date::Conversions for getting dates in different convenient string representations and other objects
+
+* Added Time::Conversions for getting times in different convenient string representations and other objects
+
+* Added Time::Calculations to ask for things like Time.now.tomorrow, Time.now.yesterday, Time.now.months_ago(4) #580 [DP|Flurin]. Examples:
+
+ "Later today" => now.in(3.hours),
+ "Tomorrow morning" => now.tomorrow.change(:hour => 9),
+ "Tomorrow afternoon" => now.tomorrow.change(:hour => 14),
+ "In a couple of days" => now.tomorrow.tomorrow.change(:hour => 9),
+ "Next monday" => now.next_week.change(:hour => 9),
+ "In a month" => now.next_month.change(:hour => 9),
+ "In 6 months" => now.months_since(6).change(:hour => 9),
+ "In a year" => now.in(1.year).change(:hour => 9)
+
+* Upgraded to breakpoint 92 which fixes:
+
+ * overload IRB.parse_opts(), fixes #443
+ => breakpoints in tests work even when running them via rake
+ * untaint handlers, might fix an issue discussed on the Rails ML
+ * added verbose mode to breakpoint_client
+ * less noise caused by breakpoint_client by default
+ * ignored TerminateLineInput exception in signal handler
+ => quiet exit on Ctrl-C
+
+* Fixed Inflector for words like "news" and "series" that are the same in plural and singular #603 [echion], #615 *marcenuc*
+
+* Added Hash#stringify_keys and Hash#stringify_keys!
+
+* Added IndifferentAccess as a way to wrap a hash by a symbol-based store that also can be accessed by string keys
+
+* Added Inflector.constantize to turn "Admin::User" into a reference for the constant Admin::User
+
+* Added that Inflector.camelize and Inflector.underscore can deal with modules like turning "Admin::User" into "admin/user" and back
+
+* Added Inflector.humanize to turn attribute names like employee_salary into "Employee salary". Used by automated error reporting in AR.
+
+* Added availability of class inheritable attributes to the masses #477 *Jeremy Kemper*
+
+ class Foo
+ class_inheritable_reader :read_me
+ class_inheritable_writer :write_me
+ class_inheritable_accessor :read_and_write_me
+ class_inheritable_array :read_and_concat_me
+ class_inheritable_hash :read_and_update_me
+ end
+
+ # Bar gets a clone of (not a reference to) Foo's attributes.
+ class Bar < Foo
+ end
+
+ Bar.read_and_write_me == Foo.read_and_write_me
+ Bar.read_and_write_me = 'bar'
+ Bar.read_and_write_me != Foo.read_and_write_me
+
+* Added Inflections as an extension on String, so Inflector.pluralize(Inflector.classify(name)) becomes name.classify.pluralize #476 *Jeremy Kemper*
+
+* Added Byte operations to Numeric, so 5.5.megabytes + 200.kilobytes #461 *Marcel Molina Jr.*
+
+* Fixed that Dependencies.reload can't load the same file twice #420 *Kent Sibilev*
+
+* Added Fixnum#ago/until, Fixnum#since/from_now #450 *Jeremy Kemper*
+
+* Added that Inflector now accepts Symbols and Classes by calling .to_s on the word supplied
+
+* Added time unit extensions to Fixnum that'll return the period in seconds, like 2.days + 4.hours.
diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE
index 5e8b7a9450..c2bcf1d3e7 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2011 David Heinemeier Hansson
+Copyright (c) 2005-2012 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index 1ab8e00608..ed688ecc59 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -19,7 +19,9 @@ Source code can be downloaded as part of the Rails project on GitHub
== License
-Active Support is released under the MIT license.
+Active Support is released under the MIT license:
+
+* http://www.opensource.org/licenses/MIT
== Support
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 822c9d98ae..822c9d98ae 100755..100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 2ee6bb788a..2c874e932e 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -7,15 +7,18 @@ Gem::Specification.new do |s|
s.summary = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.'
s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.'
- s.required_ruby_version = '>= 1.8.7'
+ s.required_ruby_version = '>= 1.9.3'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
+ s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
+ s.rdoc_options.concat ['--encoding', 'UTF-8']
+
s.add_dependency('i18n', '~> 0.6')
- s.add_dependency('multi_json', '~> 1.0')
+ s.add_dependency('multi_json', '~> 1.3')
+ s.add_dependency('tzinfo', '~> 0.3.31')
end
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 5fefa429df..5fefa429df 100644..100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index cc9ea5cffa..8f018dcbc6 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2011 David Heinemeier Hansson
+# Copyright (c) 2005-2012 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -22,26 +22,15 @@
#++
require 'securerandom'
-
-module ActiveSupport
- class << self
- attr_accessor :load_all_hooks
- def on_load_all(&hook) load_all_hooks << hook end
- def load_all!; load_all_hooks.each { |hook| hook.call } end
- end
- self.load_all_hooks = []
-
- on_load_all do
- [Dependencies, Deprecation, Gzip, MessageVerifier, Multibyte]
- end
-end
-
require "active_support/dependencies/autoload"
require "active_support/version"
+require "active_support/logger"
module ActiveSupport
extend ActiveSupport::Autoload
+ autoload :Concern
+ autoload :Dependencies
autoload :DescendantsTracker
autoload :FileUpdateChecker
autoload :LogSubscriber
@@ -50,19 +39,15 @@ module ActiveSupport
# TODO: Narrow this list down
eager_autoload do
autoload :BacktraceCleaner
- autoload :Base64
autoload :BasicObject
autoload :Benchmarkable
- autoload :BufferedLogger
autoload :Cache
autoload :Callbacks
- autoload :Concern
autoload :Configurable
autoload :Deprecation
autoload :Gzip
autoload :Inflector
autoload :JSON
- autoload :Memoizable
autoload :MessageEncryptor
autoload :MessageVerifier
autoload :Multibyte
@@ -71,6 +56,7 @@ module ActiveSupport
autoload :OrderedOptions
autoload :Rescuable
autoload :StringInquirer
+ autoload :TaggedLogging
autoload :XmlMini
end
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index 8f8deb9692..7c3a41288b 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -1,23 +1,21 @@
module ActiveSupport
- # Backtraces often include many lines that are not relevant for the context under review. This makes it hard to find the
+ # Backtraces often include many lines that are not relevant for the context under review. This makes it hard to find the
# signal amongst the backtrace noise, and adds debugging time. With a BacktraceCleaner, filters and silencers are used to
# remove the noisy lines, so that only the most relevant lines remain.
#
# Filters are used to modify lines of data, while silencers are used to remove lines entirely. The typical filter use case
- # is to remove lengthy path information from the start of each line, and view file paths relevant to the app directory
- # instead of the file system root. The typical silencer use case is to exclude the output of a noisy library from the
+ # is to remove lengthy path information from the start of each line, and view file paths relevant to the app directory
+ # instead of the file system root. The typical silencer use case is to exclude the output of a noisy library from the
# backtrace, so that you can focus on the rest.
#
- # ==== Example:
- #
# bc = BacktraceCleaner.new
# bc.add_filter { |line| line.gsub(Rails.root, '') }
# bc.add_silencer { |line| line =~ /mongrel|rubygems/ }
# bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems
#
- # To reconfigure an existing BacktraceCleaner (like the default one in Rails) and show as much data as possible, you can
- # always call <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the backtrace to a pristine state. If you
- # need to reconfigure an existing BacktraceCleaner so that it does not filter or modify the paths of any lines of the
+ # To reconfigure an existing BacktraceCleaner (like the default one in Rails) and show as much data as possible, you can
+ # always call <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the backtrace to a pristine state. If you
+ # need to reconfigure an existing BacktraceCleaner so that it does not filter or modify the paths of any lines of the
# backtrace, you can call BacktraceCleaner#remove_filters! These two methods will give you a completely untouched backtrace.
#
# Inspired by the Quiet Backtrace gem by Thoughtbot.
@@ -42,19 +40,15 @@ module ActiveSupport
# Adds a filter from the block provided. Each line in the backtrace will be mapped against this filter.
#
- # Example:
- #
# # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb"
# backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') }
def add_filter(&block)
@filters << block
end
- # Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from
+ # Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from
# the clean backtrace.
#
- # Example:
- #
# # Will reject all lines that include the word "mongrel", like "/gems/mongrel/server.rb" or "/app/my_mongrel_server/rb"
# backtrace_cleaner.add_silencer { |line| line =~ /mongrel/ }
def add_silencer(&block)
diff --git a/activesupport/lib/active_support/base64.rb b/activesupport/lib/active_support/base64.rb
deleted file mode 100644
index 35014cb3d5..0000000000
--- a/activesupport/lib/active_support/base64.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-begin
- require 'base64'
-rescue LoadError
-end
-
-module ActiveSupport
- if defined? ::Base64
- Base64 = ::Base64
- else
- # Base64 provides utility methods for encoding and de-coding binary data
- # using a base 64 representation. A base 64 representation of binary data
- # consists entirely of printable US-ASCII characters. The Base64 module
- # is included in Ruby 1.8, but has been removed in Ruby 1.9.
- module Base64
- # Encodes a string to its base 64 representation. Each 60 characters of
- # output is separated by a newline character.
- #
- # ActiveSupport::Base64.encode64("Original unencoded string")
- # # => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==\n"
- def self.encode64(data)
- [data].pack("m")
- end
-
- # Decodes a base 64 encoded string to its original representation.
- #
- # ActiveSupport::Base64.decode64("T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw==")
- # # => "Original unencoded string"
- def self.decode64(data)
- data.unpack("m").first
- end
- end
- end
-
- # Encodes the value as base64 without the newline breaks. This makes the base64 encoding readily usable as URL parameters
- # or memcache keys without further processing.
- #
- # ActiveSupport::Base64.encode64s("Original unencoded string")
- # # => "T3JpZ2luYWwgdW5lbmNvZGVkIHN0cmluZw=="
- def Base64.encode64s(value)
- encode64(value).gsub(/\n/, '')
- end
-end
diff --git a/activesupport/lib/active_support/basic_object.rb b/activesupport/lib/active_support/basic_object.rb
index 3b5277c205..6ccb0cd525 100644
--- a/activesupport/lib/active_support/basic_object.rb
+++ b/activesupport/lib/active_support/basic_object.rb
@@ -1,21 +1,13 @@
module ActiveSupport
- if defined? ::BasicObject
- # A class with no predefined methods that behaves similarly to Builder's
- # BlankSlate. Used for proxy classes.
- class BasicObject < ::BasicObject
- undef_method :==
- undef_method :equal?
+ # A class with no predefined methods that behaves similarly to Builder's
+ # BlankSlate. Used for proxy classes.
+ class BasicObject < ::BasicObject
+ undef_method :==
+ undef_method :equal?
- # Let ActiveSupport::BasicObject at least raise exceptions.
- def raise(*args)
- ::Object.send(:raise, *args)
- end
- end
- else
- class BasicObject #:nodoc:
- instance_methods.each do |m|
- undef_method(m) if m.to_s !~ /(?:^__|^nil\?$|^send$|^object_id$)/
- end
+ # Let ActiveSupport::BasicObject at least raise exceptions.
+ def raise(*args)
+ ::Object.send(:raise, *args)
end
end
end
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index cc94041a1d..f149a7f0ed 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -35,7 +35,7 @@ module ActiveSupport
options[:level] ||= :info
result = nil
- ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
+ ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield }
logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
result
else
diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb
index 26412cd7f4..0595446189 100644
--- a/activesupport/lib/active_support/buffered_logger.rb
+++ b/activesupport/lib/active_support/buffered_logger.rb
@@ -1,137 +1,7 @@
-require 'thread'
-require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/deprecation'
+require 'active_support/logger'
module ActiveSupport
- # Inspired by the buffered logger idea by Ezra
- class BufferedLogger
- module Severity
- DEBUG = 0
- INFO = 1
- WARN = 2
- ERROR = 3
- FATAL = 4
- UNKNOWN = 5
- end
- include Severity
-
- MAX_BUFFER_SIZE = 1000
-
- ##
- # :singleton-method:
- # Set to false to disable the silencer
- cattr_accessor :silencer
- self.silencer = true
-
- # Silences the logger for the duration of the block.
- def silence(temporary_level = ERROR)
- if silencer
- begin
- old_logger_level, self.level = level, temporary_level
- yield self
- ensure
- self.level = old_logger_level
- end
- else
- yield self
- end
- end
-
- attr_accessor :level
- attr_reader :auto_flushing
-
- def initialize(log, level = DEBUG)
- @level = level
- @buffer = Hash.new { |h,k| h[k] = [] }
- @auto_flushing = 1
- @guard = Mutex.new
-
- if log.respond_to?(:write)
- @log = log
- elsif File.exist?(log)
- @log = open_log(log, (File::WRONLY | File::APPEND))
- else
- FileUtils.mkdir_p(File.dirname(log))
- @log = open_log(log, (File::WRONLY | File::APPEND | File::CREAT))
- end
- end
-
- def open_log(log, mode)
- open(log, mode).tap do |open_log|
- open_log.set_encoding(Encoding::BINARY) if open_log.respond_to?(:set_encoding)
- open_log.sync = true
- end
- end
-
- def add(severity, message = nil, progname = nil, &block)
- return if @level > severity
- message = (message || (block && block.call) || progname).to_s
- # If a newline is necessary then create a new message ending with a newline.
- # Ensures that the original message is not mutated.
- message = "#{message}\n" unless message[-1] == ?\n
- buffer << message
- auto_flush
- message
- end
-
- # Dynamically add methods such as:
- # def info
- # def warn
- # def debug
- Severity.constants.each do |severity|
- class_eval <<-EOT, __FILE__, __LINE__ + 1
- def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
- add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
- end # end
-
- def #{severity.downcase}? # def debug?
- #{severity} >= @level # DEBUG >= @level
- end # end
- EOT
- end
-
- # Set the auto-flush period. Set to true to flush after every log message,
- # to an integer to flush every N messages, or to false, nil, or zero to
- # never auto-flush. If you turn auto-flushing off, be sure to regularly
- # flush the log yourself -- it will eat up memory until you do.
- def auto_flushing=(period)
- @auto_flushing =
- case period
- when true; 1
- when false, nil, 0; MAX_BUFFER_SIZE
- when Integer; period
- else raise ArgumentError, "Unrecognized auto_flushing period: #{period.inspect}"
- end
- end
-
- def flush
- @guard.synchronize do
- buffer.each do |content|
- @log.write(content)
- end
-
- # Important to do this even if buffer was empty or else @buffer will
- # accumulate empty arrays for each request where nothing was logged.
- clear_buffer
- end
- end
-
- def close
- flush
- @log.close if @log.respond_to?(:close)
- @log = nil
- end
-
- protected
- def auto_flush
- flush if buffer.size >= @auto_flushing
- end
-
- def buffer
- @buffer[Thread.current]
- end
-
- def clear_buffer
- @buffer.delete(Thread.current)
- end
- end
+ BufferedLogger = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(
+ 'BufferedLogger', '::ActiveSupport::Logger')
end
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 95d936b32f..55791bfa56 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -16,8 +16,7 @@ module ActiveSupport
autoload :FileStore, 'active_support/cache/file_store'
autoload :MemoryStore, 'active_support/cache/memory_store'
autoload :MemCacheStore, 'active_support/cache/mem_cache_store'
- autoload :SynchronizedMemoryStore, 'active_support/cache/synchronized_memory_store'
- autoload :CompressedMemCacheStore, 'active_support/cache/compressed_mem_cache_store'
+ autoload :NullStore, 'active_support/cache/null_store'
# These options mean something to all cache implementations. Individual cache
# implementations may support additional options.
@@ -27,75 +26,75 @@ module ActiveSupport
autoload :LocalCache, 'active_support/cache/strategy/local_cache'
end
- # Creates a new CacheStore object according to the given options.
- #
- # If no arguments are passed to this method, then a new
- # ActiveSupport::Cache::MemoryStore object will be returned.
- #
- # If you pass a Symbol as the first argument, then a corresponding cache
- # store class under the ActiveSupport::Cache namespace will be created.
- # For example:
- #
- # ActiveSupport::Cache.lookup_store(:memory_store)
- # # => returns a new ActiveSupport::Cache::MemoryStore object
- #
- # ActiveSupport::Cache.lookup_store(:mem_cache_store)
- # # => returns a new ActiveSupport::Cache::MemCacheStore object
- #
- # Any additional arguments will be passed to the corresponding cache store
- # class's constructor:
- #
- # ActiveSupport::Cache.lookup_store(:file_store, "/tmp/cache")
- # # => same as: ActiveSupport::Cache::FileStore.new("/tmp/cache")
- #
- # If the first argument is not a Symbol, then it will simply be returned:
- #
- # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
- # # => returns MyOwnCacheStore.new
- def self.lookup_store(*store_option)
- store, *parameters = *Array.wrap(store_option).flatten
-
- case store
- when Symbol
- store_class_name = store.to_s.camelize
- store_class =
- begin
- require "active_support/cache/#{store}"
- rescue LoadError => e
- raise "Could not find cache store adapter for #{store} (#{e})"
- else
- ActiveSupport::Cache.const_get(store_class_name)
- end
- store_class.new(*parameters)
- when nil
- ActiveSupport::Cache::MemoryStore.new
- else
- store
+ class << self
+ # Creates a new CacheStore object according to the given options.
+ #
+ # If no arguments are passed to this method, then a new
+ # ActiveSupport::Cache::MemoryStore object will be returned.
+ #
+ # If you pass a Symbol as the first argument, then a corresponding cache
+ # store class under the ActiveSupport::Cache namespace will be created.
+ # For example:
+ #
+ # ActiveSupport::Cache.lookup_store(:memory_store)
+ # # => returns a new ActiveSupport::Cache::MemoryStore object
+ #
+ # ActiveSupport::Cache.lookup_store(:mem_cache_store)
+ # # => returns a new ActiveSupport::Cache::MemCacheStore object
+ #
+ # Any additional arguments will be passed to the corresponding cache store
+ # class's constructor:
+ #
+ # ActiveSupport::Cache.lookup_store(:file_store, "/tmp/cache")
+ # # => same as: ActiveSupport::Cache::FileStore.new("/tmp/cache")
+ #
+ # If the first argument is not a Symbol, then it will simply be returned:
+ #
+ # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new)
+ # # => returns MyOwnCacheStore.new
+ def lookup_store(*store_option)
+ store, *parameters = *Array.wrap(store_option).flatten
+
+ case store
+ when Symbol
+ store_class_name = store.to_s.camelize
+ store_class =
+ begin
+ require "active_support/cache/#{store}"
+ rescue LoadError => e
+ raise "Could not find cache store adapter for #{store} (#{e})"
+ else
+ ActiveSupport::Cache.const_get(store_class_name)
+ end
+ store_class.new(*parameters)
+ when nil
+ ActiveSupport::Cache::MemoryStore.new
+ else
+ store
+ end
end
- end
- def self.expand_cache_key(key, namespace = nil)
- expanded_cache_key = namespace ? "#{namespace}/" : ""
+ def expand_cache_key(key, namespace = nil)
+ expanded_cache_key = namespace ? "#{namespace}/" : ""
+
+ if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
+ expanded_cache_key << "#{prefix}/"
+ end
- prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
- if prefix
- expanded_cache_key << "#{prefix}/"
+ expanded_cache_key << retrieve_cache_key(key)
+ expanded_cache_key
end
- expanded_cache_key <<
- if key.respond_to?(:cache_key)
- key.cache_key
- elsif key.is_a?(Array)
- if key.size > 1
- key.collect { |element| expand_cache_key(element) }.to_param
- else
- key.first.to_param
- end
- elsif key
- key.to_param
- end.to_s
+ private
- expanded_cache_key
+ def retrieve_cache_key(key)
+ case
+ when key.respond_to?(:cache_key) then key.cache_key
+ when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param
+ when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a)
+ else key.to_param
+ end.to_s
+ end
end
# An abstract cache store class. There are multiple cache store
@@ -141,7 +140,7 @@ module ActiveSupport
# large enough to warrant compression. To turn on compression either pass
# <tt>:compress => true</tt> in the initializer or as an option to +fetch+
# or +write+. To specify the threshold at which to compress values, set the
- # <tt>:compress_threshold</tt> option. The default threshold is 32K.
+ # <tt>:compress_threshold</tt> option. The default threshold is 16K.
class Store
cattr_accessor :logger, :instance_writer => true
@@ -151,7 +150,7 @@ module ActiveSupport
# Create a new cache. The options will be passed to any write method calls except
# for :namespace which can be used to set the global namespace for the cache.
- def initialize (options = nil)
+ def initialize(options = nil)
@options = options ? options.dup : {}
end
@@ -232,7 +231,7 @@ module ActiveSupport
# <tt>:race_condition_ttl</tt> does not play any role.
#
# # Set all values to expire after one minute.
- # cache = ActiveSupport::Cache::MemoryCache.new(:expires_in => 1.minute)
+ # cache = ActiveSupport::Cache::MemoryStore.new(:expires_in => 1.minute)
#
# cache.write("foo", "original value")
# val_1 = nil
@@ -281,7 +280,7 @@ module ActiveSupport
end
end
if entry && entry.expired?
- race_ttl = options[:race_condition_ttl].to_f
+ race_ttl = options[:race_condition_ttl].to_i
if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl
entry.expires_at = Time.now + race_ttl
write_entry(key, entry, :expires_in => race_ttl * 2)
@@ -384,11 +383,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:exist?, name) do |payload|
entry = read_entry(namespaced_key(name, options), options)
- if entry && !entry.expired?
- true
- else
- false
- end
+ entry && !entry.expired?
end
end
@@ -540,11 +535,11 @@ module ActiveSupport
# Create an entry with internal attributes set. This method is intended to be
# used by implementations that store cache entries in a native format instead
# of as serialized Ruby objects.
- def create (raw_value, created_at, options = {})
+ def create(raw_value, created_at, options = {})
entry = new(nil)
entry.instance_variable_set(:@value, raw_value)
entry.instance_variable_set(:@created_at, created_at.to_f)
- entry.instance_variable_set(:@compressed, !!options[:compressed])
+ entry.instance_variable_set(:@compressed, options[:compressed])
entry.instance_variable_set(:@expires_in, options[:expires_in])
entry
end
@@ -561,7 +556,7 @@ module ActiveSupport
@value = nil
else
@value = Marshal.dump(value)
- if should_compress?(value, options)
+ if should_compress?(@value, options)
@value = Zlib::Deflate.deflate(@value)
@compressed = true
end
@@ -575,6 +570,9 @@ module ActiveSupport
# Get the value stored in the cache.
def value
+ # If the original value was exactly false @value is still true because
+ # it is marshalled and eventually compressed. Both operations yield
+ # strings.
if @value
Marshal.load(compressed? ? Zlib::Inflate.inflate(@value) : @value)
end
@@ -615,13 +613,10 @@ module ActiveSupport
end
private
- def should_compress?(value, options)
- if options[:compress] && value
- unless value.is_a?(Numeric)
- compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
- serialized_value = value.is_a?(String) ? value : Marshal.dump(value)
- return true if serialized_value.size >= compress_threshold
- end
+ def should_compress?(serialized_value, options)
+ if options[:compress]
+ compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
+ return true if serialized_value.size >= compress_threshold
end
false
end
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index b431041b76..89bdb741d0 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -1,19 +1,20 @@
require 'active_support/core_ext/file/atomic'
require 'active_support/core_ext/string/conversions'
require 'active_support/core_ext/object/inclusion'
-require 'rack/utils'
+require 'uri/common'
module ActiveSupport
module Cache
# A cache store implementation which stores everything on the filesystem.
#
# FileStore implements the Strategy::LocalCache strategy which implements
- # an in memory cache inside of a block.
+ # an in-memory cache inside of a block.
class FileStore < Store
attr_reader :cache_path
DIR_FORMATTER = "%03X"
- FILENAME_MAX_SIZE = 230 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
+ FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
+ EXCLUDED_DIRS = ['.', '..'].freeze
def initialize(cache_path, options = nil)
super(options)
@@ -22,7 +23,7 @@ module ActiveSupport
end
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject{|f| f.in?(['.', '..'])}
+ root_dirs = Dir.entries(cache_path).reject{|f| f.in?(EXCLUDED_DIRS + [".gitkeep"])}
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
end
@@ -125,7 +126,7 @@ module ActiveSupport
# Translate a key into a file path.
def key_file_path(key)
- fname = Rack::Utils.escape(key)
+ fname = URI.encode_www_form_component(key)
hash = Zlib.adler32(fname)
hash, dir_1 = hash.divmod(0x1000)
dir_2 = hash.modulo(0x1000)
@@ -142,14 +143,14 @@ module ActiveSupport
# Translate a file path into a key.
def file_path_key(path)
- fname = path[cache_path.size, path.size].split(File::SEPARATOR, 4).last
- Rack::Utils.unescape(fname)
+ fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
+ URI.decode_www_form_component(fname, Encoding::UTF_8)
end
# Delete empty directories in the cache.
def delete_empty_directories(dir)
return if dir == cache_path
- if Dir.entries(dir).reject{|f| f.in?(['.', '..'])}.empty?
+ if Dir.entries(dir).reject{|f| f.in?(EXCLUDED_DIRS)}.empty?
File.delete(dir) rescue nil
delete_empty_directories(File.dirname(dir))
end
@@ -163,7 +164,7 @@ module ActiveSupport
def search_dir(dir, &callback)
return if !File.exist?(dir)
Dir.foreach(dir) do |d|
- next if d == "." || d == ".."
+ next if d.in?(EXCLUDED_DIRS)
name = File.join(dir, d)
if File.directory?(name)
search_dir(name, &callback)
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index e07294178b..2e1ccb72d8 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -6,12 +6,11 @@ rescue LoadError => e
end
require 'digest/md5'
-require 'active_support/core_ext/string/encoding'
module ActiveSupport
module Cache
# A cache store implementation which stores data in Memcached:
- # http://www.danga.com/memcached/
+ # http://memcached.org/
#
# This is currently the most popular cache store for production websites.
#
@@ -21,7 +20,7 @@ module ActiveSupport
# server goes down, then MemCacheStore will ignore it until it comes back up.
#
# MemCacheStore implements the Strategy::LocalCache strategy which implements
- # an in memory cache inside of a block.
+ # an in-memory cache inside of a block.
class MemCacheStore < Store
module Response # :nodoc:
STORED = "STORED\r\n"
@@ -165,7 +164,7 @@ module ActiveSupport
# characters properly.
def escape_key(key)
key = key.to_s.dup
- key = key.force_encoding("BINARY") if key.encoding_aware?
+ key = key.force_encoding("BINARY")
key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
key
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index b15bb42c88..7fd5e3b53d 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -137,6 +137,7 @@ module ActiveSupport
def write_entry(key, entry, options) # :nodoc:
synchronize do
old_entry = @data[key]
+ return false if @data.key?(key) && options[:unless_exist]
@cache_size -= old_entry.size if old_entry
@cache_size += entry.size
@key_access[key] = Time.now.to_f
diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb
new file mode 100644
index 0000000000..4427eaafcd
--- /dev/null
+++ b/activesupport/lib/active_support/cache/null_store.rb
@@ -0,0 +1,44 @@
+module ActiveSupport
+ module Cache
+ # A cache store implementation which doesn't actually store anything. Useful in
+ # development and test environments where you don't want caching turned on but
+ # need to go through the caching interface.
+ #
+ # This cache does implement the local cache strategy, so values will actually
+ # be cached inside blocks that utilize this strategy. See
+ # ActiveSupport::Cache::Strategy::LocalCache for more details.
+ class NullStore < Store
+ def initialize(options = nil)
+ super(options)
+ extend Strategy::LocalCache
+ end
+
+ def clear(options = nil)
+ end
+
+ def cleanup(options = nil)
+ end
+
+ def increment(name, amount = 1, options = nil)
+ end
+
+ def decrement(name, amount = 1, options = nil)
+ end
+
+ def delete_matched(matcher, options = nil)
+ end
+
+ protected
+ def read_entry(key, options) # :nodoc:
+ end
+
+ def write_entry(key, entry, options) # :nodoc:
+ true
+ end
+
+ def delete_entry(key, options) # :nodoc:
+ false
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index 0649a058aa..db5f228a70 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -4,9 +4,9 @@ require 'active_support/core_ext/string/inflections'
module ActiveSupport
module Cache
module Strategy
- # Caches that implement LocalCache will be backed by an in memory cache for the
+ # Caches that implement LocalCache will be backed by an in-memory cache for the
# duration of a block. Repeated calls to the cache for the same key will hit the
- # in memory cache for faster access.
+ # in-memory cache for faster access.
module LocalCache
# Simple memory backed cache. This cache is not thread safe and is intended only
# for serving as a temporary memory cache for a single thread.
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 656cba625c..a9253c186d 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,6 +1,5 @@
require 'active_support/concern'
require 'active_support/descendants_tracker'
-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'
@@ -24,8 +23,6 @@ module ActiveSupport
# methods, procs or lambdas, or callback objects that respond to certain predetermined
# methods. See +ClassMethods.set_callback+ for details.
#
- # ==== Example
- #
# class Record
# include ActiveSupport::Callbacks
# define_callbacks :save
@@ -55,7 +52,6 @@ module ActiveSupport
# saving...
# - save
# saved
- #
module Callbacks
extend Concern
@@ -67,8 +63,6 @@ module ActiveSupport
#
# Calls the before and around callbacks in the order they were set, yields
# the block (if given one), and then runs the after callbacks in reverse order.
- # Optionally accepts a key, which will be used to compile an optimized callback
- # method for each key. See +ClassMethods.define_callbacks+ for more information.
#
# If the callback chain was halted, returns +false+. Otherwise returns the result
# of the block, or +true+ if no block is given.
@@ -76,49 +70,53 @@ module ActiveSupport
# run_callbacks :save do
# save
# end
- #
- def run_callbacks(kind, *args, &block)
- send("_run_#{kind}_callbacks", *args, &block)
+ def run_callbacks(kind, &block)
+ runner_name = self.class.__define_callbacks(kind, self)
+ send(runner_name, &block)
+ end
+
+ private
+
+ # A hook invoked everytime a before callback is halted.
+ # This can be overriden in AS::Callback implementors in order
+ # to provide better debugging/logging.
+ def halted_callback_hook(filter)
end
class Callback #:nodoc:#
@@_callback_sequence = 0
- attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter
+ attr_accessor :chain, :filter, :kind, :options, :klass, :raw_filter
def initialize(chain, filter, kind, options, klass)
@chain, @kind, @klass = chain, kind, klass
+ deprecate_per_key_option(options)
normalize_options!(options)
- @per_key = options.delete(:per_key)
@raw_filter, @options = filter, options
@filter = _compile_filter(filter)
- @compiled_options = _compile_options(options)
- @callback_id = next_id
+ recompile_options!
+ end
- _compile_per_key_options
+ def deprecate_per_key_option(options)
+ if options[:per_key]
+ raise NotImplementedError, ":per_key option is no longer supported. Use generic :if and :unless options instead."
+ end
end
def clone(chain, klass)
obj = super()
obj.chain = chain
obj.klass = klass
- obj.per_key = @per_key.dup
obj.options = @options.dup
- obj.per_key[:if] = @per_key[:if].dup
- obj.per_key[:unless] = @per_key[:unless].dup
obj.options[:if] = @options[:if].dup
obj.options[:unless] = @options[:unless].dup
obj
end
def normalize_options!(options)
- options[:if] = Array.wrap(options[:if])
- options[:unless] = Array.wrap(options[:unless])
-
- options[:per_key] ||= {}
- options[:per_key][:if] = Array.wrap(options[:per_key][:if])
- options[:per_key][:unless] = Array.wrap(options[:per_key][:unless])
+ options[:if] = Array(options[:if])
+ options[:unless] = Array(options[:unless])
end
def name
@@ -134,100 +132,46 @@ module ActiveSupport
end
def _update_filter(filter_options, new_options)
- filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless)
- filter_options[:unless].push(new_options[:if]) if new_options.key?(:if)
+ filter_options[:if].concat(Array(new_options[:unless])) if new_options.key?(:unless)
+ filter_options[:unless].concat(Array(new_options[:if])) if new_options.key?(:if)
end
- def recompile!(_options, _per_key)
+ def recompile!(_options)
+ deprecate_per_key_option(_options)
_update_filter(self.options, _options)
- _update_filter(self.per_key, _per_key)
- @callback_id = next_id
- @filter = _compile_filter(@raw_filter)
- @compiled_options = _compile_options(@options)
- _compile_per_key_options
+ recompile_options!
end
- def _compile_per_key_options
- key_options = _compile_options(@per_key)
-
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def _one_time_conditions_valid_#{@callback_id}?
- true #{key_options[0]}
- end
- RUBY_EVAL
- end
-
- # This will supply contents for before and around filters, and no
- # contents for after filters (for the forward pass).
- def start(key=nil, object=nil)
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
- # options[0] is the compiled form of supplied conditions
- # options[1] is the "end" for the conditional
- #
+ # Wraps code with filter
+ def apply(code)
case @kind
when :before
- # if condition # before_save :filter_name, :if => :condition
- # filter_name
- # end
- filter = <<-RUBY_EVAL
- unless halted
- # This double assignment is to prevent warnings in 1.9.3. I would
- # remove the `result` variable, but apparently some other
- # generated code is depending on this variable being set sometimes
- # and sometimes not.
+ <<-RUBY_EVAL
+ if !halted && #{@compiled_options}
+ # This double assignment is to prevent warnings in 1.9.3 as
+ # the `result` variable is not always used except if the
+ # terminator code refers to it.
result = result = #{@filter}
halted = (#{chain.config[:terminator]})
- end
- RUBY_EVAL
-
- [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n")
- when :around
- # Compile around filters with conditions into proxy methods
- # that contain the conditions.
- #
- # For `around_save :filter_name, :if => :condition':
- #
- # def _conditional_callback_save_17
- # if condition
- # filter_name do
- # yield self
- # end
- # else
- # yield self
- # end
- # end
- #
- name = "_conditional_callback_#{@kind}_#{next_id}"
- @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}(halted)
- #{@compiled_options[0] || "if true"} && !halted
- #{@filter} do
- yield self
- end
- else
- yield self
+ if halted
+ halted_callback_hook(#{@raw_filter.inspect.inspect})
end
end
+ #{code}
RUBY_EVAL
- "#{name}(halted) do"
- end
- end
-
- # This will supply contents for around and after filters, but not
- # before filters (for the backward pass).
- def end(key=nil, object=nil)
- return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?")
-
- case @kind
when :after
- # if condition # after_save :filter_name, :if => :condition
- # filter_name
- # end
- [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n")
+ <<-RUBY_EVAL
+ #{code}
+ if #{!chain.config[:skip_after_callbacks_if_terminated] || "!halted"} && #{@compiled_options}
+ #{@filter}
+ end
+ RUBY_EVAL
when :around
+ name = define_conditional_callback
<<-RUBY_EVAL
+ #{name}(halted) do
+ #{code}
value
end
RUBY_EVAL
@@ -236,23 +180,51 @@ module ActiveSupport
private
+ # Compile around filters with conditions into proxy methods
+ # that contain the conditions.
+ #
+ # For `set_callback :save, :around, :filter_name, :if => :condition':
+ #
+ # def _conditional_callback_save_17
+ # if condition
+ # filter_name do
+ # yield self
+ # end
+ # else
+ # yield self
+ # end
+ # end
+ def define_conditional_callback
+ name = "_conditional_callback_#{@kind}_#{next_id}"
+ @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def #{name}(halted)
+ if #{@compiled_options} && !halted
+ #{@filter} do
+ yield self
+ end
+ else
+ yield self
+ end
+ end
+ RUBY_EVAL
+ name
+ end
+
# Options support the same options as filters themselves (and support
# symbols, string, procs, and objects), so compile a conditional
# expression based on the options
- def _compile_options(options)
- return [] if options[:if].empty? && options[:unless].empty?
-
- conditions = []
+ def recompile_options!
+ conditions = ["true"]
unless options[:if].empty?
- conditions << Array.wrap(_compile_filter(options[:if]))
+ conditions << Array(_compile_filter(options[:if]))
end
unless options[:unless].empty?
- conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"}
+ conditions << Array(_compile_filter(options[:unless])).map {|f| "!#{f}"}
end
- ["if #{conditions.flatten.join(" && ")}", "end"]
+ @compiled_options = conditions.flatten.join(" && ")
end
# Filters support:
@@ -275,7 +247,6 @@ module ActiveSupport
# Objects::
# a method is created that calls the before_foo method
# on the object.
- #
def _compile_filter(filter)
method_name = "_callback_#{@kind}_#{next_id}"
case filter
@@ -294,7 +265,7 @@ module ActiveSupport
@klass.send(:define_method, "#{method_name}_object") { filter }
_normalize_legacy_filter(kind, filter)
- scopes = Array.wrap(chain.config[:scope])
+ scopes = Array(chain.config[:scope])
method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_")
@klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
@@ -330,83 +301,42 @@ module ActiveSupport
@name = name
@config = {
:terminator => "false",
- :rescuable => false,
:scope => [ :kind ]
}.merge(config)
end
- def compile(key=nil, object=nil)
+ def compile
method = []
method << "value = nil"
method << "halted = false"
- each do |callback|
- method << callback.start(key, object)
- end
-
- if config[:rescuable]
- method << "rescued_error = nil"
- method << "begin"
- end
-
- method << "value = yield if block_given? && !halted"
-
- if config[:rescuable]
- method << "rescue Exception => e"
- method << "rescued_error = e"
- method << "end"
- end
-
+ callbacks = "value = !halted && (!block_given? || yield)"
reverse_each do |callback|
- method << callback.end(key, object)
+ callbacks = callback.apply(callbacks)
end
+ method << callbacks
- method << "raise rescued_error if rescued_error" if config[:rescuable]
- method << "halted ? false : (block_given? ? value : true)"
- method.compact.join("\n")
+ method << "value"
+ method.join("\n")
end
+
end
module ClassMethods
- # Generate the internal runner method called by +run_callbacks+.
- def __define_runner(symbol) #:nodoc:
- body = send("_#{symbol}_callbacks").compile
-
- silence_warnings do
- undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
- class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def _run_#{symbol}_callbacks(key = nil, &blk)
- if key
- name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks"
-
- unless respond_to?(name)
- self.class.__create_keyed_callback(name, :#{symbol}, self, &blk)
- end
-
- send(name, &blk)
- else
- #{body}
- end
- end
- private :_run_#{symbol}_callbacks
- RUBY_EVAL
- end
- end
- # This is called the first time a callback is called with a particular
- # key. It creates a new callback method for the key, calculating
- # which callbacks can be omitted because of per_key conditions.
- #
- def __create_keyed_callback(name, kind, object, &blk) #:nodoc:
- @_keyed_callbacks ||= {}
- @_keyed_callbacks[name] ||= begin
- str = send("_#{kind}_callbacks").compile(name, object)
+ # This method defines callback chain method for the given kind
+ # if it was not yet defined.
+ # This generated method plays caching role.
+ def __define_callbacks(kind, object) #:nodoc:
+ chain = object.send("_#{kind}_callbacks")
+ name = "_run_callbacks_#{chain.object_id.abs}"
+ unless object.respond_to?(name, true)
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
- def #{name}() #{str} end
+ def #{name}() #{chain.compile} end
protected :#{name}
RUBY_EVAL
- true
end
+ name
end
# This is used internally to append, prepend and skip callbacks to the
@@ -420,7 +350,6 @@ module ActiveSupport
([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
chain = target.send("_#{name}_callbacks")
yield target, chain.dup, type, filters, options
- target.__define_runner(name)
end
end
@@ -459,30 +388,6 @@ module ActiveSupport
# will be called only when it returns a false value.
# * <tt>:prepend</tt> - If true, the callback will be prepended to the existing
# chain rather than appended.
- # * <tt>:per_key</tt> - A hash with <tt>:if</tt> and <tt>:unless</tt> options;
- # see "Per-key conditions" below.
- #
- # ===== Per-key conditions
- #
- # When creating or skipping callbacks, you can specify conditions that
- # are always the same for a given key. For instance, in Action Pack,
- # we convert :only and :except conditions into per-key conditions.
- #
- # before_filter :authenticate, :except => "index"
- #
- # becomes
- #
- # set_callback :process_action, :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
- #
- # Per-key conditions are evaluated only once per use of a given key.
- # In the case of the above example, you would do:
- #
- # run_callbacks(:process_action, action_name) { ... dispatch stuff ... }
- #
- # In that case, each action_name would get its own compiled callback
- # method that took into consideration the per_key conditions. This
- # is a speed improvement for ActionPack.
- #
def set_callback(name, *filter_list, &block)
mapped = nil
@@ -507,7 +412,6 @@ module ActiveSupport
# class Writer < Person
# skip_callback :validate, :before, :check_membership, :if => lambda { self.age > 18 }
# end
- #
def skip_callback(name, *filter_list, &block)
__update_callbacks(name, filter_list, block) do |target, chain, type, filters, options|
filters.each do |filter|
@@ -516,7 +420,7 @@ module ActiveSupport
if filter && options.any?
new_filter = filter.clone(chain, self)
chain.insert(chain.index(filter), new_filter)
- new_filter.recompile!(options, options[:per_key] || {})
+ new_filter.recompile!(options)
end
chain.delete(filter)
@@ -526,7 +430,6 @@ module ActiveSupport
end
# Remove all set callbacks for the given event.
- #
def reset_callbacks(symbol)
callbacks = send("_#{symbol}_callbacks")
@@ -534,12 +437,9 @@ module ActiveSupport
chain = target.send("_#{symbol}_callbacks").dup
callbacks.each { |c| chain.delete(c) }
target.send("_#{symbol}_callbacks=", chain)
- target.__define_runner(symbol)
end
self.send("_#{symbol}_callbacks=", callbacks.dup.clear)
-
- __define_runner(symbol)
end
# Define sets of events in the object lifecycle that support callbacks.
@@ -560,10 +460,10 @@ module ActiveSupport
# other callbacks are not executed. Defaults to "false", meaning no value
# halts the chain.
#
- # * <tt>:rescuable</tt> - By default, after filters are not executed if
- # the given block or a before filter raises an error. By setting this option
- # to <tt>true</tt> exception raised by given block is stored and after
- # executing all the after callbacks the stored exception is raised.
+ # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after callbacks should be terminated
+ # by the <tt>:terminator</tt> option. By default after callbacks executed no matter
+ # if callback chain was terminated or not.
+ # Option makes sence only when <tt>:terminator</tt> option is specified.
#
# * <tt>:scope</tt> - Indicates which methods should be executed when an object
# is used as a callback.
@@ -607,13 +507,11 @@ module ActiveSupport
# define_callbacks :save, :scope => [:name]
#
# would call <tt>Audit#save</tt>.
- #
def define_callbacks(*callbacks)
config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
callbacks.each do |callback|
class_attribute "_#{callback}_callbacks"
send("_#{callback}_callbacks=", CallbackChain.new(callback, config))
- __define_runner(callback)
end
end
end
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index 81fb859334..c94a8d99f4 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -4,17 +4,12 @@ module ActiveSupport
# module M
# def self.included(base)
# base.extend ClassMethods
- # base.send(:include, InstanceMethods)
# scope :disabled, where(:disabled => true)
# end
#
# module ClassMethods
# ...
# end
- #
- # module InstanceMethods
- # ...
- # end
# end
#
# By using <tt>ActiveSupport::Concern</tt> the above module could instead be written as:
@@ -31,10 +26,6 @@ module ActiveSupport
# module ClassMethods
# ...
# end
- #
- # module InstanceMethods
- # ...
- # end
# end
#
# Moreover, it gracefully handles module dependencies. Given a +Foo+ module and a +Bar+
@@ -118,7 +109,6 @@ module ActiveSupport
@_dependencies.each { |dep| base.send(:include, dep) }
super
base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
- base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods")
base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
end
end
diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb
index a2d2719de7..a8aa53a80f 100644
--- a/activesupport/lib/active_support/configurable.rb
+++ b/activesupport/lib/active_support/configurable.rb
@@ -1,7 +1,5 @@
require 'active_support/concern'
require 'active_support/ordered_options'
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/array/extract_options'
module ActiveSupport
diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb
index 46a8609dd7..b48bdf08e8 100644
--- a/activesupport/lib/active_support/core_ext.rb
+++ b/activesupport/lib/active_support/core_ext.rb
@@ -1,3 +1,4 @@
Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path|
+ next if File.basename(path, '.rb') == 'logger'
require "active_support/core_ext/#{File.basename(path, '.rb')}"
end
diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb
index 268c9bed4c..79ba79192a 100644
--- a/activesupport/lib/active_support/core_ext/array.rb
+++ b/activesupport/lib/active_support/core_ext/array.rb
@@ -4,5 +4,4 @@ require 'active_support/core_ext/array/uniq_by'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/grouping'
-require 'active_support/core_ext/array/random_access'
require 'active_support/core_ext/array/prepend_and_append'
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 6162f7af27..44d90ef732 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -16,7 +16,7 @@ class Array
# %w( a b c d ).to(10) # => %w( a b c d )
# %w().to(0) # => %w()
def to(position)
- self.first position + 1
+ first position + 1
end
# Equal to <tt>self[1]</tt>.
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 3b22e8b4f9..24aa28b895 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -9,28 +9,32 @@ class Array
# * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
# * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
def to_sentence(options = {})
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
+
+ default_connectors = {
+ :words_connector => ', ',
+ :two_words_connector => ' and ',
+ :last_word_connector => ', and '
+ }
if defined?(I18n)
- default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
- default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale])
- default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
- else
- default_words_connector = ", "
- default_two_words_connector = " and "
- default_last_word_connector = ", and "
+ namespace = 'support.array.'
+ default_connectors.each_key do |name|
+ i18n_key = (namespace + name.to_s).to_sym
+ default_connectors[name] = I18n.translate i18n_key, :locale => options[:locale]
+ end
end
- options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
- options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector
+ options.reverse_merge! default_connectors
case length
- when 0
- ""
- when 1
- self[0].to_s.dup
- when 2
- "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
- else
- "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
+ when 0
+ ''
+ when 1
+ self[0].to_s.dup
+ when 2
+ "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
+ else
+ "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
end
end
@@ -39,20 +43,20 @@ class Array
#
# Blog.all.to_formatted_s # => "First PostSecond PostThird Post"
#
- # Adding in the <tt>:db</tt> argument as the format yields a prettier
- # output:
+ # Adding in the <tt>:db</tt> argument as the format yields a comma separated
+ # id list:
#
- # Blog.all.to_formatted_s(:db) # => "First Post,Second Post,Third Post"
+ # Blog.all.to_formatted_s(:db) # => "1,2,3"
def to_formatted_s(format = :default)
case format
- when :db
- if respond_to?(:empty?) && self.empty?
- "null"
- else
- collect { |element| element.id }.join(",")
- end
+ when :db
+ if empty?
+ 'null'
else
- to_default_s
+ collect { |element| element.id }.join(',')
+ end
+ else
+ to_default_s
end
end
alias_method :to_default_s, :to_s
@@ -86,20 +90,20 @@ class Array
# </project>
# </projects>
#
- # Otherwise the root element is "records":
+ # Otherwise the root element is "objects":
#
# [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml
#
# <?xml version="1.0" encoding="UTF-8"?>
- # <records type="array">
- # <record>
+ # <objects type="array">
+ # <object>
# <bar type="integer">2</bar>
# <foo type="integer">1</foo>
- # </record>
- # <record>
+ # </object>
+ # <object>
# <baz type="integer">3</baz>
- # </record>
- # </records>
+ # </object>
+ # </objects>
#
# If the collection is empty the root element is "nil-classes" by default:
#
@@ -139,26 +143,28 @@ class Array
options = options.dup
options[:indent] ||= 2
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
- options[:root] ||= if first.class.to_s != "Hash" && all? { |e| e.is_a?(first.class) }
- underscored = ActiveSupport::Inflector.underscore(first.class.name)
- ActiveSupport::Inflector.pluralize(underscored).tr('/', '_')
- else
- "objects"
- end
+ options[:root] ||= \
+ if first.class != Hash && all? { |e| e.is_a?(first.class) }
+ underscored = ActiveSupport::Inflector.underscore(first.class.name)
+ ActiveSupport::Inflector.pluralize(underscored).tr('/', '_')
+ else
+ 'objects'
+ end
builder = options[:builder]
builder.instruct! unless options.delete(:skip_instruct)
root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
children = options.delete(:children) || root.singularize
+ attributes = options[:skip_types] ? {} : {:type => 'array'}
- attributes = options[:skip_types] ? {} : {:type => "array"}
- return builder.tag!(root, attributes) if empty?
-
- builder.__send__(:method_missing, root, attributes) do
- each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
- yield builder if block_given?
+ if empty?
+ builder.tag!(root, attributes)
+ else
+ builder.__send__(:method_missing, root, attributes) do
+ each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) }
+ yield builder if block_given?
+ end
end
end
-
end
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 4cd9bfadac..ac1ae53db0 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -1,5 +1,3 @@
-require 'enumerator'
-
class Array
# Splits or iterates over the array in groups of size +number+,
# padding any remaining slots with +fill_with+ unless it is +false+.
@@ -84,11 +82,9 @@ class Array
#
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
- def split(value = nil)
- using_block = block_given?
-
+ def split(value = nil, &block)
inject([[]]) do |results, element|
- if (using_block && yield(element)) || (value == element)
+ if block && block.call(element) || value == element
results << []
else
results.last << element
diff --git a/activesupport/lib/active_support/core_ext/array/random_access.rb b/activesupport/lib/active_support/core_ext/array/random_access.rb
deleted file mode 100644
index bb1807a68a..0000000000
--- a/activesupport/lib/active_support/core_ext/array/random_access.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-class Array
- # Backport of Array#sample based on Marc-Andre Lafortune's https://github.com/marcandre/backports/
- # Returns a random element or +n+ random elements from the array.
- # If the array is empty and +n+ is nil, returns <tt>nil</tt>.
- # If +n+ is passed and its value is less than 0, it raises an +ArgumentError+ exception.
- # If the value of +n+ is equal or greater than 0 it returns <tt>[]</tt>.
- #
- # [1,2,3,4,5,6].sample # => 4
- # [1,2,3,4,5,6].sample(3) # => [2, 4, 5]
- # [1,2,3,4,5,6].sample(-3) # => ArgumentError: negative array size
- # [].sample # => nil
- # [].sample(3) # => []
- def sample(n=nil)
- return self[Kernel.rand(size)] if n.nil?
- n = n.to_int
- rescue Exception => e
- raise TypeError, "Coercion error: #{n.inspect}.to_int => Integer failed:\n(#{e.message})"
- else
- raise TypeError, "Coercion error: obj.to_int did NOT return an Integer (was #{n.class})" unless n.kind_of? Integer
- raise ArgumentError, "negative array size" if n < 0
- n = size if n > size
- result = Array.new(self)
- n.times do |i|
- r = i + Kernel.rand(size - i)
- result[i], result[r] = result[r], result[i]
- end
- result[n..size] = []
- result
- end unless method_defined? :sample
-end
diff --git a/activesupport/lib/active_support/core_ext/array/uniq_by.rb b/activesupport/lib/active_support/core_ext/array/uniq_by.rb
index 9c5f97b0e9..3bedfa9a61 100644
--- a/activesupport/lib/active_support/core_ext/array/uniq_by.rb
+++ b/activesupport/lib/active_support/core_ext/array/uniq_by.rb
@@ -1,16 +1,20 @@
class Array
- # Returns an unique array based on the criteria given as a +Proc+.
+ # *DEPRECATED*: Use +Array#uniq+ instead.
+ #
+ # Returns a unique array based on the criteria in the block.
#
# [1, 2, 3, 4].uniq_by { |i| i.odd? } # => [1, 2]
#
- def uniq_by
- hash, array = {}, []
- each { |i| hash[yield(i)] ||= (array << i) }
- array
+ def uniq_by(&block)
+ ActiveSupport::Deprecation.warn 'uniq_by is deprecated. Use Array#uniq instead', caller
+ uniq(&block)
end
- # Same as uniq_by, but modifies self.
- def uniq_by!
- replace(uniq_by{ |i| yield(i) })
+ # *DEPRECATED*: Use +Array#uniq!+ instead.
+ #
+ # Same as +uniq_by+, but modifies +self+.
+ def uniq_by!(&block)
+ ActiveSupport::Deprecation.warn 'uniq_by! is deprecated. Use Array#uniq! instead', caller
+ uniq!(&block)
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb
index 4834eca8b1..9ea93d7226 100644
--- a/activesupport/lib/active_support/core_ext/array/wrap.rb
+++ b/activesupport/lib/active_support/core_ext/array/wrap.rb
@@ -25,9 +25,6 @@ class Array
# Array(:foo => :bar) # => [[:foo, :bar]]
# Array.wrap(:foo => :bar) # => [{:foo => :bar}]
#
- # Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
- # Array.wrap("foo\nbar") # => ["foo\nbar"]
- #
# There's also a related idiom that uses the splat operator:
#
# [*object]
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 391bdc925d..3ec7e576c8 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -1,29 +1,9 @@
require 'bigdecimal'
-
-begin
- require 'psych'
-rescue LoadError
-end
-
require 'yaml'
class BigDecimal
- YAML_TAG = 'tag:yaml.org,2002:float'
YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' }
- # This emits the number without any scientific notation.
- # This is better than self.to_f.to_s since it doesn't lose precision.
- #
- # Note that reconstituting YAML floats to native floats may lose precision.
- def to_yaml(opts = {})
- return super if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
-
- YAML.quick_emit(nil, opts) do |out|
- string = to_s
- out.scalar(YAML_TAG, YAML_MAPPING[string] || string, :plain)
- end
- end
-
def encode_with(coder)
string = to_s
coder.represent_scalar(nil, YAML_MAPPING[string] || string)
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 45bec264ff..c64685a694 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -109,7 +109,7 @@ class Class
end
private
- def singleton_class?
- !name || '' == name
- end
+ def singleton_class?
+ ancestors.first != self
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index 268303aaf2..fa1dbfdf06 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -2,33 +2,37 @@ require 'active_support/core_ext/array/extract_options'
# Extends the class object with class and instance accessors for class attributes,
# just like the native attr* accessors for instance attributes.
-#
-# Note that unlike +class_attribute+, if a subclass changes the value then that would
-# also change the value for parent class. Similarly if parent class changes the value
-# then that would change the value of subclasses too.
-#
-# class Person
-# cattr_accessor :hair_colors
-# end
-#
-# Person.hair_colors = [:brown, :black, :blonde, :red]
-# Person.hair_colors # => [:brown, :black, :blonde, :red]
-# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
-#
-# To opt out of the instance writer method, pass :instance_writer => false.
-# To opt out of the instance reader method, pass :instance_reader => false.
-# To opt out of both instance methods, pass :instance_accessor => false.
-#
-# class Person
-# cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false
-# end
-#
-# Person.new.hair_colors = [:brown] # => NoMethodError
-# Person.new.hair_colors # => NoMethodError
class Class
+ # Defines a class attribute if it's not defined and creates a reader method that
+ # returns the attribute value.
+ #
+ # class Person
+ # cattr_reader :hair_colors
+ # end
+ #
+ # Person.class_variable_set("@@hair_colors", [:brown, :black])
+ # Person.hair_colors # => [:brown, :black]
+ # Person.new.hair_colors # => [:brown, :black]
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # class Person
+ # cattr_reader :"1_Badname "
+ # end
+ # # => NameError: invalid attribute name
+ #
+ # If you want to opt out the instance reader method, you can pass <tt>instance_reader: false</tt>
+ # or <tt>instance_accessor: false</tt>.
+ #
+ # class Person
+ # cattr_reader :hair_colors, instance_reader: false
+ # end
+ #
+ # Person.new.hair_colors # => NoMethodError
def cattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
unless defined? @@#{sym}
@@#{sym} = nil
@@ -49,9 +53,47 @@ class Class
end
end
+ # Defines a class attribute if it's not defined and creates a writer method to allow
+ # assignment to the attribute.
+ #
+ # class Person
+ # cattr_writer :hair_colors
+ # end
+ #
+ # Person.hair_colors = [:brown, :black]
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
+ # Person.new.hair_colors = [:blonde, :red]
+ # Person.class_variable_get("@@hair_colors") # => [:blonde, :red]
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # class Person
+ # cattr_writer :"1_Badname "
+ # end
+ # # => NameError: invalid attribute name
+ #
+ # If you want to opt out the instance writer method, pass <tt>instance_writer: false</tt>
+ # or <tt>instance_accessor: false</tt>.
+ #
+ # class Person
+ # cattr_writer :hair_colors, instance_writer: false
+ # end
+ #
+ # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
+ #
+ # Also, you can pass a block to set up the attribute with a default value.
+ #
+ # class Person
+ # cattr_writer :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def cattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
unless defined? @@#{sym}
@@#{sym} = nil
@@ -69,10 +111,58 @@ class Class
end
EOS
end
- self.send("#{sym}=", yield) if block_given?
+ send("#{sym}=", yield) if block_given?
end
end
+ # Defines both class and instance accessors for class attributes.
+ #
+ # class Person
+ # cattr_accessor :hair_colors
+ # end
+ #
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
+ #
+ # If a subclass changes the value then that would also change the value for
+ # parent class. Similarly if parent class changes the value then that would
+ # change the value of subclasses too.
+ #
+ # class Male < Person
+ # end
+ #
+ # Male.hair_colors << :blue
+ # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
+ #
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ #
+ # class Person
+ # cattr_accessor :hair_colors, instance_writer: false, instance_reader: false
+ # end
+ #
+ # Person.new.hair_colors = [:brown] # => NoMethodError
+ # Person.new.hair_colors # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # class Person
+ # cattr_accessor :hair_colors, instance_accessor: false
+ # end
+ #
+ # Person.new.hair_colors = [:brown] # => NoMethodError
+ # Person.new.hair_colors # => NoMethodError
+ #
+ # Also you can pass a block to set up the attribute with a default value.
+ #
+ # class Person
+ # cattr_accessor :hair_colors do
+ # [:brown, :black, :blonde, :red]
+ # end
+ # end
+ #
+ # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
def cattr_accessor(*syms, &blk)
cattr_reader(*syms)
cattr_writer(*syms, &blk)
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
index 29bf7c0f3d..ff870f5fd1 100644
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/remove_method'
@@ -22,23 +20,21 @@ class Class
define_method("#{name}?") { !!send("#{name}") } if options[:instance_reader] != false
end
-private
-
- # Take the object being set and store it in a method. This gives us automatic
- # inheritance behavior, without having to store the object in an instance
- # variable and look up the superclass chain manually.
- def _stash_object_in_method(object, method, instance_reader = true)
- singleton_class.remove_possible_method(method)
- singleton_class.send(:define_method, method) { object }
- remove_possible_method(method)
- define_method(method) { object } if instance_reader
- end
-
- def _superclass_delegating_accessor(name, options = {})
- singleton_class.send(:define_method, "#{name}=") do |value|
- _stash_object_in_method(value, name, options[:instance_reader] != false)
+ private
+ # Take the object being set and store it in a method. This gives us automatic
+ # inheritance behavior, without having to store the object in an instance
+ # variable and look up the superclass chain manually.
+ def _stash_object_in_method(object, method, instance_reader = true)
+ singleton_class.remove_possible_method(method)
+ singleton_class.send(:define_method, method) { object }
+ remove_possible_method(method)
+ define_method(method) { object } if instance_reader
end
- send("#{name}=", nil)
- end
+ def _superclass_delegating_accessor(name, options = {})
+ singleton_class.send(:define_method, "#{name}=") do |value|
+ _stash_object_in_method(value, name, options[:instance_reader] != false)
+ end
+ send("#{name}=", nil)
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 46e9daaa8f..74ea047c24 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -7,7 +7,7 @@ class Class #:nodoc:
def descendants
descendants = []
- ObjectSpace.each_object(class << self; self; end) do |k|
+ ObjectSpace.each_object(singleton_class) do |k|
descendants.unshift k unless k == self
end
descendants
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index 26a99658cc..3e36c54eba 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -5,25 +5,15 @@ require 'active_support/core_ext/date/zones'
require 'active_support/core_ext/time/zones'
class Date
- DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }
-
- if RUBY_VERSION < '1.9'
- undef :>>
-
- # Backported from 1.9. The one in 1.8 leads to incorrect next_month and
- # friends for dates where the calendar reform is involved. It additionally
- # prevents an infinite loop fixed in r27013.
- def >>(n)
- y, m = (year * 12 + (mon - 1) + n).divmod(12)
- m, = (m + 1) .divmod(1)
- d = mday
- until jd2 = self.class.valid_civil?(y, m, d, start)
- d -= 1
- raise ArgumentError, 'invalid date' unless d > 0
- end
- self + (jd2 - jd)
- end
- end
+ DAYS_INTO_WEEK = {
+ :monday => 0,
+ :tuesday => 1,
+ :wednesday => 2,
+ :thursday => 3,
+ :friday => 4,
+ :saturday => 5,
+ :sunday => 6
+ }
class << self
# Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
@@ -49,7 +39,7 @@ class Date
# Returns true if the Date object's date is today.
def today?
- self.to_date == ::Date.current # we need the to_date because of DateTime
+ to_date == ::Date.current # we need the to_date because of DateTime
end
# Returns true if the Date object's date lies in the future.
@@ -117,15 +107,13 @@ class Date
# Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
#
- # Examples:
- #
# Date.new(2007, 5, 12).change(:day => 1) # => Date.new(2007, 5, 1)
# Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12)
def change(options)
::Date.new(
- options[:year] || self.year,
- options[:month] || self.month,
- options[:day] || self.day
+ options.fetch(:year, year),
+ options.fetch(:month, month),
+ options.fetch(:day, day)
)
end
@@ -154,90 +142,114 @@ class Date
advance(:years => years)
end
- # Shorthand for years_ago(1)
- def prev_year
- years_ago(1)
- end unless method_defined?(:prev_year)
-
- # Shorthand for years_since(1)
- def next_year
- years_since(1)
- end unless method_defined?(:next_year)
-
- # Shorthand for months_ago(1)
- def prev_month
- months_ago(1)
- end unless method_defined?(:prev_month)
-
- # Shorthand for months_since(1)
- def next_month
- months_since(1)
- end unless method_defined?(:next_month)
+ # Returns number of days to start of this week. Week is assumed to start on
+ # +start_day+, default is +:monday+.
+ def days_to_week_start(start_day = :monday)
+ start_day_number = DAYS_INTO_WEEK[start_day]
+ current_day_number = wday != 0 ? wday - 1 : 6
+ (current_day_number - start_day_number) % 7
+ end
- # Returns a new Date/DateTime representing the "start" of this week (i.e, Monday; DateTime objects will have time set to 0:00).
- def beginning_of_week
- days_to_monday = self.wday!=0 ? self.wday-1 : 6
- result = self - days_to_monday
- self.acts_like?(:time) ? result.midnight : result
+ # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
+ # assumed to start on +start_day+, default is +:monday+. +DateTime+ objects
+ # have their time set to 0:00.
+ def beginning_of_week(start_day = :monday)
+ days_to_start = days_to_week_start(start_day)
+ result = self - days_to_start
+ acts_like?(:time) ? result.midnight : result
end
- alias :monday :beginning_of_week
alias :at_beginning_of_week :beginning_of_week
- # Returns a new Date/DateTime representing the end of this week (Sunday, DateTime objects will have time set to 23:59:59).
- def end_of_week
- days_to_sunday = self.wday!=0 ? 7-self.wday : 0
- result = self + days_to_sunday.days
- self.acts_like?(:time) ? result.end_of_day : result
+ # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
+ # assumed to start on a Monday. +DateTime+ objects have their time set to 0:00.
+ def monday
+ beginning_of_week
+ end
+
+ # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
+ # assumed to start on +start_day+, default is +:monday+. +DateTime+ objects
+ # have their time set to 23:59:59.
+ def end_of_week(start_day = :monday)
+ days_to_end = 6 - days_to_week_start(start_day)
+ result = self + days_to_end.days
+ acts_like?(:time) ? result.end_of_day : result
end
- alias :sunday :end_of_week
alias :at_end_of_week :end_of_week
- # Returns a new Date/DateTime representing the start of the given day in the previous week (default is Monday).
+ # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
+ # assumed to start on a Monday. +DateTime+ objects have their time set to 23:59:59.
+ def sunday
+ end_of_week
+ end
+
+ # Returns a new +Date+/+DateTime+ representing the given +day+ in the previous
+ # week. Default is +:monday+. +DateTime+ objects have their time set to 0:00.
def prev_week(day = :monday)
result = (self - 7).beginning_of_week + DAYS_INTO_WEEK[day]
- self.acts_like?(:time) ? result.change(:hour => 0) : result
+ acts_like?(:time) ? result.change(:hour => 0) : result
end
+ alias :last_week :prev_week
+
+ # Alias of prev_month
+ alias :last_month :prev_month
+
+ # Alias of prev_year
+ alias :last_year :prev_year
- # Returns a new Date/DateTime representing the start of the given day in next week (default is Monday).
+ # Returns a new Date/DateTime representing the start of the given day in next week (default is :monday).
def next_week(day = :monday)
result = (self + 7).beginning_of_week + DAYS_INTO_WEEK[day]
- self.acts_like?(:time) ? result.change(:hour => 0) : result
+ acts_like?(:time) ? result.change(:hour => 0) : result
end
# Returns a new ; DateTime objects will have time set to 0:00DateTime representing the start of the month (1st of the month; DateTime objects will have time set to 0:00)
def beginning_of_month
- self.acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
+ acts_like?(:time) ? change(:day => 1, :hour => 0) : change(:day => 1)
end
alias :at_beginning_of_month :beginning_of_month
# Returns a new Date/DateTime representing the end of the month (last day of the month; DateTime objects will have time set to 0:00)
def end_of_month
- last_day = ::Time.days_in_month( self.month, self.year )
- self.acts_like?(:time) ? change(:day => last_day, :hour => 23, :min => 59, :sec => 59) : change(:day => last_day)
+ last_day = ::Time.days_in_month(month, year)
+ if acts_like?(:time)
+ change(:day => last_day, :hour => 23, :min => 59, :sec => 59)
+ else
+ change(:day => last_day)
+ end
end
alias :at_end_of_month :end_of_month
# Returns a new Date/DateTime representing the start of the quarter (1st of january, april, july, october; DateTime objects will have time set to 0:00)
def beginning_of_quarter
- beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= self.month })
+ first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
+ beginning_of_month.change(:month => first_quarter_month)
end
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new Date/DateTime representing the end of the quarter (last day of march, june, september, december; DateTime objects will have time set to 23:59:59)
def end_of_quarter
- beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= self.month }).end_of_month
+ last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
+ beginning_of_month.change(:month => last_quarter_month).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
# Returns a new Date/DateTime representing the start of the year (1st of january; DateTime objects will have time set to 0:00)
def beginning_of_year
- self.acts_like?(:time) ? change(:month => 1, :day => 1, :hour => 0) : change(:month => 1, :day => 1)
+ if acts_like?(:time)
+ change(:month => 1, :day => 1, :hour => 0)
+ else
+ change(:month => 1, :day => 1)
+ end
end
alias :at_beginning_of_year :beginning_of_year
# Returns a new Time representing the end of the year (31st of december; DateTime objects will have time set to 23:59:59)
def end_of_year
- self.acts_like?(:time) ? change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) : change(:month => 12, :day => 31)
+ if acts_like?(:time)
+ change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59)
+ else
+ change(:month => 12, :day => 31)
+ end
end
alias :at_end_of_year :end_of_year
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 338104fd05..81f969e786 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -5,12 +5,15 @@ require 'active_support/core_ext/module/remove_method'
class Date
DATE_FORMATS = {
- :short => "%e %b",
- :long => "%B %e, %Y",
- :db => "%Y-%m-%d",
- :number => "%Y%m%d",
- :long_ordinal => lambda { |date| date.strftime("%B #{ActiveSupport::Inflector.ordinalize(date.day)}, %Y") }, # => "April 25th, 2007"
- :rfc822 => "%e %b %Y"
+ :short => '%e %b',
+ :long => '%B %e, %Y',
+ :db => '%Y-%m-%d',
+ :number => '%Y%m%d',
+ :long_ordinal => lambda { |date|
+ day_format = ActiveSupport::Inflector.ordinalize(date.day)
+ date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
+ },
+ :rfc822 => '%e %b %Y'
}
# Ruby 1.9 has Date#to_time which converts to localtime only.
@@ -23,7 +26,6 @@ class Date
#
# This method is aliased to <tt>to_s</tt>.
#
- # ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_formatted_s(:db) # => "2007-11-10"
@@ -40,7 +42,7 @@ class Date
# or Proc instance that takes a date argument as the value.
#
# # config/initializers/time_formats.rb
- # Date::DATE_FORMATS[:month_and_year] = "%B %Y"
+ # Date::DATE_FORMATS[:month_and_year] = '%B %Y'
# Date::DATE_FORMATS[:short_ordinal] = lambda { |date| date.strftime("%B #{date.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = DATE_FORMATS[format]
@@ -58,21 +60,14 @@ class Date
# Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
def readable_inspect
- strftime("%a, %d %b %Y")
+ strftime('%a, %d %b %Y')
end
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
- # A method to keep Time, Date and DateTime instances interchangeable on conversions.
- # In this case, it simply returns +self+.
- def to_date
- self
- end if RUBY_VERSION < '1.9'
-
# Converts a Date instance to a Time, where the time is set to the beginning of the day.
# The timezone can be either :local or :utc (default :local).
#
- # ==== Examples
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
# date.to_time # => Sat Nov 10 00:00:00 0800 2007
@@ -83,23 +78,6 @@ class Date
::Time.send("#{form}_time", year, month, day)
end
- # Converts a Date instance to a DateTime, where the time is set to the beginning of the day
- # and UTC offset is set to 0.
- #
- # ==== Examples
- # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
- #
- # date.to_datetime # => Sat, 10 Nov 2007 00:00:00 0000
- def to_datetime
- ::DateTime.civil(year, month, day, 0, 0, 0, 0)
- end if RUBY_VERSION < '1.9'
-
- def iso8601
- strftime('%F')
- end if RUBY_VERSION < '1.9'
-
- alias_method :rfc3339, :iso8601 if RUBY_VERSION < '1.9'
-
def xmlschema
to_time_in_current_zone.xmlschema
end
diff --git a/activesupport/lib/active_support/core_ext/date/freeze.rb b/activesupport/lib/active_support/core_ext/date/freeze.rb
deleted file mode 100644
index a731f8345e..0000000000
--- a/activesupport/lib/active_support/core_ext/date/freeze.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# Date memoizes some instance methods using metaprogramming to wrap
-# the methods with one that caches the result in an instance variable.
-#
-# If a Date is frozen but the memoized method hasn't been called, the
-# first call will result in a frozen object error since the memo
-# instance variable is uninitialized.
-#
-# Work around by eagerly memoizing before the first freeze.
-#
-# Ruby 1.9 uses a preinitialized instance variable so it's unaffected.
-# This hack is as close as we can get to feature detection:
-if RUBY_VERSION < '1.9'
- require 'date'
- begin
- ::Date.today.freeze.jd
- rescue => frozen_object_error
- if frozen_object_error.message =~ /frozen/
- class Date #:nodoc:
- def freeze
- unless frozen?
- self.class.private_instance_methods(false).each do |m|
- if m.to_s =~ /\A__\d+__\Z/
- instance_variable_set(:"@#{m}", [send(m)])
- end
- end
- end
-
- super
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 48cf1a435d..fd78044b5d 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -1,10 +1,12 @@
-require 'rational' unless RUBY_VERSION >= '1.9.2'
+require 'active_support/deprecation'
class DateTime
class << self
- # DateTimes aren't aware of DST rules, so use a consistent non-DST offset when creating a DateTime with an offset in the local zone
+ # *DEPRECATED*: Use +DateTime.civil_from_format+ directly.
def local_offset
- ::Time.local(2007).utc_offset.to_r / 86400
+ ActiveSupport::Deprecation.warn 'DateTime.local_offset is deprecated. Use DateTime.civil_from_format directly.', caller
+
+ ::Time.local(2012).utc_offset.to_r / 86400
end
# Returns <tt>Time.zone.now.to_datetime</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise returns <tt>Time.now.to_datetime</tt>.
@@ -33,14 +35,14 @@ class DateTime
# minute is passed, then sec is set to 0.
def change(options)
::DateTime.civil(
- options[:year] || year,
- options[:month] || month,
- options[:day] || day,
- options[:hour] || hour,
- options[:min] || (options[:hour] ? 0 : min),
- options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec),
- options[:offset] || offset,
- options[:start] || start
+ options.fetch(:year, year),
+ options.fetch(:month, month),
+ options.fetch(:day, day),
+ options.fetch(:hour, hour),
+ options.fetch(:min, options[:hour] ? 0 : min),
+ options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec),
+ options.fetch(:offset, offset),
+ options.fetch(:start, start)
)
end
@@ -51,8 +53,16 @@ class DateTime
def advance(options)
d = to_date.advance(options)
datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
- seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
- seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date.since(seconds_to_advance)
+ seconds_to_advance = \
+ options.fetch(:seconds, 0) +
+ options.fetch(:minutes, 0) * 60 +
+ options.fetch(:hours, 0) * 3600
+
+ if seconds_to_advance.zero?
+ datetime_advanced_by_date
+ else
+ datetime_advanced_by_date.since seconds_to_advance
+ end
end
# Returns a new DateTime representing the time a number of seconds ago
@@ -81,33 +91,19 @@ class DateTime
change(:hour => 23, :min => 59, :sec => 59)
end
- # 1.9.3 defines + and - on DateTime, < 1.9.3 do not.
- if DateTime.public_instance_methods(false).include?(:+)
- def plus_with_duration(other) #:nodoc:
- if ActiveSupport::Duration === other
- other.since(self)
- else
- plus_without_duration(other)
- end
- end
- alias_method :plus_without_duration, :+
- alias_method :+, :plus_with_duration
-
- def minus_with_duration(other) #:nodoc:
- if ActiveSupport::Duration === other
- plus_with_duration(-other)
- else
- minus_without_duration(other)
- end
- end
- alias_method :minus_without_duration, :-
- alias_method :-, :minus_with_duration
+ # Returns a new DateTime representing the start of the hour (hh:00:00)
+ def beginning_of_hour
+ change(:min => 0)
+ end
+ alias :at_beginning_of_hour :beginning_of_hour
+
+ # Returns a new DateTime representing the end of the hour (hh:59:59)
+ def end_of_hour
+ change(:min => 59, :sec => 59)
end
# Adjusts DateTime to UTC by adding its offset value; offset is set to 0
#
- # Example:
- #
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
def utc
@@ -129,4 +125,5 @@ class DateTime
def <=>(other)
super other.to_datetime
end
+
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index ca899c714c..19925198c0 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -6,7 +6,7 @@ require 'active_support/values/time_zone'
class DateTime
# Ruby 1.9 has DateTime#to_time which internally relies on Time. We define our own #to_time which allows
# DateTimes outside the range of what can be created with Time.
- remove_method :to_time if instance_methods.include?(:to_time)
+ remove_method :to_time
# Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
#
@@ -30,7 +30,7 @@ class DateTime
# datetime argument as the value.
#
# # config/initializers/time_formats.rb
- # Time::DATE_FORMATS[:month_and_year] = "%B %Y"
+ # Time::DATE_FORMATS[:month_and_year] = '%B %Y'
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = ::Time::DATE_FORMATS[format]
@@ -42,7 +42,6 @@ class DateTime
alias_method :to_default_s, :to_s unless (instance_methods(false) & [:to_s, 'to_s']).empty?
alias_method :to_s, :to_formatted_s
- # Returns the +utc_offset+ as an +HH:MM formatted string. Examples:
#
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
# datetime.formatted_offset # => "-06:00"
@@ -58,32 +57,31 @@ class DateTime
alias_method :default_inspect, :inspect
alias_method :inspect, :readable_inspect
- # Converts self to a Ruby Date object; time portion is discarded.
- def to_date
- ::Date.new(year, month, day)
- end unless instance_methods(false).include?(:to_date)
-
# Attempts to convert self to a Ruby Time object; returns self if out of range of Ruby Time class.
# If self has an offset other than 0, self will just be returned unaltered, since there's no clean way to map it to a Time.
def to_time
- self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * (RUBY_VERSION < '1.9' ? 86400000000 : 1000000)) : self
+ if offset == 0
+ ::Time.utc_time(year, month, day, hour, min, sec, sec_fraction * 1000000)
+ else
+ self
+ end
end
- # To be able to keep Times, Dates and DateTimes interchangeable on conversions.
- def to_datetime
- self
- end unless instance_methods(false).include?(:to_datetime)
-
+ # Returns DateTime with local offset for given year if format is local else offset is zero
+ #
+ # DateTime.civil_from_format :local, 2012
+ # # => Sun, 01 Jan 2012 00:00:00 +0300
+ # DateTime.civil_from_format :local, 2012, 12, 17
+ # # => Mon, 17 Dec 2012 00:00:00 +0000
def self.civil_from_format(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0)
- offset = utc_or_local.to_sym == :local ? local_offset : 0
+ if utc_or_local.to_sym == :local
+ offset = ::Time.local(year, month, day).utc_offset.to_r / 86400
+ else
+ offset = 0
+ end
civil(year, month, day, hour, min, sec, offset)
end
- # Converts datetime to an appropriate format for use in XML.
- def xmlschema
- strftime("%Y-%m-%dT%H:%M:%S%Z")
- end unless instance_methods(false).include?(:xmlschema)
-
# Converts self to a floating-point number of seconds since the Unix epoch.
def to_f
seconds_since_unix_epoch.to_f
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 6fa55a9255..823735d3e2 100644
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb
@@ -14,8 +14,10 @@ class DateTime
#
# DateTime.new(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
- return self unless zone
-
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ if zone
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ else
+ self
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 9343bb7106..02d5a7080f 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -1,42 +1,5 @@
-require 'active_support/ordered_hash'
-
module Enumerable
- # Ruby 1.8.7 introduces group_by, but the result isn't ordered. Override it.
- remove_method(:group_by) if [].respond_to?(:group_by) && RUBY_VERSION < '1.9'
-
- # Collect an enumerable into sets, grouped by the result of a block. Useful,
- # for example, for grouping records by date.
- #
- # Example:
- #
- # latest_transcripts.group_by(&:day).each do |day, transcripts|
- # p "#{day} -> #{transcripts.map(&:class).join(', ')}"
- # end
- # "2006-03-01 -> Transcript"
- # "2006-02-28 -> Transcript"
- # "2006-02-27 -> Transcript, Transcript"
- # "2006-02-26 -> Transcript, Transcript"
- # "2006-02-25 -> Transcript"
- # "2006-02-24 -> Transcript, Transcript"
- # "2006-02-23 -> Transcript"
- def group_by
- return to_enum :group_by unless block_given?
- assoc = ActiveSupport::OrderedHash.new
-
- each do |element|
- key = yield(element)
-
- if assoc.has_key?(key)
- assoc[key] << element
- else
- assoc[key] = [element]
- end
- end
-
- assoc
- end unless [].respond_to?(:group_by)
-
- # Calculates a sum from the elements. Examples:
+ # Calculates a sum from the elements.
#
# payments.sum { |p| p.price * p.tax_rate }
# payments.sum(&:price)
@@ -48,7 +11,7 @@ module Enumerable
# It can also calculate the sum without the use of a block.
#
# [5, 15, 10].sum # => 30
- # ["foo", "bar"].sum # => "foobar"
+ # ['foo', 'bar'].sum # => "foobar"
# [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5]
#
# The default sum of an empty list is zero. You can override this default:
@@ -63,28 +26,7 @@ module Enumerable
end
end
- # Iterates over a collection, passing the current element *and* the
- # +memo+ to the block. Handy for building up hashes or
- # reducing collections down to one object. Examples:
- #
- # %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
- # # => {'foo' => 'FOO', 'bar' => 'BAR'}
- #
- # *Note* that you can't use immutable objects like numbers, true or false as
- # the memo. You would think the following returns 120, but since the memo is
- # never changed, it does not.
- #
- # (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1
- #
- def each_with_object(memo)
- return to_enum :each_with_object, memo unless block_given?
- each do |element|
- yield element, memo
- end
- memo
- end unless [].respond_to?(:each_with_object)
-
- # Convert an enumerable to a hash. Examples:
+ # Convert an enumerable to a hash.
#
# people.index_by(&:login)
# => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
@@ -92,8 +34,11 @@ module Enumerable
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
#
def index_by
- return to_enum :index_by unless block_given?
- Hash[map { |elem| [yield(elem), elem] }]
+ if block_given?
+ Hash[map { |elem| [yield(elem), elem] }]
+ else
+ to_enum :index_by
+ end
end
# Returns true if the enumerable has more than 1 element. Functionally equivalent to enum.to_a.size > 1.
@@ -106,7 +51,7 @@ module Enumerable
cnt > 1
end
else
- any?{ (cnt += 1) > 1 }
+ any? { (cnt += 1) > 1 }
end
end
@@ -120,8 +65,11 @@ class Range #:nodoc:
# Optimize range sum to use arithmetic progression if a block is not given and
# we have a range of numeric values.
def sum(identity = 0)
- return super if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer))
- actual_last = exclude_end? ? (last - 1) : last
- (actual_last - first + 1) * (actual_last + first) / 2
+ if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer))
+ super
+ else
+ actual_last = exclude_end? ? (last - 1) : last
+ (actual_last - first + 1) * (actual_last + first) / 2
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/exception.rb b/activesupport/lib/active_support/core_ext/exception.rb
index ef801e713d..ba7757ea07 100644
--- a/activesupport/lib/active_support/core_ext/exception.rb
+++ b/activesupport/lib/active_support/core_ext/exception.rb
@@ -1,3 +1,3 @@
module ActiveSupport
- FrozenObjectError = RUBY_VERSION < '1.9' ? TypeError : RuntimeError
+ FrozenObjectError = RuntimeError
end
diff --git a/activesupport/lib/active_support/core_ext/file.rb b/activesupport/lib/active_support/core_ext/file.rb
index a763447566..dc24afbe7f 100644
--- a/activesupport/lib/active_support/core_ext/file.rb
+++ b/activesupport/lib/active_support/core_ext/file.rb
@@ -1,2 +1 @@
require 'active_support/core_ext/file/atomic'
-require 'active_support/core_ext/file/path'
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index 3645597301..9e504851e7 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -2,21 +2,22 @@ class File
# Write to a file atomically. Useful for situations where you don't
# want other processes or threads to see half-written files.
#
- # File.atomic_write("important.file") do |file|
- # file.write("hello")
+ # File.atomic_write('important.file') do |file|
+ # file.write('hello')
# end
#
# If your temp directory is not on the same filesystem as the file you're
# trying to write, you can provide a different temporary directory.
#
- # File.atomic_write("/data/something.important", "/data/tmp") do |file|
- # file.write("hello")
+ # File.atomic_write('/data/something.important', '/data/tmp') do |file|
+ # file.write('hello')
# end
def self.atomic_write(file_name, temp_dir = Dir.tmpdir)
require 'tempfile' unless defined?(Tempfile)
require 'fileutils' unless defined?(FileUtils)
temp_file = Tempfile.new(basename(file_name), temp_dir)
+ temp_file.binmode
yield temp_file
temp_file.close
@@ -25,8 +26,14 @@ class File
old_stat = stat(file_name)
rescue Errno::ENOENT
# No old permissions, write a temp file to determine the defaults
- check_name = join(dirname(file_name), ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}")
- open(check_name, "w") { }
+ temp_file_name = [
+ '.permissions_check',
+ Thread.current.object_id,
+ Process.pid,
+ rand(1000000)
+ ].join('.')
+ check_name = join(dirname(file_name), temp_file_name)
+ open(check_name, 'w') { }
old_stat = stat(check_name)
unlink(check_name)
end
diff --git a/activesupport/lib/active_support/core_ext/file/path.rb b/activesupport/lib/active_support/core_ext/file/path.rb
deleted file mode 100644
index b5feab80ae..0000000000
--- a/activesupport/lib/active_support/core_ext/file/path.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class File
- unless File.allocate.respond_to?(:to_path)
- alias to_path path
- end
-end \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/float.rb b/activesupport/lib/active_support/core_ext/float.rb
deleted file mode 100644
index 7570471b95..0000000000
--- a/activesupport/lib/active_support/core_ext/float.rb
+++ /dev/null
@@ -1 +0,0 @@
-require 'active_support/core_ext/float/rounding'
diff --git a/activesupport/lib/active_support/core_ext/float/rounding.rb b/activesupport/lib/active_support/core_ext/float/rounding.rb
deleted file mode 100644
index 0d4fb87665..0000000000
--- a/activesupport/lib/active_support/core_ext/float/rounding.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-class Float
- alias precisionless_round round
- private :precisionless_round
-
- # Rounds the float with the specified precision.
- #
- # x = 1.337
- # x.round # => 1
- # x.round(1) # => 1.3
- # x.round(2) # => 1.34
- def round(precision = nil)
- if precision
- magnitude = 10.0 ** precision
- (self * magnitude).round / magnitude
- else
- precisionless_round
- end
- end
-end if RUBY_VERSION < '1.9'
diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb
index fd1cda991e..501483498d 100644
--- a/activesupport/lib/active_support/core_ext/hash.rb
+++ b/activesupport/lib/active_support/core_ext/hash.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/hash/deep_merge'
-require 'active_support/core_ext/hash/deep_dup'
require 'active_support/core_ext/hash/diff'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/indifferent_access'
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 5f07bb4f5a..469dc41f2d 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -8,7 +8,7 @@ require 'active_support/core_ext/string/inflections'
class Hash
# Returns a string containing an XML representation of its receiver:
#
- # {"foo" => 1, "bar" => 2}.to_xml
+ # {'foo' => 1, 'bar' => 2}.to_xml
# # =>
# # <?xml version="1.0" encoding="UTF-8"?>
# # <hash>
@@ -26,20 +26,20 @@ class Hash
#
# * If +value+ is a callable object it must expect one or two arguments. Depending
# on the arity, the callable is invoked with the +options+ hash as first argument
- # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
+ # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
# callable can add nodes by using <tt>options[:builder]</tt>.
#
- # "foo".to_xml(lambda { |options, key| options[:builder].b(key) })
+ # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
# # => "<b>foo</b>"
#
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
- #
+ #
# class Foo
# def to_xml(options)
- # options[:builder].bar "fooing!"
+ # options[:builder].bar 'fooing!'
# end
# end
- #
+ #
# {:foo => Foo.new}.to_xml(:skip_instruct => true)
# # => "<hash><bar>fooing!</bar></hash>"
#
@@ -71,7 +71,7 @@ class Hash
options = options.dup
options[:indent] ||= 2
- options[:root] ||= "hash"
+ options[:root] ||= 'hash'
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
builder = options[:builder]
@@ -100,24 +100,24 @@ class Hash
[]
else
case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a?
- when "Array"
+ when 'Array'
entries.collect { |v| typecast_xml_value(v) }
- when "Hash"
+ when 'Hash'
[typecast_xml_value(entries)]
else
raise "can't typecast #{entries.inspect}"
end
end
- elsif value['type'] == 'file' ||
- (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?))
- content = value["__content__"]
- if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
+ elsif value['type'] == 'file' ||
+ (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?))
+ content = value['__content__']
+ if parser = ActiveSupport::XmlMini::PARSING[value['type']]
parser.arity == 1 ? parser.call(content) : parser.call(content, value)
else
content
end
elsif value['type'] == 'string' && value['nil'] != 'true'
- ""
+ ''
# blank or nil parsed values are represented by nil
elsif value.blank? || value['nil'] == 'true'
nil
@@ -131,7 +131,7 @@ class Hash
# Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with
# how multipart uploaded files from HTML appear
- xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value
+ xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
end
when 'Array'
value.map! { |i| typecast_xml_value(i) }
@@ -145,9 +145,9 @@ class Hash
def unrename_keys(params)
case params.class.to_s
- when "Hash"
- Hash[params.map { |k,v| [k.to_s.tr("-", "_"), unrename_keys(v)] } ]
- when "Array"
+ when 'Hash'
+ Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ]
+ when 'Array'
params.map { |v| unrename_keys(v) }
else
params
diff --git a/activesupport/lib/active_support/core_ext/hash/deep_dup.rb b/activesupport/lib/active_support/core_ext/hash/deep_dup.rb
deleted file mode 100644
index 447142605c..0000000000
--- a/activesupport/lib/active_support/core_ext/hash/deep_dup.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class Hash
- # Returns a deep copy of hash.
- def deep_dup
- duplicate = self.dup
- duplicate.each_pair do |k,v|
- tv = duplicate[k]
- duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_dup : v
- end
- duplicate
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
index af771c86ff..023bf68a87 100644
--- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb
@@ -1,11 +1,16 @@
class Hash
# Returns a new hash with +self+ and +other_hash+ merged recursively.
+ #
+ # h1 = {x: {y: [4,5,6]}, z: [7,8,9]}
+ # h2 = {x: {y: [7,8,9]}, z: "xyz"}
+ #
+ # h1.deep_merge(h2) #=> {:x => {:y => [7, 8, 9]}, :z => "xyz"}
+ # h2.deep_merge(h1) #=> {:x => {:y => [4, 5, 6]}, :z => [7, 8, 9]}
def deep_merge(other_hash)
dup.deep_merge!(other_hash)
end
- # Returns a new hash with +self+ and +other_hash+ merged recursively.
- # Modifies the receiver in place.
+ # Same as +deep_merge+, but modifies +self+.
def deep_merge!(other_hash)
other_hash.each_pair do |k,v|
tv = self[k]
diff --git a/activesupport/lib/active_support/core_ext/hash/diff.rb b/activesupport/lib/active_support/core_ext/hash/diff.rb
index b904f49fa8..831dee8ecb 100644
--- a/activesupport/lib/active_support/core_ext/hash/diff.rb
+++ b/activesupport/lib/active_support/core_ext/hash/diff.rb
@@ -1,13 +1,13 @@
class Hash
# Returns a hash that represents the difference between two hashes.
#
- # Examples:
- #
# {1 => 2}.diff(1 => 2) # => {}
# {1 => 2}.diff(1 => 3) # => {1 => 2}
# {}.diff(1 => 2) # => {1 => 2}
# {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
- def diff(h2)
- dup.delete_if { |k, v| h2[k] == v }.merge!(h2.dup.delete_if { |k, v| has_key?(k) })
+ def diff(other)
+ dup.
+ delete_if { |k, v| other[k] == v }.
+ merge!(other.dup.delete_if { |k, v| has_key?(k) })
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 89729df258..5a61906222 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -9,7 +9,7 @@ class Hash
# for instance:
#
# {:a => 1}.with_indifferent_access.except(:a) # => {}
- # {:a => 1}.with_indifferent_access.except("a") # => {}
+ # {:a => 1}.with_indifferent_access.except('a') # => {}
#
def except(*keys)
dup.except!(*keys)
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index f4cb445444..7d54c9fae6 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -14,7 +14,7 @@ class Hash
# #with_indifferent_access. This method will be called on the current object
# by the enclosing object and is aliased to #with_indifferent_access by
# default. Subclasses of Hash may overwrite this method to return +self+ if
- # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be
+ # converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
# desirable.
#
# b = {:b => 1}
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index d8748b1138..be4d611ce7 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,10 +1,18 @@
class Hash
# Return a new hash with all keys converted to strings.
+ #
+ # { :name => 'Rob', :years => '28' }.stringify_keys
+ # #=> { "name" => "Rob", "years" => "28" }
def stringify_keys
- dup.stringify_keys!
+ result = {}
+ keys.each do |key|
+ result[key.to_s] = self[key]
+ end
+ result
end
- # Destructively convert all keys to strings.
+ # Destructively convert all keys to strings. Same as
+ # +stringify_keys+, but modifies +self+.
def stringify_keys!
keys.each do |key|
self[key.to_s] = delete(key)
@@ -14,34 +22,39 @@ class Hash
# Return a new hash with all keys converted to symbols, as long as
# they respond to +to_sym+.
+ #
+ # { 'name' => 'Rob', 'years' => '28' }.symbolize_keys
+ # #=> { :name => "Rob", :years => "28" }
def symbolize_keys
- dup.symbolize_keys!
+ result = {}
+ keys.each do |key|
+ result[(key.to_sym rescue key)] = self[key]
+ end
+ result
end
+ alias_method :to_options, :symbolize_keys
# Destructively convert all keys to symbols, as long as they respond
- # to +to_sym+.
+ # to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
def symbolize_keys!
keys.each do |key|
- self[(key.to_sym rescue key) || key] = delete(key)
+ self[(key.to_sym rescue key)] = delete(key)
end
self
end
-
- alias_method :to_options, :symbolize_keys
alias_method :to_options!, :symbolize_keys!
# Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
# Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
# as keys, this will fail.
#
- # ==== Examples
- # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
- # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key: name"
- # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
+ # { :name => 'Rob', :years => '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
+ # { :name => 'Rob', :age => '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
+ # { :name => 'Rob', :age => '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
def assert_valid_keys(*valid_keys)
valid_keys.flatten!
each_key do |k|
- raise(ArgumentError, "Unknown key: #{k}") unless valid_keys.include?(k)
+ raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
index 01863a162b..6074103484 100644
--- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
@@ -18,6 +18,5 @@ class Hash
# right wins if there is no left
merge!( other_hash ){|key,left,right| left }
end
-
alias_method :reverse_update, :reverse_merge!
end
diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb
index 0484d8e5d8..b862b5ae2a 100644
--- a/activesupport/lib/active_support/core_ext/hash/slice.rb
+++ b/activesupport/lib/active_support/core_ext/hash/slice.rb
@@ -13,17 +13,15 @@ class Hash
# valid_keys = [:mass, :velocity, :time]
# search(options.slice(*valid_keys))
def slice(*keys)
- keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
- hash = self.class.new
- keys.each { |k| hash[k] = self[k] if has_key?(k) }
- hash
+ keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
+ keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
end
# Replaces the hash with only the given keys.
- # Returns a hash contained the removed key/value pairs
+ # Returns a hash containing the removed key/value pairs.
# {:a => 1, :b => 2, :c => 3, :d => 4}.slice!(:a, :b) # => {:c => 3, :d => 4}
def slice!(*keys)
- keys = keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
+ keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
omit = slice(*self.keys - keys)
hash = slice(*keys)
replace(hash)
@@ -33,8 +31,6 @@ class Hash
# Removes and returns the key/value pairs matching the given keys.
# {:a => 1, :b => 2, :c => 3, :d => 4}.extract!(:a, :b) # => {:a => 1, :b => 2}
def extract!(*keys)
- result = {}
- keys.each {|key| result[key] = delete(key) }
- result
+ keys.each_with_object({}) { |key, result| result[key] = delete(key) }
end
end
diff --git a/activesupport/lib/active_support/core_ext/integer/inflections.rb b/activesupport/lib/active_support/core_ext/integer/inflections.rb
index 0e606056c0..1e30687166 100644
--- a/activesupport/lib/active_support/core_ext/integer/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/integer/inflections.rb
@@ -14,4 +14,18 @@ class Integer
def ordinalize
ActiveSupport::Inflector.ordinalize(self)
end
+
+ # Ordinal returns the suffix used to denote the position
+ # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
+ #
+ # 1.ordinal # => "st"
+ # 2.ordinal # => "nd"
+ # 1002.ordinal # => "nd"
+ # 1003.ordinal # => "rd"
+ # -11.ordinal # => "th"
+ # -1001.ordinal # => "st"
+ #
+ def ordinal
+ ActiveSupport::Inflector.ordinal(self)
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/integer/multiple.rb b/activesupport/lib/active_support/core_ext/integer/multiple.rb
index 8dff217ddc..7c6c2f1ca7 100644
--- a/activesupport/lib/active_support/core_ext/integer/multiple.rb
+++ b/activesupport/lib/active_support/core_ext/integer/multiple.rb
@@ -1,5 +1,9 @@
class Integer
# Check whether the integer is evenly divisible by the argument.
+ #
+ # 0.multiple_of?(0) #=> true
+ # 6.multiple_of?(5) #=> false
+ # 10.multiple_of?(2) #=> true
def multiple_of?(number)
number != 0 ? self % number == 0 : zero?
end
diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb
index c677400396..894b5d0696 100644
--- a/activesupport/lib/active_support/core_ext/integer/time.rb
+++ b/activesupport/lib/active_support/core_ext/integer/time.rb
@@ -24,9 +24,9 @@ class Integer
# 1.year.to_f.from_now
#
# In such cases, Ruby's core
- # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and
- # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision
- # date and time arithmetic
+ # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
+ # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
+ # date and time arithmetic.
def months
ActiveSupport::Duration.new(self * 30.days, [[:months, self]])
end
diff --git a/activesupport/lib/active_support/core_ext/io.rb b/activesupport/lib/active_support/core_ext/io.rb
deleted file mode 100644
index 75f1055191..0000000000
--- a/activesupport/lib/active_support/core_ext/io.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-if RUBY_VERSION < '1.9.2'
-
-# :stopdoc:
-class IO
- def self.binread(name, length = nil, offset = nil)
- return File.read name unless length || offset
- File.open(name, 'rb') { |f|
- f.seek offset if offset
- f.read length
- }
- end
-end
-# :startdoc:
-
-end
diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
index 7516f41e0b..2073cac98d 100644
--- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb
@@ -1,8 +1,8 @@
module Kernel
unless respond_to?(:debugger)
- # Starts a debugging session if ruby-debug has been loaded (call rails server --debugger to do load it).
+ # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it).
def debugger
- message = "\n***** Debugger requested, but was not available (ensure ruby-debug is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n"
+ message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n"
defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message)
end
alias breakpoint debugger unless respond_to?(:breakpoint)
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 526b8378a5..ad3f9ebec9 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,4 +1,5 @@
require 'rbconfig'
+
module Kernel
# Sets $VERBOSE to nil for the duration of the block and back to its original value afterwards.
#
@@ -49,10 +50,10 @@ module Kernel
#
# suppress(ZeroDivisionError) do
# 1/0
- # puts "This code is NOT reached"
+ # puts 'This code is NOT reached'
# end
#
- # puts "This code gets executed and nothing related to ZeroDivisionError was seen"
+ # puts 'This code gets executed and nothing related to ZeroDivisionError was seen'
def suppress(*exception_classes)
begin yield
rescue Exception => e
@@ -62,7 +63,7 @@ module Kernel
# Captures the given stream and returns it:
#
- # stream = capture(:stdout) { puts "Cool" }
+ # stream = capture(:stdout) { puts 'Cool' }
# stream # => "Cool\n"
#
def capture(stream)
diff --git a/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb b/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb
index 33612155fb..9bbf1bbd73 100644
--- a/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/singleton_class.rb
@@ -1,11 +1,4 @@
module Kernel
- # Returns the object's singleton class.
- def singleton_class
- class << self
- self
- end
- end unless respond_to?(:singleton_class) # exists in 1.9.2
-
# class_eval on an object acts like singleton_class.class_eval.
def class_eval(*args, &block)
singleton_class.class_eval(*args, &block)
diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb
index e63a0a9ed9..16fce81445 100644
--- a/activesupport/lib/active_support/core_ext/logger.rb
+++ b/activesupport/lib/active_support/core_ext/logger.rb
@@ -1,4 +1,7 @@
require 'active_support/core_ext/class/attribute_accessors'
+require 'active_support/deprecation'
+
+ActiveSupport::Deprecation.warn 'this file is deprecated and will be removed'
# Adds the 'around_level' method to Logger.
class Logger #:nodoc:
@@ -53,8 +56,8 @@ class Logger
alias :old_datetime_format= :datetime_format=
# Logging date-time format (string passed to +strftime+). Ignored if the formatter
# does not respond to datetime_format=.
- def datetime_format=(datetime_format)
- formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=)
+ def datetime_format=(format)
+ formatter.datetime_format = format if formatter.respond_to?(:datetime_format=)
end
alias :old_datetime_format :datetime_format
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index 9fed346b7c..f2d4887df6 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -5,7 +5,6 @@ require 'active_support/core_ext/module/reachable'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
-require 'active_support/core_ext/module/synchronization'
require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/remove_method'
-require 'active_support/core_ext/module/method_names' \ No newline at end of file
+require 'active_support/core_ext/module/qualified_const'
diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index ce481f0e84..580cb80413 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -26,26 +26,25 @@ class Module
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
yield(aliased_target, punctuation) if block_given?
- with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
+ with_method = "#{aliased_target}_with_#{feature}#{punctuation}"
+ without_method = "#{aliased_target}_without_#{feature}#{punctuation}"
alias_method without_method, target
alias_method target, with_method
case
- when public_method_defined?(without_method)
- public target
- when protected_method_defined?(without_method)
- protected target
- when private_method_defined?(without_method)
- private target
+ when public_method_defined?(without_method)
+ public target
+ when protected_method_defined?(without_method)
+ protected target
+ when private_method_defined?(without_method)
+ private target
end
end
# Allows you to make aliases for attributes, which includes
# getter, setter, and query methods.
#
- # Example:
- #
# class Content < ActiveRecord::Base
# # has a title attribute
# end
diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb
index 3982c9c586..0a9e791030 100644
--- a/activesupport/lib/active_support/core_ext/module/anonymous.rb
+++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/blank'
-
class Module
# A module may or may not have a name.
#
@@ -7,7 +5,7 @@ class Module
# M.name # => "M"
#
# m = Module.new
- # m.name # => ""
+ # m.name # => nil
#
# A module gets a name when it is first assigned to a constant. Either
# via the +module+ or +class+ keyword or by an explicit assignment:
@@ -17,8 +15,6 @@ class Module
# m.name # => "M"
#
def anonymous?
- # Uses blank? because the name of an anonymous class is an empty
- # string in 1.8, and nil in 1.9.
- name.blank?
+ name.nil?
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
index 00db75bfec..db07d549b0 100644
--- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb
+++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb
@@ -15,7 +15,6 @@ class Module
attr_internal_reader(*attrs)
attr_internal_writer(*attrs)
end
-
alias_method :attr_internal, :attr_internal_accessor
class << self; attr_accessor :attr_internal_naming_format end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index be94ae1565..f914425827 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -4,6 +4,7 @@ class Module
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -25,6 +26,7 @@ class Module
def mattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def self.#{sym}=(obj)
@@#{sym} = obj
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 7de824a77f..fbef27c76a 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,5 +1,5 @@
class Module
- # Provides a delegate class method to easily expose contained objects' methods
+ # Provides a delegate class method to easily expose contained objects' public methods
# as your own. Pass one or more methods (specified as symbols or strings)
# and the name of the target object via the <tt>:to</tt> option (also a symbol
# or string). At least one method and the <tt>:to</tt> option are required.
@@ -8,11 +8,11 @@ class Module
#
# class Greeter < ActiveRecord::Base
# def hello
- # "hello"
+ # 'hello'
# end
#
# def goodbye
- # "goodbye"
+ # 'goodbye'
# end
# end
#
@@ -62,7 +62,7 @@ class Module
# delegate :name, :address, :to => :client, :prefix => true
# end
#
- # john_doe = Person.new("John Doe", "Vimmersvej 13")
+ # john_doe = Person.new('John Doe', 'Vimmersvej 13')
# invoice = Invoice.new(john_doe)
# invoice.client_name # => "John Doe"
# invoice.client_address # => "Vimmersvej 13"
@@ -74,45 +74,47 @@ class Module
# end
#
# invoice = Invoice.new(john_doe)
- # invoice.customer_name # => "John Doe"
- # invoice.customer_address # => "Vimmersvej 13"
+ # invoice.customer_name # => 'John Doe'
+ # invoice.customer_address # => 'Vimmersvej 13'
#
# If the delegate object is +nil+ an exception is raised, and that happens
# no matter whether +nil+ responds to the delegated method. You can get a
# +nil+ instead with the +:allow_nil+ option.
#
- # class Foo
- # attr_accessor :bar
- # def initialize(bar = nil)
- # @bar = bar
- # end
- # delegate :zoo, :to => :bar
- # end
+ # class Foo
+ # attr_accessor :bar
+ # def initialize(bar = nil)
+ # @bar = bar
+ # end
+ # delegate :zoo, :to => :bar
+ # end
#
- # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
+ # Foo.new.zoo # raises NoMethodError exception (you called nil.zoo)
#
- # class Foo
- # attr_accessor :bar
- # def initialize(bar = nil)
- # @bar = bar
- # end
- # delegate :zoo, :to => :bar, :allow_nil => true
- # end
+ # class Foo
+ # attr_accessor :bar
+ # def initialize(bar = nil)
+ # @bar = bar
+ # end
+ # delegate :zoo, :to => :bar, :allow_nil => true
+ # end
#
- # Foo.new.zoo # returns nil
+ # Foo.new.zoo # returns nil
#
def delegate(*methods)
options = methods.pop
unless options.is_a?(Hash) && to = options[:to]
- raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter)."
+ raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, :to => :greeter).'
end
- prefix, to, allow_nil = options[:prefix], options[:to], options[:allow_nil]
- if prefix == true && to.to_s =~ /^[^a-z_]/
- raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
+ to = to.to_s
+ prefix, allow_nil = options.values_at(:prefix, :allow_nil)
+
+ if prefix == true && to =~ /^[^a-z_]/
+ raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
end
- method_prefix =
+ method_prefix = \
if prefix
"#{prefix == true ? to : prefix}_"
else
@@ -125,11 +127,15 @@ class Module
methods.each do |method|
method = method.to_s
+ # Attribute writer methods only accept one argument. Makes sure []=
+ # methods still accept two arguments.
+ definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
+
if allow_nil
module_eval(<<-EOS, file, line - 2)
- def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
+ def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
- #{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
+ #{to}.#{method}(#{definition}) # client.name(*args, &block)
end # end
end # end
EOS
@@ -137,8 +143,8 @@ class Module
exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
module_eval(<<-EOS, file, line - 1)
- def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
- #{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
+ def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
+ #{to}.#{method}(#{definition}) # client.name(*args, &block)
rescue NoMethodError # rescue NoMethodError
if #{to}.nil? # if client.nil?
#{exception} # # add helpful message to the exception
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index 5a5b4e3f80..9e77ac3c45 100644
--- a/activesupport/lib/active_support/core_ext/module/deprecation.rb
+++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb
@@ -1,3 +1,5 @@
+require 'active_support/deprecation/method_wrappers'
+
class Module
# Declare that a method has been deprecated.
# deprecate :foo
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index c08ad251dd..3c8e811fa4 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -5,10 +5,11 @@ class Module
#
# M::N.parent_name # => "M"
def parent_name
- unless defined? @parent_name
+ if defined? @parent_name
+ @parent_name
+ else
@parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
end
- @parent_name
end
# Returns the module which contains this one according to its name.
@@ -57,32 +58,23 @@ class Module
parents
end
- if RUBY_VERSION < '1.9'
- # Returns the constants that have been defined locally by this object and
- # not in an ancestor. This method is exact if running under Ruby 1.9. In
- # previous versions it may miss some constants if their definition in some
- # ancestor is identical to their definition in the receiver.
- def local_constants
- inherited = {}
-
- ancestors.each do |anc|
- next if anc == self
- anc.constants.each { |const| inherited[const] = anc.const_get(const) }
- end
-
- constants.select do |const|
- !inherited.key?(const) || inherited[const].object_id != const_get(const).object_id
- end
- end
- else
- def local_constants #:nodoc:
- constants(false)
- end
+ def local_constants #:nodoc:
+ constants(false)
end
- # Returns the names of the constants defined locally rather than the
- # constants themselves. See <tt>local_constants</tt>.
+ # *DEPRECATED*: Use +local_constants+ instead.
+ #
+ # Returns the names of the constants defined locally as strings.
+ #
+ # module M
+ # X = 1
+ # end
+ # M.local_constant_names # => ["X"]
+ #
+ # This method is useful for forward compatibility, since Ruby 1.8 returns
+ # constant names as strings, whereas 1.9 returns them as symbols.
def local_constant_names
+ ActiveSupport::Deprecation.warn 'Module#local_constant_names is deprecated, use Module#local_constants instead', caller
local_constants.map { |c| c.to_s }
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/method_names.rb b/activesupport/lib/active_support/core_ext/module/method_names.rb
deleted file mode 100644
index 2eb40a83ab..0000000000
--- a/activesupport/lib/active_support/core_ext/module/method_names.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-class Module
- if instance_methods[0].is_a?(Symbol)
- def instance_method_names(*args)
- instance_methods(*args).map(&:to_s)
- end
-
- def method_names(*args)
- methods(*args).map(&:to_s)
- end
- else
- alias_method :instance_method_names, :instance_methods
- alias_method :method_names, :methods
- end
-end \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
new file mode 100644
index 0000000000..65525013db
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
@@ -0,0 +1,52 @@
+require 'active_support/core_ext/string/inflections'
+
+#--
+# Allows code reuse in the methods below without polluting Module.
+#++
+module QualifiedConstUtils
+ def self.raise_if_absolute(path)
+ raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
+ end
+
+ def self.names(path)
+ path.split('::')
+ end
+end
+
+##
+# Extends the API for constants to be able to deal with qualified names. Arguments
+# are assumed to be relative to the receiver.
+#
+#--
+# Qualified names are required to be relative because we are extending existing
+# methods that expect constant names, ie, relative paths of length 1. For example,
+# Object.const_get('::String') raises NameError and so does qualified_const_get.
+#++
+class Module
+ def qualified_const_defined?(path, search_parents=true)
+ QualifiedConstUtils.raise_if_absolute(path)
+
+ QualifiedConstUtils.names(path).inject(self) do |mod, name|
+ return unless mod.const_defined?(name, search_parents)
+ mod.const_get(name)
+ end
+ return true
+ end
+
+ def qualified_const_get(path)
+ QualifiedConstUtils.raise_if_absolute(path)
+
+ QualifiedConstUtils.names(path).inject(self) do |mod, name|
+ mod.const_get(name)
+ end
+ end
+
+ def qualified_const_set(path, value)
+ QualifiedConstUtils.raise_if_absolute(path)
+
+ const_name = path.demodulize
+ mod_name = path.deconstantize
+ mod = mod_name.empty? ? self : qualified_const_get(mod_name)
+ mod.const_set(const_name, value)
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb
index b76bc16ee1..719071d1c2 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -1,12 +1,8 @@
class Module
def remove_possible_method(method)
if method_defined?(method) || private_method_defined?(method)
- remove_method(method)
+ undef_method(method)
end
- rescue NameError
- # If the requested method is defined on a superclass or included module,
- # method_defined? returns true but remove_method throws a NameError.
- # Ignore this.
end
def redefine_method(method, &block)
diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb
deleted file mode 100644
index ed16c2f71b..0000000000
--- a/activesupport/lib/active_support/core_ext/module/synchronization.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'thread'
-require 'active_support/core_ext/module/aliasing'
-require 'active_support/core_ext/array/extract_options'
-
-class Module
- # Synchronize access around a method, delegating synchronization to a
- # particular mutex. A mutex (either a Mutex, or any object that responds to
- # #synchronize and yields to a block) must be provided as a final :with option.
- # The :with option should be a symbol or string, and can represent a method,
- # constant, or instance or class variable.
- # Example:
- # class SharedCache
- # @@lock = Mutex.new
- # def expire
- # ...
- # end
- # synchronize :expire, :with => :@@lock
- # end
- def synchronize(*methods)
- options = methods.extract_options!
- unless options.is_a?(Hash) && with = options[:with]
- raise ArgumentError, "Synchronization needs a mutex. Supply an options hash with a :with key as the last argument (e.g. synchronize :hello, :with => :@mutex)."
- end
-
- methods.each do |method|
- aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
-
- if method_defined?("#{aliased_method}_without_synchronization#{punctuation}")
- raise ArgumentError, "#{method} is already synchronized. Double synchronization is not currently supported."
- end
-
- module_eval(<<-EOS, __FILE__, __LINE__ + 1)
- def #{aliased_method}_with_synchronization#{punctuation}(*args, &block) # def expire_with_synchronization(*args, &block)
- #{with}.synchronize do # @@lock.synchronize do
- #{aliased_method}_without_synchronization#{punctuation}(*args, &block) # expire_without_synchronization(*args, &block)
- end # end
- end # end
- EOS
-
- alias_method_chain method, :synchronization
- end
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 58a03d508e..2bf3d1f278 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -8,13 +8,13 @@ class Numeric
# These methods use Time#advance for precise date calculations when using from_now, ago, etc.
# as well as adding or subtracting their results from a Time object. For example:
#
- # # equivalent to Time.now.advance(:months => 1)
+ # # equivalent to Time.current.advance(:months => 1)
# 1.month.from_now
#
- # # equivalent to Time.now.advance(:years => 2)
+ # # equivalent to Time.current.advance(:years => 2)
# 2.years.from_now
#
- # # equivalent to Time.now.advance(:months => 4, :years => 5)
+ # # equivalent to Time.current.advance(:months => 4, :years => 5)
# (4.months + 5.years).from_now
#
# While these methods provide precise calculation when used as in the examples above, care
@@ -28,9 +28,9 @@ class Numeric
# 1.year.to_f.from_now
#
# In such cases, Ruby's core
- # Date[http://stdlib.rubyonrails.org/libdoc/date/rdoc/index.html] and
- # Time[http://stdlib.rubyonrails.org/libdoc/time/rdoc/index.html] should be used for precision
- # date and time arithmetic
+ # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
+ # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
+ # date and time arithmetic.
def seconds
ActiveSupport::Duration.new(self, [[:seconds, self]])
end
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 9ad1e12699..ec2157221f 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -1,6 +1,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/deep_dup'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index fe27f45295..e238fef5a2 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -1,9 +1,8 @@
# encoding: utf-8
-require 'active_support/core_ext/string/encoding'
class Object
# An object is blank if it's false, empty, or a whitespace string.
- # For example, "", " ", +nil+, [], and {} are all blank.
+ # For example, '', ' ', +nil+, [], and {} are all blank.
#
# This simplifies:
#
@@ -89,23 +88,15 @@ class Hash
end
class String
- # 0x3000: fullwidth whitespace
- NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]!
-
# A string is blank if it's empty or contains whitespaces only:
#
- # "".blank? # => true
- # " ".blank? # => true
- # " ".blank? # => true
- # " something here ".blank? # => false
+ # ''.blank? # => true
+ # ' '.blank? # => true
+ # ' '.blank? # => true
+ # ' something here '.blank? # => false
#
def blank?
- # 1.8 does not takes [:space:] properly
- if encoding_aware?
- self !~ /[^[:space:]]/
- else
- self !~ NON_WHITESPACE_REGEXP
- end
+ self !~ /[^[:space:]]/
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
new file mode 100644
index 0000000000..883f5f556c
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -0,0 +1,44 @@
+class Object
+ # Returns a deep copy of object if it's duplicable. If it's
+ # not duplicable, returns +self+.
+ #
+ # object = Object.new
+ # dup = object.deep_dup
+ # dup.instance_variable_set(:@a, 1)
+ #
+ # object.instance_variable_defined?(:@a) #=> false
+ # dup.instance_variable_defined?(:@a) #=> true
+ def deep_dup
+ duplicable? ? dup : self
+ end
+end
+
+class Array
+ # Returns a deep copy of array.
+ #
+ # array = [1, [2, 3]]
+ # dup = array.deep_dup
+ # dup[1][2] = 4
+ #
+ # array[1][2] #=> nil
+ # dup[1][2] #=> 4
+ def deep_dup
+ map { |it| it.deep_dup }
+ end
+end
+
+class Hash
+ # Returns a deep copy of hash.
+ #
+ # hash = { a: { b: 'b' } }
+ # dup = hash.deep_dup
+ # dup[:a][:c] = 'c'
+ #
+ # hash[:a][:c] #=> nil
+ # dup[:a][:c] #=> "c"
+ def deep_dup
+ each_with_object(dup) do |(key, value), hash|
+ hash[key.deep_dup] = value.deep_dup
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 9d044eba71..f1b755c2c4 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbols, numbers, class and module objects;
+ # False for +nil+, +false+, +true+, symbol, and number objects;
# true otherwise.
def duplicable?
true
@@ -81,26 +81,15 @@ class Numeric
end
end
-class Class
- # Classes are not duplicable:
- #
- # c = Class.new # => #<Class:0x10328fd80>
- # c.dup # => #<Class:0x10328fd80>
- #
- # Note +dup+ returned the same class object.
- def duplicable?
- false
- end
-end
+require 'bigdecimal'
+class BigDecimal
+ begin
+ BigDecimal.new('4.56').dup
-class Module
- # Modules are not duplicable:
- #
- # m = Module.new # => #<Module:0x10328b6e0>
- # m.dup # => #<Module:0x10328b6e0>
- #
- # Note +dup+ returned the same module object.
- def duplicable?
- false
+ def duplicable?
+ true
+ end
+ rescue TypeError
+ # can't dup, so use superclass implementation
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index b5671f66d0..3fec465ec0 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -1,15 +1,25 @@
class Object
- # Returns true if this object is included in the argument. Argument must be
- # any object which responds to +#include?+. Usage:
+ # Returns true if this object is included in the argument(s). Argument must be
+ # any object which responds to +#include?+ or optionally, multiple arguments can be passed in. Usage:
#
- # characters = ["Konata", "Kagami", "Tsukasa"]
- # "Konata".in?(characters) # => true
+ # characters = ['Konata', 'Kagami', 'Tsukasa']
+ # 'Konata'.in?(characters) # => true
#
- # This will throw an ArgumentError if the argument doesn't respond
+ # character = 'Konata'
+ # character.in?('Konata', 'Kagami', 'Tsukasa') # => true
+ #
+ # This will throw an ArgumentError if a single argument is passed in and it doesn't 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?")
+ def in?(*args)
+ if args.length > 1
+ args.include? self
+ else
+ another_object = args.first
+ if another_object.respond_to? :include?
+ another_object.include? self
+ else
+ raise ArgumentError.new 'The single parameter passed to #in? must respond to #include?'
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
index eda9694614..40821fd619 100644
--- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb
+++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
@@ -1,6 +1,6 @@
class Object
- # Returns a hash that maps instance variable names without "@" to their
- # corresponding values. Keys are strings both in Ruby 1.8 and 1.9.
+ # Returns a hash with string keys that maps instance variable names without "@" to their
+ # corresponding values.
#
# class C
# def initialize(x, y)
@@ -9,12 +9,11 @@ class Object
# end
#
# C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
- def instance_values #:nodoc:
- Hash[instance_variables.map { |name| [name.to_s[1..-1], instance_variable_get(name)] }]
+ def instance_values
+ Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }]
end
- # Returns an array of instance variable names including "@". They are strings
- # both in Ruby 1.8 and 1.9.
+ # Returns an array of instance variable names including "@".
#
# class C
# def initialize(x, y)
@@ -23,11 +22,7 @@ class Object
# end
#
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
- if RUBY_VERSION >= '1.9'
- def instance_variable_names
- instance_variables.map { |var| var.to_s }
- end
- else
- alias_method :instance_variable_names, :instance_variables
+ def instance_variable_names
+ instance_variables.map { |var| var.to_s }
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb
index 14ef27340e..e7dc60a612 100644
--- a/activesupport/lib/active_support/core_ext/object/to_json.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_json.rb
@@ -10,10 +10,10 @@ end
# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
- klass.class_eval <<-RUBY, __FILE__, __LINE__
+ klass.class_eval do
# Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
def to_json(options = nil)
ActiveSupport::JSON.encode(self, options)
end
- RUBY
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index e77a9da0ec..48eb546a7d 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -1,6 +1,6 @@
class Object
- # Invokes the method identified by the symbol +method+, passing it any arguments
- # and/or the block specified, just like the regular Ruby <tt>Object#send</tt> does.
+ # Invokes the public method identified by the symbol +method+, passing it any arguments
+ # and/or the block specified, just like the regular Ruby <tt>Object#public_send</tt> does.
#
# *Unlike* that method however, a +NoMethodError+ exception will *not* be raised
# and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass.
@@ -24,12 +24,12 @@ class Object
# Without a method argument try will yield to the block unless the receiver is nil.
# @person.try { |p| "#{p.first_name} #{p.last_name}" }
#--
- # +try+ behaves like +Object#send+, unless called on +NilClass+.
+ # +try+ behaves like +Object#public_send+, unless called on +NilClass+.
def try(*a, &b)
if a.empty? && block_given?
yield self
else
- __send__(*a, &b)
+ public_send(*a, &b)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index 1397142c04..e058367111 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -29,7 +29,7 @@ class Object
#
# It can also be used with an explicit receiver:
#
- # I18n.with_options :locale => user.locale, :scope => "newsletter" do |i18n|
+ # I18n.with_options :locale => user.locale, :scope => 'newsletter' do |i18n|
# subject i18n.t :subject
# body i18n.t :body, :user_name => user.name
# end
diff --git a/activesupport/lib/active_support/core_ext/proc.rb b/activesupport/lib/active_support/core_ext/proc.rb
index 94bb5fb0cb..cd63740940 100644
--- a/activesupport/lib/active_support/core_ext/proc.rb
+++ b/activesupport/lib/active_support/core_ext/proc.rb
@@ -1,7 +1,10 @@
require "active_support/core_ext/kernel/singleton_class"
+require "active_support/deprecation"
class Proc #:nodoc:
def bind(object)
+ ActiveSupport::Deprecation.warn 'Proc#bind is deprecated and will be removed in future versions', caller
+
block, time = self, Time.now
object.class_eval do
method_name = "__bind_#{time.to_i}_#{time.usec}"
diff --git a/activesupport/lib/active_support/core_ext/process.rb b/activesupport/lib/active_support/core_ext/process.rb
deleted file mode 100644
index 0b0bc6dc69..0000000000
--- a/activesupport/lib/active_support/core_ext/process.rb
+++ /dev/null
@@ -1 +0,0 @@
-require 'active_support/core_ext/process/daemon'
diff --git a/activesupport/lib/active_support/core_ext/process/daemon.rb b/activesupport/lib/active_support/core_ext/process/daemon.rb
deleted file mode 100644
index f5202ddee4..0000000000
--- a/activesupport/lib/active_support/core_ext/process/daemon.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module Process
- def self.daemon(nochdir = nil, noclose = nil)
- exit if fork # Parent exits, child continues.
- Process.setsid # Become session leader.
- exit if fork # Zap session leader. See [1].
-
- unless nochdir
- Dir.chdir "/" # Release old working directory.
- end
-
- File.umask 0000 # Ensure sensible umask. Adjust as needed.
-
- unless noclose
- STDIN.reopen "/dev/null" # Free file descriptors and
- STDOUT.reopen "/dev/null", "a" # point them somewhere sensible.
- STDERR.reopen '/dev/null', 'a'
- end
-
- trap("TERM") { exit }
-
- return 0
- end unless respond_to?(:daemon)
-end
diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb
index 2428a02242..1d8b1ede5a 100644
--- a/activesupport/lib/active_support/core_ext/range.rb
+++ b/activesupport/lib/active_support/core_ext/range.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/range/blockless_step'
require 'active_support/core_ext/range/conversions'
require 'active_support/core_ext/range/include_range'
require 'active_support/core_ext/range/overlaps'
-require 'active_support/core_ext/range/cover'
diff --git a/activesupport/lib/active_support/core_ext/range/blockless_step.rb b/activesupport/lib/active_support/core_ext/range/blockless_step.rb
deleted file mode 100644
index db42ef5c47..0000000000
--- a/activesupport/lib/active_support/core_ext/range/blockless_step.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-require 'active_support/core_ext/module/aliasing'
-
-class Range
- begin
- (1..2).step
- # Range#step doesn't return an Enumerator
- rescue LocalJumpError
- # Return an array when step is called without a block.
- def step_with_blockless(*args, &block)
- if block_given?
- step_without_blockless(*args, &block)
- else
- array = []
- step_without_blockless(*args) { |step| array << step }
- array
- end
- end
- else
- def step_with_blockless(*args, &block) #:nodoc:
- if block_given?
- step_without_blockless(*args, &block)
- else
- step_without_blockless(*args).to_a
- end
- end
- end
-
- alias_method_chain :step, :blockless
-end
diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb
index 43134b4314..b1a12781f3 100644
--- a/activesupport/lib/active_support/core_ext/range/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/range/conversions.rb
@@ -5,8 +5,6 @@ class Range
# Gives a human readable format of the range.
#
- # ==== Example
- #
# (1..100).to_formatted_s # => "1..100"
def to_formatted_s(format = :default)
if formatter = RANGE_FORMATS[format]
diff --git a/activesupport/lib/active_support/core_ext/range/cover.rb b/activesupport/lib/active_support/core_ext/range/cover.rb
deleted file mode 100644
index 3a182cddd2..0000000000
--- a/activesupport/lib/active_support/core_ext/range/cover.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Range
- alias_method(:cover?, :include?) unless instance_methods.include?(:cover?)
-end
diff --git a/activesupport/lib/active_support/core_ext/range/include_range.rb b/activesupport/lib/active_support/core_ext/range/include_range.rb
index 0246627467..3af66aaf2f 100644
--- a/activesupport/lib/active_support/core_ext/range/include_range.rb
+++ b/activesupport/lib/active_support/core_ext/range/include_range.rb
@@ -5,13 +5,13 @@ class Range
# (1..5).include?(2..6) # => false
#
# The native Range#include? behavior is untouched.
- # ("a".."f").include?("c") # => true
+ # ('a'..'f').include?('c') # => true
# (5..9).include?(11) # => false
def include_with_range?(value)
if value.is_a?(::Range)
- operator = exclude_end? ? :< : :<=
- end_value = value.exclude_end? ? last.succ : last
- include_without_range?(value.first) && (value.last <=> end_value).send(operator, 0)
+ # 1...10 includes 1..9 but it does not include 1..10.
+ operator = exclude_end? && !value.exclude_end? ? :< : :<=
+ include_without_range?(value.first) && value.last.send(operator, last)
else
include_without_range?(value)
end
diff --git a/activesupport/lib/active_support/core_ext/range/overlaps.rb b/activesupport/lib/active_support/core_ext/range/overlaps.rb
index 7df653b53f..603657c180 100644
--- a/activesupport/lib/active_support/core_ext/range/overlaps.rb
+++ b/activesupport/lib/active_support/core_ext/range/overlaps.rb
@@ -3,6 +3,6 @@ class Range
# (1..5).overlaps?(4..6) # => true
# (1..5).overlaps?(7..9) # => false
def overlaps?(other)
- include?(other.first) || other.include?(first)
+ cover?(other.first) || other.cover?(first)
end
end
diff --git a/activesupport/lib/active_support/core_ext/rexml.rb b/activesupport/lib/active_support/core_ext/rexml.rb
deleted file mode 100644
index 0419ebc84b..0000000000
--- a/activesupport/lib/active_support/core_ext/rexml.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'active_support/core_ext/kernel/reporting'
-
-# Fixes the rexml vulnerability disclosed at:
-# http://www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexml/
-# This fix is identical to rexml-expansion-fix version 1.0.1.
-#
-# We still need to distribute this fix because albeit the REXML
-# in recent 1.8.7s is patched, it wasn't in early patchlevels.
-require 'rexml/rexml'
-
-# Earlier versions of rexml defined REXML::Version, newer ones REXML::VERSION
-unless (defined?(REXML::VERSION) ? REXML::VERSION : REXML::Version) > "3.1.7.2"
- silence_warnings { require 'rexml/document' }
-
- # REXML in 1.8.7 has the patch but early patchlevels didn't update Version from 3.1.7.2.
- unless REXML::Document.respond_to?(:entity_expansion_limit=)
- silence_warnings { require 'rexml/entity' }
-
- module REXML #:nodoc:
- class Entity < Child #:nodoc:
- undef_method :unnormalized
- def unnormalized
- document.record_entity_expansion! if document
- v = value()
- return nil if v.nil?
- @unnormalized = Text::unnormalize(v, parent)
- @unnormalized
- end
- end
- class Document < Element #:nodoc:
- @@entity_expansion_limit = 10_000
- def self.entity_expansion_limit= val
- @@entity_expansion_limit = val
- end
-
- def record_entity_expansion!
- @number_of_expansions ||= 0
- @number_of_expansions += 1
- if @number_of_expansions > @@entity_expansion_limit
- raise "Number of entity expansions exceeded, processing aborted."
- end
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb
index 72522d395c..fb36262528 100644
--- a/activesupport/lib/active_support/core_ext/string.rb
+++ b/activesupport/lib/active_support/core_ext/string.rb
@@ -6,9 +6,7 @@ require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/string/access'
require 'active_support/core_ext/string/xchar'
require 'active_support/core_ext/string/behavior'
-require 'active_support/core_ext/string/interpolation'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/exclude'
-require 'active_support/core_ext/string/encoding'
require 'active_support/core_ext/string/strip'
require 'active_support/core_ext/string/inquiry'
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index c0d5cdf2d5..5c32a2453d 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -1,99 +1,106 @@
-require "active_support/multibyte"
+require 'active_support/multibyte'
class String
- unless '1.9'.respond_to?(:force_encoding)
- # Returns the character at the +position+ treating the string as an array (where 0 is the first character).
- #
- # Examples:
- # "hello".at(0) # => "h"
- # "hello".at(4) # => "o"
- # "hello".at(10) # => ERROR if < 1.9, nil in 1.9
- def at(position)
- mb_chars[position, 1].to_s
- end
-
- # Returns the remaining of the string from the +position+ treating the string as an array (where 0 is the first character).
- #
- # Examples:
- # "hello".from(0) # => "hello"
- # "hello".from(2) # => "llo"
- # "hello".from(10) # => "" if < 1.9, nil in 1.9
- def from(position)
- mb_chars[position..-1].to_s
- end
-
- # Returns the beginning of the string up to the +position+ treating the string as an array (where 0 is the first character).
- #
- # Examples:
- # "hello".to(0) # => "h"
- # "hello".to(2) # => "hel"
- # "hello".to(10) # => "hello"
- def to(position)
- mb_chars[0..position].to_s
- end
-
- # Returns the first character of the string or the first +limit+ characters.
- #
- # Examples:
- # "hello".first # => "h"
- # "hello".first(2) # => "he"
- # "hello".first(10) # => "hello"
- def first(limit = 1)
- if limit == 0
- ''
- elsif limit >= size
- self
- else
- mb_chars[0...limit].to_s
- end
- end
-
- # Returns the last character of the string or the last +limit+ characters.
- #
- # Examples:
- # "hello".last # => "o"
- # "hello".last(2) # => "lo"
- # "hello".last(10) # => "hello"
- def last(limit = 1)
- if limit == 0
- ''
- elsif limit >= size
- self
- else
- mb_chars[(-limit)..-1].to_s
- end
- end
- else
- def at(position)
- self[position]
- end
+ # If you pass a single Fixnum, returns a substring of one character at that
+ # position. The first character of the string is at position 0, the next at
+ # position 1, and so on. If a range is supplied, a substring containing
+ # characters at offsets given by the range is returned. In both cases, if an
+ # offset is negative, it is counted from the end of the string. Returns nil
+ # if the initial offset falls outside the string. Returns an empty string if
+ # the beginning of the range is greater than the end of the string.
+ #
+ # str = "hello"
+ # str.at(0) #=> "h"
+ # str.at(1..3) #=> "ell"
+ # str.at(-2) #=> "l"
+ # str.at(-2..-1) #=> "lo"
+ # str.at(5) #=> nil
+ # str.at(5..-1) #=> ""
+ #
+ # If a Regexp is given, the matching portion of the string is returned.
+ # If a String is given, that given string is returned if it occurs in
+ # the string. In both cases, nil is returned if there is no match.
+ #
+ # str = "hello"
+ # str.at(/lo/) #=> "lo"
+ # str.at(/ol/) #=> nil
+ # str.at("lo") #=> "lo"
+ # str.at("ol") #=> nil
+ def at(position)
+ self[position]
+ end
- def from(position)
- self[position..-1]
- end
+ # Returns a substring from the given position to the end of the string.
+ # If the position is negative, it is counted from the end of the string.
+ #
+ # str = "hello"
+ # str.from(0) #=> "hello"
+ # str.from(3) #=> "lo"
+ # str.from(-2) #=> "lo"
+ #
+ # You can mix it with +to+ method and do fun things like:
+ #
+ # str = "hello"
+ # str.from(0).to(-1) #=> "hello"
+ # str.from(1).to(-2) #=> "ell"
+ def from(position)
+ self[position..-1]
+ end
- def to(position)
- self[0..position]
- end
+ # Returns a substring from the beginning of the string to the given position.
+ # If the position is negative, it is counted from the end of the string.
+ #
+ # str = "hello"
+ # str.to(0) #=> "h"
+ # str.to(3) #=> "hell"
+ # str.to(-2) #=> "hell"
+ #
+ # You can mix it with +from+ method and do fun things like:
+ #
+ # str = "hello"
+ # str.from(0).to(-1) #=> "hello"
+ # str.from(1).to(-2) #=> "ell"
+ def to(position)
+ self[0..position]
+ end
- def first(limit = 1)
- if limit == 0
- ''
- elsif limit >= size
- self
- else
- to(limit - 1)
- end
+ # Returns the first character. If a limit is supplied, returns a substring
+ # from the beginning of the string until it reaches the limit value. If the
+ # given limit is greater than or equal to the string length, returns self.
+ #
+ # str = "hello"
+ # str.first #=> "h"
+ # str.first(1) #=> "h"
+ # str.first(2) #=> "he"
+ # str.first(0) #=> ""
+ # str.first(6) #=> "hello"
+ def first(limit = 1)
+ if limit == 0
+ ''
+ elsif limit >= size
+ self
+ else
+ to(limit - 1)
end
+ end
- def last(limit = 1)
- if limit == 0
- ''
- elsif limit >= size
- self
- else
- from(-limit)
- end
+ # Returns the last character of the string. If a limit is supplied, returns a substring
+ # from the end of the string until it reaches the limit value (counting backwards). If
+ # the given limit is greater than or equal to the string length, returns self.
+ #
+ # str = "hello"
+ # str.last #=> "o"
+ # str.last(1) #=> "o"
+ # str.last(2) #=> "lo"
+ # str.last(0) #=> ""
+ # str.last(6) #=> "hello"
+ def last(limit = 1)
+ if limit == 0
+ ''
+ elsif limit >= size
+ self
+ else
+ from(-limit)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 0f8933b658..022b376aec 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -1,54 +1,48 @@
-# encoding: utf-8
require 'date'
-require 'active_support/core_ext/time/publicize_conversion_methods'
require 'active_support/core_ext/time/calculations'
class String
- # Returns the codepoint of the first character of the string, assuming a
- # single-byte character encoding:
- #
- # "a".ord # => 97
- # "à".ord # => 224, in ISO-8859-1
- #
- # This method is defined in Ruby 1.8 for Ruby 1.9 forward compatibility on
- # these character encodings.
- #
- # <tt>ActiveSupport::Multibyte::Chars#ord</tt> is forward compatible with
- # Ruby 1.9 on UTF8 strings:
- #
- # "a".mb_chars.ord # => 97
- # "à".mb_chars.ord # => 224, in UTF8
- #
- # Note that the 224 is different in both examples. In ISO-8859-1 "à" is
- # represented as a single byte, 224. In UTF8 it is represented with two
- # bytes, namely 195 and 160, but its Unicode codepoint is 224. If we
- # call +ord+ on the UTF8 string "à" the return value will be 195. That is
- # not an error, because UTF8 is unsupported, the call itself would be
- # bogus.
- def ord
- self[0]
- end unless method_defined?(:ord)
-
- # +getbyte+ backport from Ruby 1.9
- alias_method :getbyte, :[] unless method_defined?(:getbyte)
-
# Form can be either :utc (default) or :local.
def to_time(form = :utc)
- return nil if self.blank?
- d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset).map { |arg| arg || 0 }
- d[6] *= 1000000
- ::Time.send("#{form}_time", *d[0..6]) - d[7]
+ unless blank?
+ date_values = ::Date._parse(self, false).
+ values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset).
+ map! { |arg| arg || 0 }
+ date_values[6] *= 1000000
+ offset = date_values.pop
+
+ ::Time.send("#{form}_time", *date_values) - offset
+ end
end
+ # Converts a string to a Date value.
+ #
+ # "1-1-2012".to_date #=> Sun, 01 Jan 2012
+ # "01/01/2012".to_date #=> Sun, 01 Jan 2012
+ # "2012-12-13".to_date #=> Thu, 13 Dec 2012
+ # "12/13/2012".to_date #=> ArgumentError: invalid date
def to_date
- return nil if self.blank?
- ::Date.new(*::Date._parse(self, false).values_at(:year, :mon, :mday))
+ unless blank?
+ date_values = ::Date._parse(self, false).values_at(:year, :mon, :mday)
+
+ ::Date.new(*date_values)
+ end
end
+ # Converts a string to a DateTime value.
+ #
+ # "1-1-2012".to_datetime #=> Sun, 01 Jan 2012 00:00:00 +0000
+ # "01/01/2012 23:59:59".to_datetime #=> Sun, 01 Jan 2012 23:59:59 +0000
+ # "2012-12-13 12:50".to_datetime #=> Thu, 13 Dec 2012 12:50:00 +0000
+ # "12/13/2012".to_datetime #=> ArgumentError: invalid date
def to_datetime
- return nil if self.blank?
- d = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction).map { |arg| arg || 0 }
- d[5] += d.pop
- ::DateTime.civil(*d)
+ unless blank?
+ date_values = ::Date._parse(self, false).
+ values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :sec_fraction).
+ map! { |arg| arg || 0 }
+ date_values[5] += date_values.pop
+
+ ::DateTime.civil(*date_values)
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/string/encoding.rb b/activesupport/lib/active_support/core_ext/string/encoding.rb
index d4781bfe0c..dc635ed6a5 100644
--- a/activesupport/lib/active_support/core_ext/string/encoding.rb
+++ b/activesupport/lib/active_support/core_ext/string/encoding.rb
@@ -1,11 +1,8 @@
+require 'active_support/deprecation'
+
class String
- if defined?(Encoding) && "".respond_to?(:encode)
- def encoding_aware?
- true
- end
- else
- def encoding_aware?
- false
- end
+ def encoding_aware?
+ ActiveSupport::Deprecation.warn 'String#encoding_aware? is deprecated', caller
+ true
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/core_ext/string/exclude.rb b/activesupport/lib/active_support/core_ext/string/exclude.rb
index 5e184ec1b3..114bcb87f0 100644
--- a/activesupport/lib/active_support/core_ext/string/exclude.rb
+++ b/activesupport/lib/active_support/core_ext/string/exclude.rb
@@ -1,5 +1,10 @@
class String
- # The inverse of <tt>String#include?</tt>. Returns true if the string does not include the other string.
+ # The inverse of <tt>String#include?</tt>. Returns true if the string
+ # does not include the other string.
+ #
+ # "hello".exclude? "lo" #=> false
+ # "hello".exclude? "ol" #=> true
+ # "hello".exclude? ?h #=> false
def exclude?(string)
!include?(string)
end
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index d478ee0ef6..2478f42290 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -5,7 +5,6 @@ class String
# the string, and then changing remaining consecutive whitespace
# groups into one space each.
#
- # Examples:
# %{ Multi-line
# string }.squish # => "Multi-line string"
# " foo bar \n \t boo".squish # => "foo bar boo"
@@ -22,28 +21,34 @@ class String
# Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
#
- # "Once upon a time in a world far far away".truncate(27)
+ # 'Once upon a time in a world far far away'.truncate(27)
# # => "Once upon a time in a wo..."
#
- # Pass a <tt>:separator</tt> to truncate +text+ at a natural break:
+ # Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural break:
#
- # "Once upon a time in a world far far away".truncate(27, :separator => ' ')
+ # 'Once upon a time in a world far far away'.truncate(27, :separator => ' ')
+ # # => "Once upon a time in a..."
+ #
+ # 'Once upon a time in a world far far away'.truncate(27, :separator => /\s/)
# # => "Once upon a time in a..."
#
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
# for a total length not exceeding <tt>:length</tt>:
#
- # "And they found that many people were sleeping better.".truncate(25, :omission => "... (continued)")
+ # 'And they found that many people were sleeping better.'.truncate(25, :omission => '... (continued)')
# # => "And they f... (continued)"
- def truncate(length, options = {})
- text = self.dup
- options[:omission] ||= "..."
+ def truncate(truncate_at, options = {})
+ return dup unless length > truncate_at
- length_with_room_for_omission = length - options[:omission].mb_chars.length
- chars = text.mb_chars
- stop = options[:separator] ?
- (chars.rindex(options[:separator].mb_chars, length_with_room_for_omission) || length_with_room_for_omission) : length_with_room_for_omission
+ options[:omission] ||= '...'
+ length_with_room_for_omission = truncate_at - options[:omission].length
+ stop = \
+ if options[:separator]
+ rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
+ else
+ length_with_room_for_omission
+ end
- (chars.length > length ? chars[0...stop] + options[:omission] : text).to_s
+ self[0...stop] + options[:omission]
end
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index fd91b3cacb..070bfd7af6 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -4,7 +4,7 @@ require 'active_support/inflector/transliterate'
# String inflections define new methods on the String class to transform names for different purposes.
# For instance, you can figure out the name of a table from the name of a class.
#
-# "ScaleScore".tableize # => "scale_scores"
+# 'ScaleScore'.tableize # => "scale_scores"
#
class String
# Returns the plural form of the word in the string.
@@ -13,15 +13,14 @@ class String
# the singular form will be returned if <tt>count == 1</tt>.
# For any other value of +count+ the plural will be returned.
#
- # ==== Examples
- # "post".pluralize # => "posts"
- # "octopus".pluralize # => "octopi"
- # "sheep".pluralize # => "sheep"
- # "words".pluralize # => "words"
- # "the blue mailman".pluralize # => "the blue mailmen"
- # "CamelOctopus".pluralize # => "CamelOctopi"
- # "apple".pluralize(1) # => "apple"
- # "apple".pluralize(2) # => "apples"
+ # 'post'.pluralize # => "posts"
+ # 'octopus'.pluralize # => "octopi"
+ # 'sheep'.pluralize # => "sheep"
+ # 'words'.pluralize # => "words"
+ # 'the blue mailman'.pluralize # => "the blue mailmen"
+ # 'CamelOctopus'.pluralize # => "CamelOctopi"
+ # 'apple'.pluralize(1) # => "apple"
+ # 'apple'.pluralize(2) # => "apples"
def pluralize(count = nil)
if count == 1
self
@@ -32,12 +31,12 @@ class String
# The reverse of +pluralize+, returns the singular form of a word in a string.
#
- # "posts".singularize # => "post"
- # "octopi".singularize # => "octopus"
- # "sheep".singularize # => "sheep"
- # "word".singularize # => "word"
- # "the blue mailmen".singularize # => "the blue mailman"
- # "CamelOctopi".singularize # => "CamelOctopus"
+ # 'posts'.singularize # => "post"
+ # 'octopi'.singularize # => "octopus"
+ # 'sheep'.singularize # => "sheep"
+ # 'word'.singularize # => "word"
+ # 'the blue mailmen'.singularize # => "the blue mailman"
+ # 'CamelOctopi'.singularize # => "CamelOctopus"
def singularize
ActiveSupport::Inflector.singularize(self)
end
@@ -46,10 +45,9 @@ class String
# in the string. It raises a NameError when the name is not in CamelCase
# or is not initialized. See ActiveSupport::Inflector.constantize
#
- # Examples
- # "Module".constantize # => Module
- # "Class".constantize # => Class
- # "blargle".constantize # => NameError: wrong constant name blargle
+ # 'Module'.constantize # => Module
+ # 'Class'.constantize # => Class
+ # 'blargle'.constantize # => NameError: wrong constant name blargle
def constantize
ActiveSupport::Inflector.constantize(self)
end
@@ -58,10 +56,9 @@ class String
# in the string. It returns nil when the name is not in CamelCase
# or is not initialized. See ActiveSupport::Inflector.safe_constantize
#
- # Examples
- # "Module".safe_constantize # => Module
- # "Class".safe_constantize # => Class
- # "blargle".safe_constantize # => nil
+ # 'Module'.safe_constantize # => Module
+ # 'Class'.safe_constantize # => Class
+ # 'blargle'.safe_constantize # => nil
def safe_constantize
ActiveSupport::Inflector.safe_constantize(self)
end
@@ -71,14 +68,16 @@ class String
#
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
#
- # "active_record".camelize # => "ActiveRecord"
- # "active_record".camelize(:lower) # => "activeRecord"
- # "active_record/errors".camelize # => "ActiveRecord::Errors"
- # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
+ # 'active_record'.camelize # => "ActiveRecord"
+ # 'active_record'.camelize(:lower) # => "activeRecord"
+ # 'active_record/errors'.camelize # => "ActiveRecord::Errors"
+ # 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"
def camelize(first_letter = :upper)
case first_letter
- when :upper then ActiveSupport::Inflector.camelize(self, true)
- when :lower then ActiveSupport::Inflector.camelize(self, false)
+ when :upper
+ ActiveSupport::Inflector.camelize(self, true)
+ when :lower
+ ActiveSupport::Inflector.camelize(self, false)
end
end
alias_method :camelcase, :camelize
@@ -89,8 +88,8 @@ class String
#
# +titleize+ is also aliased as +titlecase+.
#
- # "man from the boondocks".titleize # => "Man From The Boondocks"
- # "x-men: the last stand".titleize # => "X Men: The Last Stand"
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
def titleize
ActiveSupport::Inflector.titleize(self)
end
@@ -100,30 +99,43 @@ class String
#
# +underscore+ will also change '::' to '/' to convert namespaces to paths.
#
- # "ActiveRecord".underscore # => "active_record"
- # "ActiveRecord::Errors".underscore # => active_record/errors
+ # 'ActiveModel'.underscore # => "active_model"
+ # 'ActiveModel::Errors'.underscore # => "active_model/errors"
def underscore
ActiveSupport::Inflector.underscore(self)
end
# Replaces underscores with dashes in the string.
#
- # "puni_puni" # => "puni-puni"
+ # 'puni_puni' # => "puni-puni"
def dasherize
ActiveSupport::Inflector.dasherize(self)
end
# Removes the module part from the constant expression in the string.
#
- # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
- # "Inflections".demodulize # => "Inflections"
+ # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
+ # 'Inflections'.demodulize # => "Inflections"
+ #
+ # See also +deconstantize+.
def demodulize
ActiveSupport::Inflector.demodulize(self)
end
- # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
+ # Removes the rightmost segment from the constant expression in the string.
+ #
+ # 'Net::HTTP'.deconstantize # => "Net"
+ # '::Net::HTTP'.deconstantize # => "::Net"
+ # 'String'.deconstantize # => ""
+ # '::String'.deconstantize # => ""
+ # ''.deconstantize # => ""
#
- # ==== Examples
+ # See also +demodulize+.
+ def deconstantize
+ ActiveSupport::Inflector.deconstantize(self)
+ end
+
+ # Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
#
# class Person
# def to_param
@@ -143,9 +155,9 @@ class String
# Creates the name of a table like Rails does for models to table names. This method
# uses the +pluralize+ method on the last word in the string.
#
- # "RawScaledScorer".tableize # => "raw_scaled_scorers"
- # "egg_and_ham".tableize # => "egg_and_hams"
- # "fancyCategory".tableize # => "fancy_categories"
+ # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
+ # 'egg_and_ham'.tableize # => "egg_and_hams"
+ # 'fancyCategory'.tableize # => "fancy_categories"
def tableize
ActiveSupport::Inflector.tableize(self)
end
@@ -154,12 +166,12 @@ class String
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
- # "egg_and_hams".classify # => "EggAndHam"
- # "posts".classify # => "Post"
+ # 'egg_and_hams'.classify # => "EggAndHam"
+ # 'posts'.classify # => "Post"
#
# Singular names are not handled correctly.
#
- # "business".classify # => "Busines"
+ # 'business'.classify # => "Busines"
def classify
ActiveSupport::Inflector.classify(self)
end
@@ -167,8 +179,8 @@ class String
# Capitalizes the first word, turns underscores into spaces, and strips '_id'.
# Like +titleize+, this is meant for creating pretty output.
#
- # "employee_salary" # => "Employee salary"
- # "author_id" # => "Author"
+ # 'employee_salary' # => "Employee salary"
+ # 'author_id' # => "Author"
def humanize
ActiveSupport::Inflector.humanize(self)
end
@@ -177,10 +189,9 @@ class String
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
#
- # Examples
- # "Message".foreign_key # => "message_id"
- # "Message".foreign_key(false) # => "messageid"
- # "Admin::Post".foreign_key # => "post_id"
+ # 'Message'.foreign_key # => "message_id"
+ # 'Message'.foreign_key(false) # => "messageid"
+ # 'Admin::Post'.foreign_key # => "post_id"
def foreign_key(separate_class_name_and_id_with_underscore = true)
ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
end
diff --git a/activesupport/lib/active_support/core_ext/string/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb
index 5f0a017de6..1dcd949536 100644
--- a/activesupport/lib/active_support/core_ext/string/inquiry.rb
+++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb
@@ -2,9 +2,9 @@ require 'active_support/string_inquirer'
class String
# Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class,
- # which gives you a prettier way to test for equality. Example:
+ # which gives you a prettier way to test for equality.
#
- # env = "production".inquiry
+ # env = 'production'.inquiry
# env.production? # => true
# env.development? # => false
def inquiry
diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb
deleted file mode 100644
index 7f764e9de1..0000000000
--- a/activesupport/lib/active_support/core_ext/string/interpolation.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-require 'active_support/i18n'
-require 'i18n/core_ext/string/interpolate'
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index aae1cfccf2..4e7824ad74 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -2,71 +2,55 @@
require 'active_support/multibyte'
class String
- if RUBY_VERSION >= "1.9"
- # == Multibyte proxy
- #
- # +mb_chars+ is a multibyte safe proxy for string methods.
- #
- # In Ruby 1.8 and older it creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
- # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
- # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
- #
- # name = 'Claus Müller'
- # name.reverse # => "rell??M sualC"
- # name.length # => 13
- #
- # name.mb_chars.reverse.to_s # => "rellüM sualC"
- # name.mb_chars.length # => 12
- #
- # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
- # it becomes easy to run one version of your code on multiple Ruby versions.
- #
- # == Method chaining
- #
- # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
- # method chaining on the result of any of these methods.
- #
- # name.mb_chars.reverse.length # => 12
- #
- # == Interoperability and configuration
- #
- # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
- # String and Char work like expected. The bang! methods change the internal string representation in the Chars
- # object. Interoperability problems can be resolved easily with a +to_s+ call.
- #
- # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
- # information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
- def mb_chars
- if ActiveSupport::Multibyte.proxy_class.consumes?(self)
- ActiveSupport::Multibyte.proxy_class.new(self)
- else
- self
- end
- end
-
- def is_utf8? #:nodoc
- case encoding
- when Encoding::UTF_8
- valid_encoding?
- when Encoding::ASCII_8BIT, Encoding::US_ASCII
- dup.force_encoding(Encoding::UTF_8).valid_encoding?
- else
- false
- end
- end
- else
- def mb_chars
- if ActiveSupport::Multibyte.proxy_class.wants?(self)
- ActiveSupport::Multibyte.proxy_class.new(self)
- else
- self
- end
+ # == Multibyte proxy
+ #
+ # +mb_chars+ is a multibyte safe proxy for string methods.
+ #
+ # In Ruby 1.8 and older it creates and returns an instance of the ActiveSupport::Multibyte::Chars class which
+ # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
+ # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
+ #
+ # name = 'Claus Müller'
+ # name.reverse # => "rell??M sualC"
+ # name.length # => 13
+ #
+ # name.mb_chars.reverse.to_s # => "rellüM sualC"
+ # name.mb_chars.length # => 12
+ #
+ # In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
+ # it becomes easy to run one version of your code on multiple Ruby versions.
+ #
+ # == Method chaining
+ #
+ # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
+ # method chaining on the result of any of these methods.
+ #
+ # name.mb_chars.reverse.length # => 12
+ #
+ # == Interoperability and configuration
+ #
+ # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between
+ # String and Char work like expected. The bang! methods change the internal string representation in the Chars
+ # object. Interoperability problems can be resolved easily with a +to_s+ call.
+ #
+ # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For
+ # information about how to change the default Multibyte behavior see ActiveSupport::Multibyte.
+ def mb_chars
+ if ActiveSupport::Multibyte.proxy_class.consumes?(self)
+ ActiveSupport::Multibyte.proxy_class.new(self)
+ else
+ self
end
+ end
- # Returns true if the string has UTF-8 semantics (a String used for purely byte resources is unlikely to have
- # them), returns false otherwise.
- def is_utf8?
- ActiveSupport::Multibyte::Chars.consumes?(self)
+ def is_utf8?
+ case encoding
+ when Encoding::UTF_8
+ valid_encoding?
+ when Encoding::ASCII_8BIT, Encoding::US_ASCII
+ dup.force_encoding(Encoding::UTF_8).valid_encoding?
+ else
+ false
end
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 5d7f74bb65..5226ff0cbe 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -5,6 +5,8 @@ class ERB
module Util
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;' }
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
+ HTML_ESCAPE_ONCE_REGEXP = /[\"><]|&(?!([a-zA-Z]+|(#\d+));)/
+ JSON_ESCAPE_REGEXP = /[&"><]/
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
@@ -12,15 +14,14 @@ class ERB
# In your ERB templates, use this method to escape any unsafe content. For example:
# <%=h @person.name %>
#
- # ==== Example:
- # puts html_escape("is a > 0 & a < 10?")
+ # puts html_escape('is a > 0 & a < 10?')
# # => is a &gt; 0 &amp; a &lt; 10?
def html_escape(s)
s = s.to_s
if s.html_safe?
s
else
- s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;").html_safe
+ s.encode(s.encoding, :xml => :attr)[1...-1].html_safe
end
end
@@ -33,10 +34,24 @@ class ERB
singleton_class.send(:remove_method, :html_escape)
module_function :html_escape
+ # A utility method for escaping HTML without affecting existing escaped entities.
+ #
+ # html_escape_once('1 < 2 &amp; 3')
+ # # => "1 &lt; 2 &amp; 3"
+ #
+ # html_escape_once('&lt;&lt; Accept & Checkout')
+ # # => "&lt;&lt; Accept &amp; Checkout"
+ def html_escape_once(s)
+ result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP) { |special| HTML_ESCAPE[special] }
+ s.html_safe? ? result.html_safe : result
+ end
+
+ module_function :html_escape_once
+
# A utility method for escaping HTML entities in JSON strings
# using \uXXXX JavaScript escape sequences for string literals:
#
- # json_escape("is a > 0 & a < 10?")
+ # json_escape('is a > 0 & a < 10?')
# # => is a \u003E 0 \u0026 a \u003C 10?
#
# Note that after this operation is performed the output is not
@@ -51,7 +66,7 @@ class ERB
# <%=j @person.to_json %>
#
def json_escape(s)
- result = s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] }
+ result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] }
s.html_safe? ? result.html_safe : result
end
@@ -75,40 +90,55 @@ end
module ActiveSupport #:nodoc:
class SafeBuffer < String
- UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase", "prepend"].freeze
+ UNSAFE_STRING_METHODS = %w(
+ capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
+ slice squeeze strip sub succ swapcase tr tr_s upcase prepend
+ )
alias_method :original_concat, :concat
private :original_concat
class SafeConcatError < StandardError
def initialize
- super "Could not concatenate to the buffer because it is not html safe."
+ super 'Could not concatenate to the buffer because it is not html safe.'
end
end
- def[](*args)
- new_safe_buffer = super
- new_safe_buffer.instance_eval { @dirty = false }
- new_safe_buffer
+ def [](*args)
+ if args.size < 2
+ super
+ else
+ if html_safe?
+ new_safe_buffer = super
+ new_safe_buffer.instance_eval { @html_safe = true }
+ new_safe_buffer
+ else
+ to_str[*args]
+ end
+ end
end
def safe_concat(value)
- raise SafeConcatError if dirty?
+ raise SafeConcatError unless html_safe?
original_concat(value)
end
def initialize(*)
- @dirty = false
+ @html_safe = true
super
end
def initialize_copy(other)
super
- @dirty = other.dirty?
+ @html_safe = other.html_safe?
+ end
+
+ def clone_empty
+ self[0, 0]
end
def concat(value)
- if dirty? || value.html_safe?
+ if !html_safe? || value.html_safe?
super(value)
else
super(ERB::Util.h(value))
@@ -120,8 +150,20 @@ module ActiveSupport #:nodoc:
dup.concat(other)
end
+ def %(args)
+ args = Array(args).map do |arg|
+ if !html_safe? || arg.html_safe?
+ arg
+ else
+ ERB::Util.h(arg)
+ end
+ end
+
+ self.class.new(super(args))
+ end
+
def html_safe?
- !dirty?
+ defined?(@html_safe) && @html_safe
end
def to_s
@@ -136,11 +178,6 @@ module ActiveSupport #:nodoc:
coder.represent_scalar nil, to_str
end
- def to_yaml(*args)
- return super() if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
- to_str.to_yaml(*args)
- end
-
UNSAFE_STRING_METHODS.each do |unsafe_method|
if 'String'.respond_to?(unsafe_method)
class_eval <<-EOT, __FILE__, __LINE__ + 1
@@ -149,18 +186,12 @@ module ActiveSupport #:nodoc:
end # end
def #{unsafe_method}!(*args) # def capitalize!(*args)
- @dirty = true # @dirty = true
+ @html_safe = false # @html_safe = false
super # super
end # end
EOT
end
end
-
- protected
-
- def dirty?
- @dirty
- end
end
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 372dd69212..92b8417150 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -1,10 +1,17 @@
require 'active_support/duration'
-require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/time/conversions'
class Time
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
- DAYS_INTO_WEEK = { :monday => 0, :tuesday => 1, :wednesday => 2, :thursday => 3, :friday => 4, :saturday => 5, :sunday => 6 }
+ DAYS_INTO_WEEK = {
+ :monday => 0,
+ :tuesday => 1,
+ :wednesday => 2,
+ :thursday => 3,
+ :friday => 4,
+ :saturday => 5,
+ :sunday => 6
+ }
class << self
# Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances
@@ -15,8 +22,11 @@ class Time
# Return the number of days in the given month.
# If no year is specified, it will use the current year.
def days_in_month(month, year = now.year)
- return 29 if month == 2 && ::Date.gregorian_leap?(year)
- COMMON_YEAR_DAYS_IN_MONTH[month]
+ if month == 2 && ::Date.gregorian_leap?(year)
+ 29
+ else
+ COMMON_YEAR_DAYS_IN_MONTH[month]
+ end
end
# Returns a new Time if requested year can be accommodated by Ruby's Time class
@@ -24,8 +34,13 @@ class Time
# otherwise returns a DateTime.
def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
time = ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
+
# This check is needed because Time.utc(y) returns a time object in the 2000s for 0 <= y <= 138.
- time.year == year ? time : ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
+ if time.year == year
+ time
+ else
+ ::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
+ end
rescue
::DateTime.civil_from_format(utc_or_local, year, month, day, hour, min, sec)
end
@@ -67,18 +82,18 @@ class Time
end
# Returns a new Time where one or more of the elements have been changed according to the +options+ parameter. The time options
- # (hour, minute, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
+ # (hour, min, sec, usec) reset cascadingly, so if only the hour is passed, then minute, sec, and usec is set to 0. If the hour and
# minute is passed, then sec and usec is set to 0.
def change(options)
::Time.send(
utc? ? :utc_time : :local_time,
- options[:year] || year,
- options[:month] || month,
- options[:day] || day,
- options[:hour] || hour,
- options[:min] || (options[:hour] ? 0 : min),
- options[:sec] || ((options[:hour] || options[:min]) ? 0 : sec),
- options[:usec] || ((options[:hour] || options[:min] || options[:sec]) ? 0 : usec)
+ options.fetch(:year, year),
+ options.fetch(:month, month),
+ options.fetch(:day, day),
+ options.fetch(:hour, hour),
+ options.fetch(:min, options[:hour] ? 0 : min),
+ options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec),
+ options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
)
end
@@ -89,18 +104,26 @@ class Time
def advance(options)
unless options[:weeks].nil?
options[:weeks], partial_weeks = options[:weeks].divmod(1)
- options[:days] = (options[:days] || 0) + 7 * partial_weeks
+ options[:days] = options.fetch(:days, 0) + 7 * partial_weeks
end
unless options[:days].nil?
options[:days], partial_days = options[:days].divmod(1)
- options[:hours] = (options[:hours] || 0) + 24 * partial_days
+ options[:hours] = options.fetch(:hours, 0) + 24 * partial_days
end
d = to_date.advance(options)
time_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day)
- seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600
- seconds_to_advance == 0 ? time_advanced_by_date : time_advanced_by_date.since(seconds_to_advance)
+ seconds_to_advance = \
+ options.fetch(:seconds, 0) +
+ options.fetch(:minutes, 0) * 60 +
+ options.fetch(:hours, 0) * 3600
+
+ if seconds_to_advance.zero?
+ time_advanced_by_date
+ else
+ time_advanced_by_date.since(seconds_to_advance)
+ end
end
# Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension
@@ -145,6 +168,7 @@ class Time
def prev_year
years_ago(1)
end
+ alias_method :last_year, :prev_year
# Short-hand for years_since(1)
def next_year
@@ -155,35 +179,63 @@ class Time
def prev_month
months_ago(1)
end
+ alias_method :last_month, :prev_month
# Short-hand for months_since(1)
def next_month
months_since(1)
end
- # Returns a new Time representing the "start" of this week (Monday, 0:00)
- def beginning_of_week
- days_to_monday = wday!=0 ? wday-1 : 6
- (self - days_to_monday.days).midnight
+ # Returns number of days to start of this week, week starts on start_day (default is :monday).
+ def days_to_week_start(start_day = :monday)
+ start_day_number = DAYS_INTO_WEEK[start_day]
+ current_day_number = wday != 0 ? wday - 1 : 6
+ days_span = current_day_number - start_day_number
+
+ days_span >= 0 ? days_span : 7 + days_span
+ end
+
+ # Returns a new Time representing the "start" of this week, week starts on start_day (default is :monday, i.e. Monday, 0:00).
+ def beginning_of_week(start_day = :monday)
+ days_to_start = days_to_week_start(start_day)
+ (self - days_to_start.days).midnight
end
- alias :monday :beginning_of_week
alias :at_beginning_of_week :beginning_of_week
- # Returns a new Time representing the end of this week, (end of Sunday)
- def end_of_week
- days_to_sunday = wday!=0 ? 7-wday : 0
- (self + days_to_sunday.days).end_of_day
+ # Returns a new +Date+/+DateTime+ representing the start of this week. Week is
+ # assumed to start on a Monday. +DateTime+ objects have their time set to 0:00.
+ def monday
+ beginning_of_week
+ end
+
+ # Returns a new Time representing the end of this week, week starts on start_day (default is :monday, i.e. end of Sunday).
+ def end_of_week(start_day = :monday)
+ days_to_end = 6 - days_to_week_start(start_day)
+ (self + days_to_end.days).end_of_day
end
alias :at_end_of_week :end_of_week
- # Returns a new Time representing the start of the given day in the previous week (default is Monday).
+ # Returns a new +Date+/+DateTime+ representing the end of this week. Week is
+ # assumed to start on a Monday. +DateTime+ objects have their time set to 23:59:59.
+ def sunday
+ end_of_week
+ end
+
+ # Returns a new Time representing the start of the given day in the previous week (default is :monday).
def prev_week(day = :monday)
- ago(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0)
+ ago(1.week).
+ beginning_of_week.
+ since(DAYS_INTO_WEEK[day].day).
+ change(:hour => 0)
end
+ alias_method :last_week, :prev_week
- # Returns a new Time representing the start of the given day in next week (default is Monday).
+ # Returns a new Time representing the start of the given day in next week (default is :monday).
def next_week(day = :monday)
- since(1.week).beginning_of_week.since(DAYS_INTO_WEEK[day].day).change(:hour => 0)
+ since(1.week).
+ beginning_of_week.
+ since(DAYS_INTO_WEEK[day].day).
+ change(:hour => 0)
end
# Returns a new Time representing the start of the day (0:00)
@@ -197,7 +249,27 @@ class Time
# Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
def end_of_day
- change(:hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
+ change(
+ :hour => 23,
+ :min => 59,
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
+ end
+
+ # Returns a new Time representing the start of the hour (x:00)
+ def beginning_of_hour
+ change(:min => 0)
+ end
+ alias :at_beginning_of_hour :beginning_of_hour
+
+ # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9)
+ def end_of_hour
+ change(
+ :min => 59,
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
end
# Returns a new Time representing the start of the month (1st of the month, 0:00)
@@ -211,19 +283,27 @@ class Time
def end_of_month
#self - ((self.mday-1).days + self.seconds_since_midnight)
last_day = ::Time.days_in_month(month, year)
- change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
+ change(
+ :day => last_day,
+ :hour => 23,
+ :min => 59,
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
end
alias :at_end_of_month :end_of_month
# Returns a new Time representing the start of the quarter (1st of january, april, july, october, 0:00)
def beginning_of_quarter
- beginning_of_month.change(:month => [10, 7, 4, 1].detect { |m| m <= month })
+ first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
+ beginning_of_month.change(:month => first_quarter_month)
end
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new Time representing the end of the quarter (end of the last day of march, june, september, december)
def end_of_quarter
- beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= month }).end_of_month
+ last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
+ beginning_of_month.change(:month => last_quarter_month).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
@@ -235,7 +315,14 @@ class Time
# Returns a new Time representing the end of the year (end of the 31st of december)
def end_of_year
- change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
+ change(
+ :month => 12,
+ :day => 31,
+ :hour => 23,
+ :min => 59,
+ :sec => 59,
+ :usec => Rational(999999999, 1000)
+ )
end
alias :at_end_of_year :end_of_year
@@ -254,9 +341,9 @@ class Time
beginning_of_day..end_of_day
end
- # Returns a Range representing the whole week of the current time.
- def all_week
- beginning_of_week..end_of_week
+ # Returns a Range representing the whole week of the current time. Week starts on start_day (default is :monday, i.e. end of Sunday).
+ def all_week(start_day = :monday)
+ beginning_of_week(start_day)..end_of_week(start_day)
end
# Returns a Range representing the whole month of the current time.
@@ -308,8 +395,23 @@ class Time
# can be chronologically compared with a Time
def compare_with_coercion(other)
# we're avoiding Time#to_datetime cause it's expensive
- other.is_a?(Time) ? compare_without_coercion(other.to_time) : to_datetime <=> other
+ if other.is_a?(Time)
+ compare_without_coercion(other.to_time)
+ else
+ to_datetime <=> other
+ end
end
alias_method :compare_without_coercion, :<=>
alias_method :<=>, :compare_with_coercion
+
+ # Layers additional behavior on Time#eql? so that ActiveSupport::TimeWithZone instances
+ # can be eql? to an equivalent Time
+ def eql_with_coercion(other)
+ # if other is an ActiveSupport::TimeWithZone, coerce a Time instance from it so we can do eql? comparison
+ other = other.comparable_time if other.respond_to?(:comparable_time)
+ eql_without_coercion(other)
+ end
+ alias_method :eql_without_coercion, :eql?
+ alias_method :eql?, :eql_with_coercion
+
end
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index 49ac18d245..10ca26acf2 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -1,16 +1,22 @@
require 'active_support/inflector/methods'
-require 'active_support/core_ext/time/publicize_conversion_methods'
require 'active_support/values/time_zone'
class Time
DATE_FORMATS = {
- :db => "%Y-%m-%d %H:%M:%S",
- :number => "%Y%m%d%H%M%S",
- :time => "%H:%M",
- :short => "%d %b %H:%M",
- :long => "%B %d, %Y %H:%M",
- :long_ordinal => lambda { |time| time.strftime("%B #{ActiveSupport::Inflector.ordinalize(time.day)}, %Y %H:%M") },
- :rfc822 => lambda { |time| time.strftime("%a, %d %b %Y %H:%M:%S #{time.formatted_offset(false)}") }
+ :db => '%Y-%m-%d %H:%M:%S',
+ :number => '%Y%m%d%H%M%S',
+ :nsec => '%Y%m%d%H%M%S%9N',
+ :time => '%H:%M',
+ :short => '%d %b %H:%M',
+ :long => '%B %d, %Y %H:%M',
+ :long_ordinal => lambda { |time|
+ day_format = ActiveSupport::Inflector.ordinalize(time.day)
+ time.strftime("%B #{day_format}, %Y %H:%M")
+ },
+ :rfc822 => lambda { |time|
+ offset_format = time.formatted_offset(false)
+ time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}")
+ }
}
# Converts to a formatted string. See DATE_FORMATS for builtin formats.
@@ -35,7 +41,7 @@ class Time
# or Proc instance that takes a time argument as the value.
#
# # config/initializers/time_formats.rb
- # Time::DATE_FORMATS[:month_and_year] = "%B %Y"
+ # Time::DATE_FORMATS[:month_and_year] = '%B %Y'
# Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") }
def to_formatted_s(format = :default)
if formatter = DATE_FORMATS[format]
@@ -54,32 +60,4 @@ class Time
def formatted_offset(colon = true, alternate_utc_string = nil)
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
-
- # Converts a Time object to a Date, dropping hour, minute, and second precision.
- #
- # my_time = Time.now # => Mon Nov 12 22:59:51 -0500 2007
- # my_time.to_date # => Mon, 12 Nov 2007
- #
- # your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009
- # your_time.to_date # => Tue, 13 Jan 2009
- def to_date
- ::Date.new(year, month, day)
- end unless method_defined?(:to_date)
-
- # A method to keep Time, Date and DateTime instances interchangeable on conversions.
- # In this case, it simply returns +self+.
- def to_time
- self
- end unless method_defined?(:to_time)
-
- # Converts a Time instance to a Ruby DateTime instance, preserving UTC offset.
- #
- # my_time = Time.now # => Mon Nov 12 23:04:21 -0500 2007
- # my_time.to_datetime # => Mon, 12 Nov 2007 23:04:21 -0500
- #
- # your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009
- # your_time.to_datetime # => Tue, 13 Jan 2009 13:13:03 -0500
- def to_datetime
- ::DateTime.civil(year, month, day, hour, min, sec, Rational(utc_offset, 86400))
- end unless method_defined?(:to_datetime)
end
diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb
index 457d3f5b62..1bf622d6a6 100644
--- a/activesupport/lib/active_support/core_ext/time/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/time/marshal.rb
@@ -1,30 +1,3 @@
-# Pre-1.9 versions of Ruby have a bug with marshaling Time instances, where utc instances are
-# unmarshalled in the local zone, instead of utc. We're layering behavior on the _dump and _load
-# methods so that utc instances can be flagged on dump, and coerced back to utc on load.
-if !Marshal.load(Marshal.dump(Time.now.utc)).utc?
- class Time
- class << self
- alias_method :_load_without_utc_flag, :_load
- def _load(marshaled_time)
- time = _load_without_utc_flag(marshaled_time)
- time.instance_eval do
- if defined?(@marshal_with_utc_coercion)
- val = remove_instance_variable("@marshal_with_utc_coercion")
- end
- val ? utc : self
- end
- end
- end
-
- alias_method :_dump_without_utc_flag, :_dump
- def _dump(*args)
- obj = dup
- obj.instance_variable_set('@marshal_with_utc_coercion', utc?)
- obj._dump_without_utc_flag(*args)
- end
- end
-end
-
# Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only
# preserves utc_offset. Preserve zone also, even though it may not
# work in some edge cases.
diff --git a/activesupport/lib/active_support/core_ext/time/publicize_conversion_methods.rb b/activesupport/lib/active_support/core_ext/time/publicize_conversion_methods.rb
deleted file mode 100644
index e1878d3c20..0000000000
--- a/activesupport/lib/active_support/core_ext/time/publicize_conversion_methods.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'date'
-
-class Time
- # Ruby 1.8-cvs and early 1.9 series define private Time#to_date
- %w(to_date to_datetime).each do |method|
- if private_instance_methods.include?(method) || private_instance_methods.include?(method.to_sym)
- public method
- end
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 0c5962858e..e48866abe3 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/time/calculations'
require 'active_support/time_with_zone'
class Time
@@ -50,13 +51,21 @@ class Time
# 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)
+ if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
+ time_zone
+ else
+ # 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
+ if time_zone.is_a?(ActiveSupport::TimeZone)
+ time_zone
+ else
+ ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
+ end
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
@@ -79,8 +88,10 @@ class Time
#
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
- return self unless zone
-
- ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ if zone
+ ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone))
+ else
+ self
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb
index ee991e3439..bfe0832b37 100644
--- a/activesupport/lib/active_support/core_ext/uri.rb
+++ b/activesupport/lib/active_support/core_ext/uri.rb
@@ -1,22 +1,18 @@
# encoding: utf-8
-if RUBY_VERSION >= '1.9'
- require 'uri'
+require 'uri'
+str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
+parser = URI::Parser.new
- str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
-
- parser = URI::Parser.new
-
- unless str == parser.unescape(parser.escape(str))
- URI::Parser.class_eval do
- remove_method :unescape
- def unescape(str, escaped = /%[a-fA-F\d]{2}/)
- # TODO: Are we actually sure that ASCII == UTF-8?
- # YK: My initial experiments say yes, but let's be sure please
- enc = str.encoding
- enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
- str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc)
- end
+unless str == parser.unescape(parser.escape(str))
+ URI::Parser.class_eval do
+ remove_method :unescape
+ def unescape(str, escaped = /%[a-fA-F\d]{2}/)
+ # TODO: Are we actually sure that ASCII == UTF-8?
+ # YK: My initial experiments say yes, but let's be sure please
+ enc = str.encoding
+ enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
+ str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc)
end
end
end
@@ -24,7 +20,7 @@ end
module URI
class << self
def parser
- @parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI
+ @parser ||= URI::Parser.new
end
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index c072a8e646..66f3af7002 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -5,6 +5,7 @@ require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/module/anonymous'
+require 'active_support/core_ext/module/qualified_const'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
@@ -70,14 +71,24 @@ module ActiveSupport #:nodoc:
#
# This is handled by walking back up the watch stack and adding the constants
# found by child.rb to the list of original constants in parent.rb
- class WatchStack < Hash
+ class WatchStack
+ include Enumerable
+
# @watching is a stack of lists of constants being watched. For instance,
# if parent.rb is autoloaded, the stack will look like [[Object]]. If parent.rb
# then requires namespace/child.rb, the stack will look like [[Object], [Namespace]].
def initialize
@watching = []
- super { |h,k| h[k] = [] }
+ @stack = Hash.new { |h,k| h[k] = [] }
+ end
+
+ def each(&block)
+ @stack.each(&block)
+ end
+
+ def watching?
+ !@watching.empty?
end
# return a list of new constants found since the last call to watch_namespaces
@@ -88,20 +99,20 @@ module ActiveSupport #:nodoc:
@watching.last.each do |namespace|
# Retrieve the constants that were present under the namespace when watch_namespaces
# was originally called
- original_constants = self[namespace].last
+ original_constants = @stack[namespace].last
mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace)
next unless mod.is_a?(Module)
# Get a list of the constants that were added
- new_constants = mod.local_constant_names - original_constants
+ new_constants = mod.local_constants - original_constants
# self[namespace] returns an Array of the constants that are being evaluated
# for that namespace. For instance, if parent.rb requires child.rb, the first
# element of self[Object] will be an Array of the constants that were present
# before parent.rb was required. The second element will be an Array of the
# constants that were present before child.rb was required.
- self[namespace].each do |namespace_constants|
+ @stack[namespace].each do |namespace_constants|
namespace_constants.concat(new_constants)
end
@@ -118,20 +129,19 @@ module ActiveSupport #:nodoc:
# Add a set of modules to the watch stack, remembering the initial constants
def watch_namespaces(namespaces)
- watching = []
- namespaces.map do |namespace|
+ @watching << namespaces.map do |namespace|
module_name = Dependencies.to_constant_name(namespace)
original_constants = Dependencies.qualified_const_defined?(module_name) ?
- Inflector.constantize(module_name).local_constant_names : []
+ Inflector.constantize(module_name).local_constants : []
- watching << module_name
- self[module_name] << original_constants
+ @stack[module_name] << original_constants
+ module_name
end
- @watching << watching
end
+ private
def pop_modules(modules)
- modules.each { |mod| self[mod].pop }
+ modules.each { |mod| @stack[mod].pop }
end
end
@@ -218,8 +228,8 @@ module ActiveSupport #:nodoc:
end
def load_dependency(file)
- if Dependencies.load?
- Dependencies.new_constants_in(Object) { yield }.presence
+ if Dependencies.load? && ActiveSupport::Dependencies.constant_watch_stack.watching?
+ Dependencies.new_constants_in(Object) { yield }
else
yield
end
@@ -228,13 +238,13 @@ module ActiveSupport #:nodoc:
raise
end
- def load(file, *)
+ def load(file, wrap = false)
result = false
load_dependency(file) { result = super }
result
end
- def require(file, *)
+ def require(file)
result = false
load_dependency(file) { result = super }
result
@@ -353,29 +363,12 @@ module ActiveSupport #:nodoc:
# Record history *after* loading so first load gets warnings.
history << expanded
- return result
+ result
end
# Is the provided constant path defined?
def qualified_const_defined?(path)
- names = path.sub(/^::/, '').to_s.split('::')
-
- names.inject(Object) do |mod, name|
- return false unless local_const_defined?(mod, name)
- mod.const_get name
- end
- end
-
- if Module.method(:const_defined?).arity == 1
- # Does this module define this constant?
- # Wrapper to accommodate changing Module#const_defined? in Ruby 1.9
- def local_const_defined?(mod, const)
- mod.const_defined?(const)
- end
- else
- def local_const_defined?(mod, const) #:nodoc:
- mod.const_defined?(const, false)
- end
+ Object.qualified_const_defined?(path.sub(/^::/, ''), false)
end
# Given +path+, a filesystem path to a ruby file, return an array of constant
@@ -426,7 +419,7 @@ module ActiveSupport #:nodoc:
end
# Attempt to autoload the provided module name by searching for a directory
- # matching the expect path suffix. If found, the module is created and assigned
+ # matching the expected path suffix. If found, the module is created and assigned
# to +into+'s constants with the name +const_name+. Provided that the directory
# was loaded from a reloadable base path, it is added to the set of constants
# that are to be unloaded.
@@ -435,7 +428,7 @@ module ActiveSupport #:nodoc:
mod = Module.new
into.const_set const_name, mod
autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path)
- return mod
+ mod
end
# Load the file at the provided path. +const_paths+ is a set of qualified
@@ -459,7 +452,7 @@ module ActiveSupport #:nodoc:
autoloaded_constants.concat newly_defined_paths unless load_once_path?(path)
autoloaded_constants.uniq!
log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
- return result
+ result
end
# Return the constant path for the provided parent and constant name.
@@ -478,7 +471,7 @@ module ActiveSupport #:nodoc:
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
end
- raise NameError, "#{from_mod} is not missing constant #{const_name}!" if local_const_defined?(from_mod, const_name)
+ raise NameError, "#{from_mod} is not missing constant #{const_name}!" if from_mod.const_defined?(const_name, false)
qualified_name = qualified_name_for from_mod, const_name
path_suffix = qualified_name.underscore
@@ -487,12 +480,12 @@ module ActiveSupport #:nodoc:
if file_path && ! loaded.include?(File.expand_path(file_path)) # We found a matching file to load
require_or_load file_path
- raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless local_const_defined?(from_mod, const_name)
+ raise LoadError, "Expected #{file_path} to define #{qualified_name}" unless from_mod.const_defined?(const_name, false)
return from_mod.const_get(const_name)
elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
return mod
elsif (parent = from_mod.parent) && parent != from_mod &&
- ! from_mod.parents.any? { |p| local_const_defined?(p, const_name) }
+ ! from_mod.parents.any? { |p| p.const_defined?(const_name, false) }
# If our parents do not have a constant named +const_name+ then we are free
# to attempt to load upwards. If they do have such a constant, then this
# const_missing must be due to from_mod::const_name, which should not
@@ -506,7 +499,7 @@ module ActiveSupport #:nodoc:
raise NameError,
"uninitialized constant #{qualified_name}",
- caller.reject {|l| l.starts_with? __FILE__ }
+ caller.reject { |l| l.starts_with? __FILE__ }
end
# Remove the constants that have been autoloaded, and those that have been
@@ -525,7 +518,7 @@ module ActiveSupport #:nodoc:
class ClassCache
def initialize
- @store = Hash.new { |h, k| h[k] = Inflector.constantize(k) }
+ @store = Hash.new
end
def empty?
@@ -536,23 +529,21 @@ module ActiveSupport #:nodoc:
@store.key?(key)
end
- def []=(key, value)
- return unless key.respond_to?(:name)
-
- raise(ArgumentError, 'anonymous classes cannot be cached') if key.name.blank?
-
- @store[key.name] = value
+ def get(key)
+ key = key.name if key.respond_to?(:name)
+ @store[key] ||= Inflector.constantize(key)
end
+ alias :[] :get
- def [](key)
+ def safe_get(key)
key = key.name if key.respond_to?(:name)
-
- @store[key]
+ @store[key] ||= Inflector.safe_constantize(key)
end
- alias :get :[]
- def store(name)
- self[name] = name
+ def store(klass)
+ return self unless klass.respond_to?(:name)
+ raise(ArgumentError, 'anonymous classes cannot be cached') if klass.name.empty?
+ @store[klass.name] = klass
self
end
@@ -569,10 +560,17 @@ module ActiveSupport #:nodoc:
end
# Get the reference for class named +name+.
+ # Raises an exception if referenced class does not exist.
def constantize(name)
Reference.get(name)
end
+ # Get the reference for class named +name+ if one exists.
+ # Otherwise returns nil.
+ def safe_constantize(name)
+ Reference.safe_get(name)
+ end
+
# Determine if the given constant has been automatically loaded.
def autoloaded?(desc)
# No name => anonymous module.
@@ -593,10 +591,10 @@ module ActiveSupport #:nodoc:
def mark_for_unload(const_desc)
name = to_constant_name const_desc
if explicitly_unloadable_constants.include? name
- return false
+ false
else
explicitly_unloadable_constants << name
- return true
+ true
end
end
@@ -624,10 +622,10 @@ module ActiveSupport #:nodoc:
return new_constants unless aborting
log "Error during loading, removing partially loaded constants "
- new_constants.each {|c| remove_constant(c) }.clear
+ new_constants.each { |c| remove_constant(c) }.clear
end
- return []
+ []
end
# Convert the provided const desc to a qualified constant name (as a string).
@@ -656,7 +654,7 @@ module ActiveSupport #:nodoc:
constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
parent.instance_eval { remove_const to_remove }
- return true
+ true
end
protected
diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb
index 4c771da096..a1626ebeba 100644
--- a/activesupport/lib/active_support/dependencies/autoload.rb
+++ b/activesupport/lib/active_support/dependencies/autoload.rb
@@ -9,13 +9,16 @@ module ActiveSupport
@@eager_autoload = false
def autoload(const_name, path = @@at_path)
- full = [self.name, @@under_path, const_name.to_s, path].compact.join("::")
- location = path || Inflector.underscore(full)
+ unless path
+ full = [name, @@under_path, const_name.to_s, path].compact.join("::")
+ path = Inflector.underscore(full)
+ end
if @@eager_autoload
- @@autoloads[const_name] = location
+ @@autoloads[const_name] = path
end
- super const_name, location
+
+ super const_name, path
end
def autoload_under(path)
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 45b9dda5ca..176edefa42 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/module/deprecation'
require 'active_support/deprecation/behaviors'
require 'active_support/deprecation/reporting'
require 'active_support/deprecation/method_wrappers'
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index f9505a247c..fc962dcb57 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,5 +1,4 @@
require "active_support/notifications"
-require "active_support/core_ext/array/wrap"
module ActiveSupport
module Deprecation
@@ -7,19 +6,33 @@ module ActiveSupport
# Whether to print a backtrace along with the warning.
attr_accessor :debug
- # Returns the set behavior or if one isn't set, defaults to +:stderr+
+ # Returns the current behavior or if one isn't set, defaults to +:stderr+
def behavior
@behavior ||= [DEFAULT_BEHAVIORS[:stderr]]
end
- # Sets the behavior to the specified value. Can be a single value or an array.
+ # Sets the behavior to the specified value. Can be a single value, array, or
+ # an object that responds to +call+.
#
- # Examples
+ # Available behaviors:
+ #
+ # [+stderr+] Log all deprecation warnings to <tt>$stderr</tt>.
+ # [+log+] Log all deprecation warnings to +Rails.logger+.
+ # [+notify+] Use <tt>ActiveSupport::Notifications</tt> to notify +deprecation.rails+.
+ # [+silence+] Do nothing.
+ #
+ # Setting behaviors only affects deprecations that happen after boot time.
+ # Deprecation warnings raised by gems are not affected by this setting because
+ # they happen before Rails boots up.
#
# ActiveSupport::Deprecation.behavior = :stderr
# ActiveSupport::Deprecation.behavior = [:stderr, :log]
+ # ActiveSupport::Deprecation.behavior = MyCustomHandler
+ # ActiveSupport::Deprecation.behavior = proc { |message, callstack|
+ # # custom stuff
+ # }
def behavior=(behavior)
- @behavior = Array.wrap(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
+ @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
end
end
@@ -34,16 +47,17 @@ module ActiveSupport
if defined?(Rails) && Rails.logger
Rails.logger
else
- require 'logger'
- Logger.new($stderr)
+ require 'active_support/logger'
+ ActiveSupport::Logger.new($stderr)
end
logger.warn message
logger.debug callstack.join("\n ") if debug
},
:notify => Proc.new { |message, callstack|
ActiveSupport::Notifications.instrument("deprecation.rails",
- :message => message, :callstack => callstack)
- }
+ :message => message, :callstack => callstack)
+ },
+ :silence => Proc.new { |message, callstack| }
}
end
end
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index d0d8b577b3..c5de5e6a95 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -1,11 +1,10 @@
-require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/array/extract_options'
module ActiveSupport
- class << Deprecation
+ module Deprecation
# Declare that a method has been deprecated.
- def deprecate_methods(target_module, *method_names)
+ def self.deprecate_methods(target_module, *method_names)
options = method_names.extract_options!
method_names += options.keys
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 89b0923882..2cdc991120 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -5,12 +5,10 @@ require 'active_support/core_ext/object/acts_like'
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
# Time#advance, respectively. It mainly supports the methods on Numeric.
- # Example:
#
# 1.month.ago # equivalent to Time.now.advance(:months => -1)
class Duration < BasicObject
attr_accessor :value, :parts
- delegate :duplicable?, :to => :value # required when using ActiveSupport's BasicObject on 1.8
def initialize(value, parts) #:nodoc:
@value, @parts = value, parts
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index f76ddff038..8860636168 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -1,8 +1,25 @@
module ActiveSupport
- # This class is responsible to track files and invoke the given block
- # whenever one of these files are changed. For example, this class
- # is used by Rails to reload the I18n framework whenever they are
- # changed upon a new request.
+ # \FileUpdateChecker specifies the API used by Rails to watch files
+ # and control reloading. The API depends on four methods:
+ #
+ # * +initialize+ which expects two parameters and one block as
+ # described below;
+ #
+ # * +updated?+ which returns a boolean if there were updates in
+ # the filesystem or not;
+ #
+ # * +execute+ which executes the given block on initialization
+ # and updates the latest watched files and timestamp;
+ #
+ # * +execute_if_updated+ which just executes the block if it was updated;
+ #
+ # After initialization, a call to +execute_if_updated+ must execute
+ # the block only if there was really a change in the filesystem.
+ #
+ # == Examples
+ #
+ # This class is used by Rails to reload the I18n framework whenever
+ # they are changed upon a new request.
#
# i18n_reloader = ActiveSupport::FileUpdateChecker.new(paths) do
# I18n.reload!
@@ -13,24 +30,109 @@ module ActiveSupport
# end
#
class FileUpdateChecker
- attr_reader :paths, :last_update_at
-
- def initialize(paths, calculate=false, &block)
- @paths = paths
+ # It accepts two parameters on initialization. The first is an array
+ # of files and the second is an optional hash of directories. The hash must
+ # have directories as keys and the value is an array of extensions to be
+ # watched under that directory.
+ #
+ # This method must also receive a block that will be called once a path changes.
+ #
+ # == Implementation details
+ #
+ # This particular implementation checks for added, updated, and removed
+ # files. Directories lookup are compiled to a glob for performance.
+ # Therefore, while someone can add new files to the +files+ array after
+ # initialization (and parts of Rails do depend on this feature), adding
+ # new directories after initialization is not supported.
+ #
+ # Notice that other objects that implement the FileUpdateChecker API may
+ # not even allow new files to be added after initialization. If this
+ # is the case, we recommend freezing the +files+ after initialization to
+ # avoid changes that won't make effect.
+ def initialize(files, dirs={}, &block)
+ @files = files
+ @glob = compile_glob(dirs)
@block = block
- @last_update_at = calculate ? updated_at : nil
+
+ @watched = nil
+ @updated_at = nil
+
+ @last_watched = watched
+ @last_update_at = updated_at(@last_watched)
end
- def updated_at
- paths.map { |path| File.mtime(path) }.max
+ # Check if any of the entries were updated. If so, the watched and/or
+ # updated_at values are cached until the block is executed via +execute+
+ # or +execute_if_updated+
+ def updated?
+ current_watched = watched
+ if @last_watched.size != current_watched.size
+ @watched = current_watched
+ true
+ else
+ current_updated_at = updated_at(current_watched)
+ if @last_update_at < current_updated_at
+ @watched = current_watched
+ @updated_at = current_updated_at
+ true
+ else
+ false
+ end
+ end
end
+ # Executes the given block and updates the latest watched files and timestamp.
+ def execute
+ @last_watched = watched
+ @last_update_at = updated_at(@last_watched)
+ @block.call
+ ensure
+ @watched = nil
+ @updated_at = nil
+ end
+
+ # Execute the block given if updated.
def execute_if_updated
- current_update_at = self.updated_at
- if @last_update_at != current_update_at
- @last_update_at = current_update_at
- @block.call
+ if updated?
+ execute
+ true
+ else
+ false
end
end
+
+ private
+
+ def watched
+ @watched || begin
+ all = @files.select { |f| File.exists?(f) }
+ all.concat(Dir[@glob]) if @glob
+ all
+ end
+ end
+
+ def updated_at(paths)
+ @updated_at || paths.map { |path| File.mtime(path) }.max || Time.at(0)
+ end
+
+ def compile_glob(hash)
+ hash.freeze # Freeze so changes aren't accidently pushed
+ return if hash.empty?
+
+ globs = hash.map do |key, value|
+ "#{escape(key)}/**/*#{compile_ext(value)}"
+ end
+ "{#{globs.join(",")}}"
+ end
+
+ def escape(key)
+ key.gsub(',','\,')
+ end
+
+ def compile_ext(array)
+ array = Array(array)
+ return if array.empty?
+ ".{#{array.join(",")}}"
+ end
end
end
diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb
index 9651f02c73..420b965c87 100644
--- a/activesupport/lib/active_support/gzip.rb
+++ b/activesupport/lib/active_support/gzip.rb
@@ -1,6 +1,5 @@
require 'zlib'
require 'stringio'
-require 'active_support/core_ext/string/encoding'
module ActiveSupport
# A convenient wrapper for the zlib standard library that allows compression/decompression of strings with gzip.
@@ -8,7 +7,7 @@ module ActiveSupport
class Stream < StringIO
def initialize(*)
super
- set_encoding "BINARY" if "".encoding_aware?
+ set_encoding "BINARY"
end
def close; rewind; end
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 636f019cd5..91459f3e5b 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -1,12 +1,11 @@
require 'active_support/core_ext/hash/keys'
-# This class has dubious semantics and we only have it so that
-# people can write <tt>params[:key]</tt> instead of <tt>params['key']</tt>
-# and they get the same value for both keys.
-
module ActiveSupport
+ # This class has dubious semantics and we only have it so that
+ # people can write <tt>params[:key]</tt> instead of <tt>params['key']</tt>
+ # and they get the same value for both keys.
class HashWithIndifferentAccess < Hash
-
+
# Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class.
def extractable_options?
true
@@ -16,6 +15,10 @@ module ActiveSupport
dup
end
+ def nested_under_indifferent_access
+ self
+ end
+
def initialize(constructor = {})
if constructor.is_a?(Hash)
super()
@@ -39,6 +42,10 @@ module ActiveSupport
end
end
+ def self.[](*args)
+ new.merge(Hash[*args])
+ end
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
alias_method :regular_update, :update unless method_defined?(:regular_update)
diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb
index f9c5e5e2f8..188653bd9b 100644
--- a/activesupport/lib/active_support/i18n.rb
+++ b/activesupport/lib/active_support/i18n.rb
@@ -6,4 +6,5 @@ rescue LoadError => e
raise e
end
+ActiveSupport.run_load_hooks(:i18n)
I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 4c59fe9ac9..bbeb8d82c6 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -10,14 +10,19 @@ module I18n
config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
def self.reloader
- @reloader ||= ActiveSupport::FileUpdateChecker.new([]){ I18n.reload! }
+ @reloader ||= ActiveSupport::FileUpdateChecker.new(reloader_paths){ I18n.reload! }
+ end
+
+ def self.reloader_paths
+ @reloader_paths ||= []
end
# Add <tt>I18n::Railtie.reloader</tt> to ActionDispatch callbacks. Since, at this
# point, no path was added to the reloader, I18n.reload! is not triggered
# on to_prepare callbacks. This will only happen on the config.after_initialize
# callback below.
- initializer "i18n.callbacks" do
+ initializer "i18n.callbacks" do |app|
+ app.reloaders << I18n::Railtie.reloader
ActionDispatch::Reloader.to_prepare do
I18n::Railtie.reloader.execute_if_updated
end
@@ -58,8 +63,8 @@ module I18n
init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
- reloader.paths.concat I18n.load_path
- reloader.execute_if_updated
+ reloader_paths.concat I18n.load_path
+ reloader.execute
@i18n_inited = true
end
diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb
index daf2a1e1d9..c04c2ed15b 100644
--- a/activesupport/lib/active_support/inflections.rb
+++ b/activesupport/lib/active_support/inflections.rb
@@ -2,7 +2,7 @@ module ActiveSupport
Inflector.inflections do |inflect|
inflect.plural(/$/, 's')
inflect.plural(/s$/i, 's')
- inflect.plural(/(ax|test)is$/i, '\1es')
+ inflect.plural(/^(ax|test)is$/i, '\1es')
inflect.plural(/(octop|vir)us$/i, '\1i')
inflect.plural(/(octop|vir)i$/i, '\1i')
inflect.plural(/(alias|status)$/i, '\1es')
@@ -16,17 +16,18 @@ module ActiveSupport
inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
- inflect.plural(/([m|l])ouse$/i, '\1ice')
- inflect.plural(/([m|l])ice$/i, '\1ice')
+ inflect.plural(/^(m|l)ouse$/i, '\1ice')
+ inflect.plural(/^(m|l)ice$/i, '\1ice')
inflect.plural(/^(ox)$/i, '\1en')
inflect.plural(/^(oxen)$/i, '\1')
inflect.plural(/(quiz)$/i, '\1zes')
inflect.singular(/s$/i, '')
+ inflect.singular(/(ss)$/i, '\1')
inflect.singular(/(n)ews$/i, '\1ews')
inflect.singular(/([ti])a$/i, '\1um')
- inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
- inflect.singular(/(^analy)ses$/i, '\1sis')
+ inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
+ inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
inflect.singular(/([^f])ves$/i, '\1fe')
inflect.singular(/(hive)s$/i, '\1')
inflect.singular(/(tive)s$/i, '\1')
@@ -35,13 +36,14 @@ module ActiveSupport
inflect.singular(/(s)eries$/i, '\1eries')
inflect.singular(/(m)ovies$/i, '\1ovie')
inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
- inflect.singular(/([m|l])ice$/i, '\1ouse')
- inflect.singular(/(bus)es$/i, '\1')
+ inflect.singular(/^(m|l)ice$/i, '\1ouse')
+ inflect.singular(/(bus)(es)?$/i, '\1')
inflect.singular(/(o)es$/i, '\1')
inflect.singular(/(shoe)s$/i, '\1')
- inflect.singular(/(cris|ax|test)es$/i, '\1is')
- inflect.singular(/(octop|vir)i$/i, '\1us')
- inflect.singular(/(alias|status)es$/i, '\1')
+ inflect.singular(/(cris|test)(is|es)$/i, '\1is')
+ inflect.singular(/^(a)x[ie]s$/i, '\1xis')
+ inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
+ inflect.singular(/(alias|status)(es)?$/i, '\1')
inflect.singular(/^(ox)en/i, '\1')
inflect.singular(/(vert|ind)ices$/i, '\1ex')
inflect.singular(/(matr)ices$/i, '\1ix')
@@ -56,6 +58,6 @@ module ActiveSupport
inflect.irregular('cow', 'kine')
inflect.irregular('zombie', 'zombies')
- inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
+ inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
end
end
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 90bb62f57b..600e353812 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,7 +1,9 @@
+require 'active_support/core_ext/array/prepend_and_append'
+
module ActiveSupport
module Inflector
# A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
- # inflection rules. Examples:
+ # inflection rules.
#
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1\2en'
@@ -26,12 +28,18 @@ module ActiveSupport
@plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
end
+ # Private, for the test suite.
+ def initialize_dup(orig)
+ %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
+ instance_variable_set("@#{scope}", orig.send(scope).dup)
+ end
+ end
+
# Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore
# string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`.
# A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will
# convert the acronym into a non-delimited single lowercase word when passed to +underscore+.
#
- # Examples:
# acronym 'HTML'
# titleize 'html' #=> 'HTML'
# camelize 'html' #=> 'HTML'
@@ -61,7 +69,6 @@ module ActiveSupport
# `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard
# capitalization. The only restriction is that the word must begin with a capital letter.
#
- # Examples:
# acronym 'RESTful'
# underscore 'RESTful' #=> 'restful'
# underscore 'RESTfulController' #=> 'restful_controller'
@@ -82,7 +89,7 @@ module ActiveSupport
def plural(rule, replacement)
@uncountables.delete(rule) if rule.is_a?(String)
@uncountables.delete(replacement)
- @plurals.insert(0, [rule, replacement])
+ @plurals.prepend([rule, replacement])
end
# Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
@@ -90,13 +97,12 @@ module ActiveSupport
def singular(rule, replacement)
@uncountables.delete(rule) if rule.is_a?(String)
@uncountables.delete(replacement)
- @singulars.insert(0, [rule, replacement])
+ @singulars.prepend([rule, replacement])
end
# Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
# for strings, not regular expressions. You simply pass the irregular in singular and plural form.
#
- # Examples:
# irregular 'octopus', 'octopi'
# irregular 'person', 'people'
def irregular(singular, plural)
@@ -118,7 +124,6 @@ module ActiveSupport
# Add uncountable words that shouldn't be attempted inflected.
#
- # Examples:
# uncountable "money"
# uncountable "money", "information"
# uncountable %w( money information rice )
@@ -130,18 +135,16 @@ module ActiveSupport
# When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
# When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
#
- # Examples:
# human /_cnt$/i, '\1_count'
# human "legacy_col_person_name", "Name"
def human(rule, replacement)
- @humans.insert(0, [rule, replacement])
+ @humans.prepend([rule, replacement])
end
# Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
# Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
# <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
#
- # Examples:
# clear :all
# clear :plurals
def clear(scope = :all)
@@ -157,7 +160,6 @@ module ActiveSupport
# Yields a singleton instance of Inflector::Inflections so you can specify additional
# inflector rules.
#
- # Example:
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.uncountable "rails"
# end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 934529d496..2acc6ddee5 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
require 'active_support/inflector/inflections'
module ActiveSupport
@@ -14,40 +16,24 @@ module ActiveSupport
# Returns the plural form of the word in the string.
#
- # Examples:
# "post".pluralize # => "posts"
# "octopus".pluralize # => "octopi"
# "sheep".pluralize # => "sheep"
# "words".pluralize # => "words"
# "CamelOctopus".pluralize # => "CamelOctopi"
def pluralize(word)
- result = word.to_s.dup
-
- if word.empty? || inflections.uncountables.include?(result.downcase)
- result
- else
- inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
- result
- end
+ apply_inflections(word, inflections.plurals)
end
# The reverse of +pluralize+, returns the singular form of a word in a string.
#
- # Examples:
# "posts".singularize # => "post"
# "octopi".singularize # => "octopus"
# "sheep".singularize # => "sheep"
# "word".singularize # => "word"
# "CamelOctopi".singularize # => "CamelOctopus"
def singularize(word)
- result = word.to_s.dup
-
- if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i }
- result
- else
- inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
- result
- end
+ apply_inflections(word, inflections.singulars)
end
# By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
@@ -55,11 +41,10 @@ module ActiveSupport
#
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
#
- # Examples:
- # "active_record".camelize # => "ActiveRecord"
- # "active_record".camelize(:lower) # => "activeRecord"
- # "active_record/errors".camelize # => "ActiveRecord::Errors"
- # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
+ # "active_model".camelize # => "ActiveModel"
+ # "active_model".camelize(:lower) # => "activeModel"
+ # "active_model/errors".camelize # => "ActiveModel::Errors"
+ # "active_model/errors".camelize(:lower) # => "activeModel::Errors"
#
# As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
# though there are cases where that does not hold:
@@ -79,9 +64,8 @@ module ActiveSupport
#
# Changes '::' to '/' to convert namespaces to paths.
#
- # Examples:
- # "ActiveRecord".underscore # => "active_record"
- # "ActiveRecord::Errors".underscore # => active_record/errors
+ # "ActiveModel".underscore # => "active_model"
+ # "ActiveModel::Errors".underscore # => "active_model/errors"
#
# As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
# though there are cases where that does not hold:
@@ -89,7 +73,7 @@ module ActiveSupport
# "SSLError".underscore.camelize # => "SslError"
def underscore(camel_cased_word)
word = camel_cased_word.to_s.dup
- word.gsub!(/::/, '/')
+ word.gsub!('::', '/')
word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
@@ -101,35 +85,35 @@ module ActiveSupport
# Capitalizes the first word and turns underscores into spaces and strips a
# trailing "_id", if any. Like +titleize+, this is meant for creating pretty output.
#
- # Examples:
# "employee_salary" # => "Employee salary"
# "author_id" # => "Author"
def humanize(lower_case_and_underscored_word)
result = lower_case_and_underscored_word.to_s.dup
- inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
+ inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
result.gsub!(/_id$/, "")
- result.gsub(/(_)?([a-z\d]*)/i) { "#{$1 && ' '}#{inflections.acronyms[$2] || $2.downcase}" }.gsub(/^\w/) { $&.upcase }
+ result.tr!('_', ' ')
+ result.gsub(/([a-z\d]*)/i) { |match|
+ "#{inflections.acronyms[match] || match.downcase}"
+ }.gsub(/^\w/) { $&.upcase }
end
# Capitalizes all the words and replaces some characters in the string to create
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
# used in the Rails internals.
#
- # +titleize+ is also aliased as as +titlecase+.
+ # +titleize+ is also aliased as +titlecase+.
#
- # Examples:
# "man from the boondocks".titleize # => "Man From The Boondocks"
# "x-men: the last stand".titleize # => "X Men: The Last Stand"
# "TheManWithoutAPast".titleize # => "The Man Without A Past"
# "raiders_of_the_lost_ark".titleize # => "Raiders Of The Lost Ark"
def titleize(word)
- humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
+ humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
end
# Create the name of a table like Rails does for models to table names. This method
# uses the +pluralize+ method on the last word in the string.
#
- # Examples
# "RawScaledScorer".tableize # => "raw_scaled_scorers"
# "egg_and_ham".tableize # => "egg_and_hams"
# "fancyCategory".tableize # => "fancy_categories"
@@ -141,7 +125,6 @@ module ActiveSupport
# Note that this returns a string and not a Class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
- # Examples:
# "egg_and_hams".classify # => "EggAndHam"
# "posts".classify # => "Post"
#
@@ -154,26 +137,43 @@ module ActiveSupport
# Replaces underscores with dashes in the string.
#
- # Example:
- # "puni_puni" # => "puni-puni"
+ # "puni_puni".dasherize # => "puni-puni"
def dasherize(underscored_word)
- underscored_word.gsub(/_/, '-')
+ underscored_word.tr('_', '-')
end
- # Removes the module part from the expression in the string.
+ # Removes the module part from the expression in the string:
#
- # Examples:
# "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
# "Inflections".demodulize # => "Inflections"
- def demodulize(class_name_in_module)
- class_name_in_module.to_s.gsub(/^.*::/, '')
+ #
+ # See also +deconstantize+.
+ def demodulize(path)
+ path = path.to_s
+ if i = path.rindex('::')
+ path[(i+2)..-1]
+ else
+ path
+ end
+ end
+
+ # Removes the rightmost segment from the constant expression in the string:
+ #
+ # "Net::HTTP".deconstantize # => "Net"
+ # "::Net::HTTP".deconstantize # => "::Net"
+ # "String".deconstantize # => ""
+ # "::String".deconstantize # => ""
+ # "".deconstantize # => ""
+ #
+ # See also +demodulize+.
+ def deconstantize(path)
+ path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename
end
# Creates a foreign key name from a class name.
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
#
- # Examples:
# "Message".foreign_key # => "message_id"
# "Message".foreign_key(false) # => "messageid"
# "Admin::Post".foreign_key # => "post_id"
@@ -181,46 +181,46 @@ module ActiveSupport
underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
end
- # Ruby 1.9 introduces an inherit argument for Module#const_get and
- # #const_defined? and changes their default behavior.
- if Module.method(:const_get).arity == 1
- # Tries to find a constant with the name specified in the argument string:
- #
- # "Module".constantize # => Module
- # "Test::Unit".constantize # => Test::Unit
- #
- # The name is assumed to be the one of a top-level constant, no matter whether
- # it starts with "::" or not. No lexical context is taken into account:
- #
- # C = 'outside'
- # module M
- # C = 'inside'
- # C # => 'inside'
- # "C".constantize # => 'outside', same as ::C
- # end
- #
- # NameError is raised when the name is not in CamelCase or the constant is
- # unknown.
- def constantize(camel_cased_word)
- names = camel_cased_word.split('::')
- names.shift if names.empty? || names.first.empty?
+ # Tries to find a constant with the name specified in the argument string:
+ #
+ # "Module".constantize # => Module
+ # "Test::Unit".constantize # => Test::Unit
+ #
+ # The name is assumed to be the one of a top-level constant, no matter whether
+ # it starts with "::" or not. No lexical context is taken into account:
+ #
+ # C = 'outside'
+ # module M
+ # C = 'inside'
+ # C # => 'inside'
+ # "C".constantize # => 'outside', same as ::C
+ # end
+ #
+ # NameError is raised when the name is not in CamelCase or the constant is
+ # unknown.
+ def constantize(camel_cased_word)
+ names = camel_cased_word.split('::')
+ names.shift if names.empty? || names.first.empty?
- constant = Object
- names.each do |name|
- constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
- end
- constant
- end
- else
- def constantize(camel_cased_word) #:nodoc:
- names = camel_cased_word.split('::')
- names.shift if names.empty? || names.first.empty?
+ names.inject(Object) do |constant, name|
+ if constant == Object
+ constant.const_get(name)
+ else
+ candidate = constant.const_get(name)
+ next candidate if constant.const_defined?(name, false)
+ next candidate unless Object.const_defined?(name)
+
+ # Go down the ancestors to check it it's owned
+ # directly before we reach Object or the end of ancestors.
+ constant = constant.ancestors.inject do |const, ancestor|
+ break const if ancestor == Object
+ break ancestor if ancestor.const_defined?(name, false)
+ const
+ end
- constant = Object
- names.each do |name|
- constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
+ # owner is in Object, so raise
+ constant.const_get(name, false)
end
- constant
end
end
@@ -250,17 +250,38 @@ module ActiveSupport
begin
constantize(camel_cased_word)
rescue NameError => e
- raise unless e.message =~ /uninitialized constant #{const_regexp(camel_cased_word)}$/ ||
+ raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
e.name.to_s == camel_cased_word.to_s
rescue ArgumentError => e
raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
end
end
+ # Returns the suffix that should be added to a number to denote the position
+ # in an ordered sequence such as 1st, 2nd, 3rd, 4th.
+ #
+ # ordinal(1) # => "st"
+ # ordinal(2) # => "nd"
+ # ordinal(1002) # => "nd"
+ # ordinal(1003) # => "rd"
+ # ordinal(-11) # => "th"
+ # ordinal(-1021) # => "st"
+ def ordinal(number)
+ if (11..13).include?(number.to_i.abs % 100)
+ "th"
+ else
+ case number.to_i.abs % 10
+ when 1; "st"
+ when 2; "nd"
+ when 3; "rd"
+ else "th"
+ end
+ end
+ end
+
# Turns a number into an ordinal string used to denote the position in an
# ordered sequence such as 1st, 2nd, 3rd, 4th.
#
- # Examples:
# ordinalize(1) # => "1st"
# ordinalize(2) # => "2nd"
# ordinalize(1002) # => "1002nd"
@@ -268,16 +289,7 @@ module ActiveSupport
# ordinalize(-11) # => "-11th"
# ordinalize(-1021) # => "-1021st"
def ordinalize(number)
- if (11..13).include?(number.to_i.abs % 100)
- "#{number}th"
- else
- case number.to_i.abs % 10
- when 1; "#{number}st"
- when 2; "#{number}nd"
- when 3; "#{number}rd"
- else "#{number}th"
- end
- end
+ "#{number}#{ordinal(number)}"
end
private
@@ -292,5 +304,20 @@ module ActiveSupport
part.empty? ? acc : "#{part}(::#{acc})?"
end
end
+
+ # Applies inflection rules for +singularize+ and +pluralize+.
+ #
+ # apply_inflections("post", inflections.plurals) # => "posts"
+ # apply_inflections("posts", inflections.singulars) # => "post"
+ def apply_inflections(word, rules)
+ result = word.to_s.dup
+
+ if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
+ result
+ else
+ rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
+ result
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index 40e7a0e389..a372b6d1f7 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -66,8 +66,6 @@ module ActiveSupport
# Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
#
- # ==== Examples
- #
# class Person
# def to_param
# "#{id}-#{name.parameterize}"
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index cbeb6c0a28..986a764479 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -9,7 +9,7 @@ module ActiveSupport
module JSON
class << self
def decode(json, options ={})
- data = MultiJson.decode(json, options)
+ data = MultiJson.load(json, options)
if ActiveSupport.parse_json_times
convert_dates_from(data)
else
@@ -18,12 +18,12 @@ module ActiveSupport
end
def engine
- MultiJson.engine
+ MultiJson.adapter
end
alias :backend :engine
def engine=(name)
- MultiJson.engine = name
+ MultiJson.use(name)
end
alias :backend= :engine=
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 469ae69258..a6e4e7ced2 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -1,11 +1,9 @@
require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/module/delegation'
require 'active_support/json/variable'
-require 'active_support/ordered_hash'
require 'bigdecimal'
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/instance_variables'
@@ -19,6 +17,7 @@ module ActiveSupport
class << self
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
:escape_html_entities_in_json, :escape_html_entities_in_json=,
+ :encode_big_decimal_as_string, :encode_big_decimal_as_string=,
:to => :'ActiveSupport::JSON::Encoding'
end
@@ -50,9 +49,9 @@ module ActiveSupport
end
# like encode, but only calls as_json, without encoding to string
- def as_json(value)
+ def as_json(value, use_options = true)
check_for_circular_references(value) do
- value.as_json(options_for(value))
+ use_options ? value.as_json(options_for(value)) : value.as_json
end
end
@@ -106,6 +105,9 @@ module ActiveSupport
# If true, use ISO 8601 format for dates and times. Otherwise, fall back to the Active Support legacy format.
attr_accessor :use_standard_json_time_format
+ # If false, serializes BigDecimal objects as numeric instead of wrapping them in a string
+ attr_accessor :encode_big_decimal_as_string
+
attr_accessor :escape_regex
attr_reader :escape_html_entities_in_json
@@ -119,9 +121,7 @@ module ActiveSupport
end
def escape(string)
- if string.respond_to?(:force_encoding)
- string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
- end
+ string = string.encode(::Encoding::UTF_8, :undef => :replace).force_encoding(::Encoding::BINARY)
json = string.
gsub(escape_regex) { |s| ESCAPED_CHARS[s] }.
gsub(/([\xC0-\xDF][\x80-\xBF]|
@@ -130,13 +130,14 @@ module ActiveSupport
s.unpack("U*").pack("n*").unpack("H*")[0].gsub(/.{4}/n, '\\\\u\&')
}
json = %("#{json}")
- json.force_encoding(::Encoding::UTF_8) if json.respond_to?(:force_encoding)
+ json.force_encoding(::Encoding::UTF_8)
json
end
end
self.use_standard_json_time_format = true
- self.escape_html_entities_in_json = false
+ self.escape_html_entities_in_json = true
+ self.encode_big_decimal_as_string = true
end
end
end
@@ -151,8 +152,8 @@ class Object
end
end
-class Struct
- def as_json(options = nil) #:nodoc:
+class Struct #:nodoc:
+ def as_json(options = nil)
Hash[members.zip(values)]
end
end
@@ -186,6 +187,12 @@ class Numeric
def encode_json(encoder) to_s end #:nodoc:
end
+class Float
+ # Encoding Infinity or NaN to JSON should return "null". The default returns
+ # "Infinity" or "NaN" what breaks parsing the JSON. E.g. JSON.parse('[NaN]').
+ def as_json(options = nil) finite? ? self : NilClass::AS_JSON end #:nodoc:
+end
+
class BigDecimal
# A BigDecimal would be naturally represented as a JSON number. Most libraries,
# however, parse non-integer JSON numbers directly as floats. Clients using
@@ -195,7 +202,15 @@ class BigDecimal
# That's why a JSON string is returned. The JSON literal is not numeric, but if
# the other end knows by contract that the data is supposed to be a BigDecimal,
# it still has the chance to post-process the string and get the real value.
- def as_json(options = nil) to_s end #:nodoc:
+ #
+ # Use ActiveSupport.use_standard_json_big_decimal_format = true to override this behaviour
+ def as_json(options = nil) #:nodoc:
+ if finite?
+ ActiveSupport.encode_big_decimal_as_string ? to_s : self
+ else
+ NilClass::AS_JSON
+ end
+ end
end
class Regexp
@@ -208,11 +223,15 @@ module Enumerable
end
end
+class Range
+ def as_json(options = nil) to_s end #:nodoc:
+end
+
class Array
def as_json(options = nil) #:nodoc:
# use encoder as a proxy to call as_json on all elements, to protect from circular references
encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
- map { |v| encoder.as_json(v) }
+ map { |v| encoder.as_json(v, options) }
end
def encode_json(encoder) #:nodoc:
@@ -226,9 +245,9 @@ class Hash
# create a subset of the hash by applying :only or :except
subset = if options
if attrs = options[:only]
- slice(*Array.wrap(attrs))
+ slice(*Array(attrs))
elsif attrs = options[:except]
- except(*Array.wrap(attrs))
+ except(*Array(attrs))
else
self
end
@@ -238,8 +257,7 @@ class Hash
# use encoder as a proxy to call as_json on all values in the subset, to protect from circular references
encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options)
- result = self.is_a?(ActiveSupport::OrderedHash) ? ActiveSupport::OrderedHash : Hash
- result[subset.map { |k, v| [k.to_s, encoder.as_json(v)] }]
+ Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }]
end
def encode_json(encoder)
diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb
index 82507c1e03..c167efc1a7 100644
--- a/activesupport/lib/active_support/lazy_load_hooks.rb
+++ b/activesupport/lib/active_support/lazy_load_hooks.rb
@@ -1,32 +1,32 @@
-# lazy_load_hooks allows rails to lazily load a lot of components and thus making the app boot faster. Because of
-# this feature now there is no need to require <tt>ActiveRecord::Base</tt> at boot time purely to apply configuration. Instead
-# a hook is registered that applies configuration once <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is used
-# as example but this feature can be applied elsewhere too.
-#
-# Here is an example where +on_load+ method is called to register a hook.
-#
-# initializer "active_record.initialize_timezone" do
-# ActiveSupport.on_load(:active_record) do
-# self.time_zone_aware_attributes = true
-# self.default_timezone = :utc
-# end
-# end
-#
-# When the entirety of +activerecord/lib/active_record/base.rb+ has been evaluated then +run_load_hooks+ is invoked.
-# The very last line of +activerecord/lib/active_record/base.rb+ is:
-#
-# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
-#
module ActiveSupport
- @load_hooks = Hash.new {|h,k| h[k] = [] }
- @loaded = {}
+ # lazy_load_hooks allows rails to lazily load a lot of components and thus making the app boot faster. Because of
+ # this feature now there is no need to require <tt>ActiveRecord::Base</tt> at boot time purely to apply configuration. Instead
+ # a hook is registered that applies configuration once <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is used
+ # as example but this feature can be applied elsewhere too.
+ #
+ # Here is an example where +on_load+ method is called to register a hook.
+ #
+ # initializer "active_record.initialize_timezone" do
+ # ActiveSupport.on_load(:active_record) do
+ # self.time_zone_aware_attributes = true
+ # self.default_timezone = :utc
+ # end
+ # end
+ #
+ # When the entirety of +activerecord/lib/active_record/base.rb+ has been evaluated then +run_load_hooks+ is invoked.
+ # The very last line of +activerecord/lib/active_record/base.rb+ is:
+ #
+ # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
+ #
+ @load_hooks = Hash.new { |h,k| h[k] = [] }
+ @loaded = Hash.new { |h,k| h[k] = [] }
def self.on_load(name, options = {}, &block)
- if base = @loaded[name]
+ @loaded[name].each do |base|
execute_hook(base, options, block)
- else
- @load_hooks[name] << [block, options]
end
+
+ @load_hooks[name] << [block, options]
end
def self.execute_hook(base, options, block)
@@ -38,7 +38,7 @@ module ActiveSupport
end
def self.run_load_hooks(name, base = Object)
- @loaded[name] = base
+ @loaded[name] << base
@load_hooks[name].each do |hook, options|
execute_hook(base, options, hook)
end
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index 6296c1d4b8..d2a6e1bd82 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/class/attribute'
module ActiveSupport
# ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications
- # with the sole purpose of logging them. The log subscriber dispatches notifications to
+ # with the sole purpose of logging them. The log subscriber dispatches notifications to
# a registered object based on its given namespace.
#
# An example would be Active Record log subscriber responsible for logging queries:
@@ -75,7 +75,8 @@ module ActiveSupport
@@flushable_loggers ||= begin
loggers = log_subscribers.map(&:logger)
loggers.uniq!
- loggers.select { |l| l.respond_to?(:flush) }
+ loggers.select! { |l| l.respond_to?(:flush) }
+ loggers
end
end
@@ -92,7 +93,7 @@ module ActiveSupport
begin
send(method, ActiveSupport::Notifications::Event.new(message, *args))
rescue Exception => e
- logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message}"
+ logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
end
end
@@ -100,9 +101,8 @@ module ActiveSupport
%w(info debug warn error fatal unknown).each do |level|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{level}(*args, &block)
- return unless logger
- logger.#{level}(*args, &block)
+ def #{level}(progname = nil, &block)
+ logger.#{level}(progname, &block) if logger
end
METHOD
end
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index dcfcf0b63c..b65ea6208c 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -50,7 +50,7 @@ module ActiveSupport
end
class MockLogger
- include ActiveSupport::BufferedLogger::Severity
+ include ActiveSupport::Logger::Severity
attr_reader :flush_count
attr_accessor :level
@@ -61,8 +61,12 @@ module ActiveSupport
@logged = Hash.new { |h,k| h[k] = [] }
end
- def method_missing(level, message)
- @logged[level] << message
+ def method_missing(level, message = nil)
+ if block_given?
+ @logged[level] << yield
+ else
+ @logged[level] << message
+ end
end
def logged(level)
@@ -73,7 +77,7 @@ module ActiveSupport
@flush_count += 1
end
- ActiveSupport::BufferedLogger::Severity.constants.each do |severity|
+ ActiveSupport::Logger::Severity.constants.each do |severity|
class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{severity.downcase}?
#{severity} >= @level
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
new file mode 100644
index 0000000000..d055767eab
--- /dev/null
+++ b/activesupport/lib/active_support/logger.rb
@@ -0,0 +1,53 @@
+require 'logger'
+
+module ActiveSupport
+ class Logger < ::Logger
+ # Broadcasts logs to multiple loggers
+ def self.broadcast(logger) # :nodoc:
+ Module.new do
+ define_method(:add) do |*args, &block|
+ logger.add(*args, &block)
+ super(*args, &block)
+ end
+
+ define_method(:<<) do |x|
+ logger << x
+ super(x)
+ end
+
+ define_method(:close) do
+ logger.close
+ super()
+ end
+
+ define_method(:progname=) do |name|
+ logger.progname = name
+ super(name)
+ end
+
+ define_method(:formatter=) do |formatter|
+ logger.formatter = formatter
+ super(formatter)
+ end
+
+ define_method(:level=) do |level|
+ logger.level = level
+ super(level)
+ end
+ end
+ end
+
+ def initialize(*args)
+ super
+ @formatter = SimpleFormatter.new
+ end
+
+ # Simple formatter which only displays the message.
+ class SimpleFormatter < ::Logger::Formatter
+ # This method is invoked when a log event occurs
+ def call(severity, timestamp, progname, msg)
+ "#{String === msg ? msg : msg.inspect}\n"
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
deleted file mode 100644
index 4c67676ad5..0000000000
--- a/activesupport/lib/active_support/memoizable.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/module/aliasing'
-require 'active_support/deprecation'
-
-module ActiveSupport
- module Memoizable
- def self.extended(base)
- ActiveSupport::Deprecation.warn "ActiveSupport::Memoizable is deprecated and will be removed in future releases," \
- "simply use Ruby memoization pattern instead.", caller
- super
- end
-
- def self.memoized_ivar_for(symbol)
- "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym
- end
-
- module InstanceMethods
- def self.included(base)
- base.class_eval do
- unless base.method_defined?(:freeze_without_memoizable)
- alias_method_chain :freeze, :memoizable
- end
- end
- end
-
- def freeze_with_memoizable
- memoize_all unless frozen?
- freeze_without_memoizable
- end
-
- def memoize_all
- prime_cache ".*"
- end
-
- def unmemoize_all
- flush_cache ".*"
- end
-
- def prime_cache(*syms)
- syms.each do |sym|
- methods.each do |m|
- if m.to_s =~ /^_unmemoized_(#{sym})/
- if method(m).arity == 0
- __send__($1)
- else
- ivar = ActiveSupport::Memoizable.memoized_ivar_for($1)
- instance_variable_set(ivar, {})
- end
- end
- end
- end
- end
-
- def flush_cache(*syms)
- syms.each do |sym|
- (methods + private_methods + protected_methods).each do |m|
- if m.to_s =~ /^_unmemoized_(#{sym.to_s.gsub(/\?\Z/, '\?')})/
- ivar = ActiveSupport::Memoizable.memoized_ivar_for($1)
- instance_variable_get(ivar).clear if instance_variable_defined?(ivar)
- end
- end
- end
- end
- end
-
- def memoize(*symbols)
- symbols.each do |symbol|
- original_method = :"_unmemoized_#{symbol}"
- memoized_ivar = ActiveSupport::Memoizable.memoized_ivar_for(symbol)
-
- class_eval <<-EOS, __FILE__, __LINE__ + 1
- include InstanceMethods # include InstanceMethods
- #
- if method_defined?(:#{original_method}) # if method_defined?(:_unmemoized_mime_type)
- raise "Already memoized #{symbol}" # raise "Already memoized mime_type"
- end # end
- alias #{original_method} #{symbol} # alias _unmemoized_mime_type mime_type
- #
- if instance_method(:#{symbol}).arity == 0 # if instance_method(:mime_type).arity == 0
- def #{symbol}(reload = false) # def mime_type(reload = false)
- if reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty? # if reload || !defined?(@_memoized_mime_type) || @_memoized_mime_type.empty?
- #{memoized_ivar} = [#{original_method}] # @_memoized_mime_type = [_unmemoized_mime_type]
- end # end
- #{memoized_ivar}[0] # @_memoized_mime_type[0]
- end # end
- else # else
- def #{symbol}(*args) # def mime_type(*args)
- #{memoized_ivar} ||= {} unless frozen? # @_memoized_mime_type ||= {} unless frozen?
- args_length = method(:#{original_method}).arity # args_length = method(:_unmemoized_mime_type).arity
- if args.length == args_length + 1 && # if args.length == args_length + 1 &&
- (args.last == true || args.last == :reload) # (args.last == true || args.last == :reload)
- reload = args.pop # reload = args.pop
- end # end
- #
- if defined?(#{memoized_ivar}) && #{memoized_ivar} # if defined?(@_memoized_mime_type) && @_memoized_mime_type
- if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args)
- #{memoized_ivar}[args] # @_memoized_mime_type[args]
- elsif #{memoized_ivar} # elsif @_memoized_mime_type
- #{memoized_ivar}[args] = #{original_method}(*args) # @_memoized_mime_type[args] = _unmemoized_mime_type(*args)
- end # end
- else # else
- #{original_method}(*args) # _unmemoized_mime_type(*args)
- end # end
- end # end
- end # end
- #
- if private_method_defined?(#{original_method.inspect}) # if private_method_defined?(:_unmemoized_mime_type)
- private #{symbol.inspect} # private :mime_type
- elsif protected_method_defined?(#{original_method.inspect}) # elsif protected_method_defined?(:_unmemoized_mime_type)
- protected #{symbol.inspect} # protected :mime_type
- end # end
- EOS
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index e14386a85d..ada2e79ccb 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -1,5 +1,5 @@
require 'openssl'
-require 'active_support/base64'
+require 'base64'
module ActiveSupport
# MessageEncryptor is a simple way to encrypt values which get stored somewhere
@@ -9,22 +9,56 @@ module ActiveSupport
#
# This can be used in situations similar to the <tt>MessageVerifier</tt>, but where you don't
# want users to be able to determine the value of the payload.
+ #
+ # key = OpenSSL::Digest::SHA256.new('password').digest # => "\x89\xE0\x156\xAC..."
+ # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
+ # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
+ # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
class MessageEncryptor
+ module NullSerializer #:nodoc:
+ def self.load(value)
+ value
+ end
+
+ def self.dump(value)
+ value
+ end
+ end
+
class InvalidMessage < StandardError; end
OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
+ # Initialize a new MessageEncryptor.
+ # +secret+ must be at least as long as the cipher key size. For the default 'aes-256-cbc' cipher,
+ # this is 256 bits. If you are using a user-entered secret, you can generate a suitable key with
+ # <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or similar.
+ #
+ # Options:
+ # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'
+ # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
+ #
def initialize(secret, options = {})
- unless options.is_a?(Hash)
- ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :cipher => 'algorithm' to specify the cipher algorithm."
- options = { :cipher => options }
- end
-
@secret = secret
@cipher = options[:cipher] || 'aes-256-cbc'
+ @verifier = MessageVerifier.new(@secret, :serializer => NullSerializer)
@serializer = options[:serializer] || Marshal
end
- def encrypt(value)
+ # Encrypt and sign a message. We need to sign the message in order to avoid padding attacks.
+ # Reference: http://www.limited-entropy.com/padding-oracle-attacks
+ def encrypt_and_sign(value)
+ verifier.generate(_encrypt(value))
+ end
+
+ # Decrypt and verify a message. We need to verify the message in order to avoid padding attacks.
+ # Reference: http://www.limited-entropy.com/padding-oracle-attacks
+ def decrypt_and_verify(value)
+ _decrypt(verifier.verify(value))
+ end
+
+ private
+
+ def _encrypt(value)
cipher = new_cipher
# Rely on OpenSSL for the initialization vector
iv = cipher.random_iv
@@ -36,12 +70,12 @@ module ActiveSupport
encrypted_data = cipher.update(@serializer.dump(value))
encrypted_data << cipher.final
- [encrypted_data, iv].map {|v| ActiveSupport::Base64.encode64s(v)}.join("--")
+ [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--")
end
- def decrypt(encrypted_message)
+ def _decrypt(encrypted_message)
cipher = new_cipher
- encrypted_data, iv = encrypted_message.split("--").map {|v| ActiveSupport::Base64.decode64(v)}
+ encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.decode64(v)}
cipher.decrypt
cipher.key = @secret
@@ -55,23 +89,12 @@ module ActiveSupport
raise InvalidMessage
end
- def encrypt_and_sign(value)
- verifier.generate(encrypt(value))
+ def new_cipher
+ OpenSSL::Cipher::Cipher.new(@cipher)
end
- def decrypt_and_verify(value)
- decrypt(verifier.verify(value))
+ def verifier
+ @verifier
end
-
-
-
- private
- def new_cipher
- OpenSSL::Cipher::Cipher.new(@cipher)
- end
-
- def verifier
- MessageVerifier.new(@secret)
- end
end
end
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 9d7c81142a..3b27089fa0 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -1,4 +1,4 @@
-require 'active_support/base64'
+require 'base64'
require 'active_support/core_ext/object/blank'
module ActiveSupport
@@ -18,7 +18,7 @@ module ActiveSupport
# self.current_user = User.find(id)
# end
#
- # By default it uses Marshal to serialize the message. If you want to use another
+ # By default it uses Marshal to serialize the message. If you want to use another
# serialization method, you can set the serializer attribute to something that responds
# to dump and load, e.g.:
#
@@ -27,11 +27,6 @@ module ActiveSupport
class InvalidSignature < StandardError; end
def initialize(secret, options = {})
- unless options.is_a?(Hash)
- ActiveSupport::Deprecation.warn "The second parameter should be an options hash. Use :digest => 'algorithm' to specify the digest algorithm."
- options = { :digest => options }
- end
-
@secret = secret
@digest = options[:digest] || 'SHA1'
@serializer = options[:serializer] || Marshal
@@ -42,14 +37,14 @@ module ActiveSupport
data, digest = signed_message.split("--")
if data.present? && digest.present? && secure_compare(digest, generate_digest(data))
- @serializer.load(ActiveSupport::Base64.decode64(data))
+ @serializer.load(::Base64.decode64(data))
else
raise InvalidSignature
end
end
def generate(value)
- data = ActiveSupport::Base64.encode64s(@serializer.dump(value))
+ data = ::Base64.strict_encode64(@serializer.dump(value))
"#{data}--#{generate_digest(data)}"
end
diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb
index 57e8e24bf4..977fe95dbe 100644
--- a/activesupport/lib/active_support/multibyte.rb
+++ b/activesupport/lib/active_support/multibyte.rb
@@ -1,9 +1,5 @@
-# encoding: utf-8
-require 'active_support/core_ext/module/attribute_accessors'
-
module ActiveSupport #:nodoc:
module Multibyte
- autoload :EncodingError, 'active_support/multibyte/exceptions'
autoload :Chars, 'active_support/multibyte/chars'
autoload :Unicode, 'active_support/multibyte/unicode'
@@ -11,7 +7,6 @@ module ActiveSupport #:nodoc:
# class so you can support other encodings. See the ActiveSupport::Multibyte::Chars implementation for
# an example how to do this.
#
- # Example:
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
def self.proxy_class=(klass)
@proxy_class = klass
@@ -21,24 +16,5 @@ module ActiveSupport #:nodoc:
def self.proxy_class
@proxy_class ||= ActiveSupport::Multibyte::Chars
end
-
- # Regular expressions that describe valid byte sequences for a character
- VALID_CHARACTER = {
- # Borrowed from the Kconv library by Shinji KONO - (also as seen on the W3C site)
- 'UTF-8' => /\A(?:
- [\x00-\x7f] |
- [\xc2-\xdf] [\x80-\xbf] |
- \xe0 [\xa0-\xbf] [\x80-\xbf] |
- [\xe1-\xef] [\x80-\xbf] [\x80-\xbf] |
- \xf0 [\x90-\xbf] [\x80-\xbf] [\x80-\xbf] |
- [\xf1-\xf3] [\x80-\xbf] [\x80-\xbf] [\x80-\xbf] |
- \xf4 [\x80-\x8f] [\x80-\xbf] [\x80-\xbf])\z /xn,
- # Quick check for valid Shift-JIS characters, disregards the odd-even pairing
- 'Shift_JIS' => /\A(?:
- [\x00-\x7e\xa1-\xdf] |
- [\x81-\x9f\xe0-\xef] [\x40-\x7e\x80-\x9e\x9f-\xfc])\z /xn
- }
end
end
-
-require 'active_support/multibyte/utils' \ No newline at end of file
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index b78d92f599..4fe925f7f4 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -1,6 +1,8 @@
# encoding: utf-8
+require 'active_support/json'
require 'active_support/core_ext/string/access'
require 'active_support/core_ext/string/behavior'
+require 'active_support/core_ext/module/delegation'
module ActiveSupport #:nodoc:
module Multibyte #:nodoc:
@@ -34,27 +36,24 @@ module ActiveSupport #:nodoc:
#
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
class Chars
+ include Comparable
attr_reader :wrapped_string
alias to_s wrapped_string
alias to_str wrapped_string
- if RUBY_VERSION >= "1.9"
- # Creates a new Chars instance by wrapping _string_.
- def initialize(string)
- @wrapped_string = string
- @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
- end
- else
- def initialize(string) #:nodoc:
- @wrapped_string = string
- end
+ delegate :<=>, :=~, :acts_like_string?, :to => :wrapped_string
+
+ # Creates a new Chars instance by wrapping _string_.
+ def initialize(string)
+ @wrapped_string = string
+ @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
end
# Forward all undefined methods to the wrapped string.
def method_missing(method, *args, &block)
if method.to_s =~ /!$/
- @wrapped_string.__send__(method, *args, &block)
- self
+ result = @wrapped_string.__send__(method, *args, &block)
+ self if result
else
result = @wrapped_string.__send__(method, *args, &block)
result.kind_of?(String) ? chars(result) : result
@@ -63,298 +62,67 @@ module ActiveSupport #:nodoc:
# Returns +true+ if _obj_ responds to the given method. Private methods are included in the search
# only if the optional second parameter evaluates to +true+.
- def respond_to?(method, include_private=false)
- super || @wrapped_string.respond_to?(method, include_private)
- end
-
- # Enable more predictable duck-typing on String-like classes. See Object#acts_like?.
- def acts_like_string?
- true
+ def respond_to_missing?(method, include_private)
+ @wrapped_string.respond_to?(method, include_private)
end
# Returns +true+ when the proxy class can handle the string. Returns +false+ otherwise.
def self.consumes?(string)
- # Unpack is a little bit faster than regular expressions.
- string.unpack('U*')
- true
- rescue ArgumentError
- false
- end
-
- include Comparable
-
- # Returns -1, 0, or 1, depending on whether the Chars object is to be sorted before,
- # equal or after the object on the right side of the operation. It accepts any object
- # that implements +to_s+:
- #
- # 'é'.mb_chars <=> 'ü'.mb_chars # => -1
- #
- # See <tt>String#<=></tt> for more details.
- def <=>(other)
- @wrapped_string <=> other.to_s
- end
-
- if RUBY_VERSION < "1.9"
- # Returns +true+ if the Chars class can and should act as a proxy for the string _string_. Returns
- # +false+ otherwise.
- def self.wants?(string)
- $KCODE == 'UTF8' && consumes?(string)
- end
-
- # Returns a new Chars object containing the _other_ object concatenated to the string.
- #
- # Example:
- # ('Café'.mb_chars + ' périferôl').to_s # => "Café périferôl"
- def +(other)
- chars(@wrapped_string + other)
- end
-
- # Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
- #
- # Example:
- # 'Café périferôl'.mb_chars =~ /ô/ # => 12
- def =~(other)
- translate_offset(@wrapped_string =~ other)
- end
-
- # Inserts the passed string at specified codepoint offsets.
- #
- # Example:
- # 'Café'.mb_chars.insert(4, ' périferôl').to_s # => "Café périferôl"
- def insert(offset, fragment)
- unpacked = Unicode.u_unpack(@wrapped_string)
- unless offset > unpacked.length
- @wrapped_string.replace(
- Unicode.u_unpack(@wrapped_string).insert(offset, *Unicode.u_unpack(fragment)).pack('U*')
- )
- else
- raise IndexError, "index #{offset} out of string"
- end
- self
- end
-
- # Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
- #
- # Example:
- # 'Café'.mb_chars.include?('é') # => true
- def include?(other)
- # We have to redefine this method because Enumerable defines it.
- @wrapped_string.include?(other)
- end
-
- # Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
- #
- # Example:
- # 'Café périferôl'.mb_chars.index('ô') # => 12
- # 'Café périferôl'.mb_chars.index(/\w/u) # => 0
- def index(needle, offset=0)
- wrapped_offset = first(offset).wrapped_string.length
- index = @wrapped_string.index(needle, wrapped_offset)
- index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
- end
-
- # Returns the position _needle_ in the string, counting in
- # codepoints, searching backward from _offset_ or the end of the
- # string. Returns +nil+ if _needle_ isn't found.
- #
- # Example:
- # 'Café périferôl'.mb_chars.rindex('é') # => 6
- # 'Café périferôl'.mb_chars.rindex(/\w/u) # => 13
- def rindex(needle, offset=nil)
- offset ||= length
- wrapped_offset = first(offset).wrapped_string.length
- index = @wrapped_string.rindex(needle, wrapped_offset)
- index ? (Unicode.u_unpack(@wrapped_string.slice(0...index)).size) : nil
- end
-
- # Returns the number of codepoints in the string
- def size
- Unicode.u_unpack(@wrapped_string).size
- end
- alias_method :length, :size
-
- # Strips entire range of Unicode whitespace from the right of the string.
- def rstrip
- chars(@wrapped_string.gsub(Unicode::TRAILERS_PAT, ''))
- end
-
- # Strips entire range of Unicode whitespace from the left of the string.
- def lstrip
- chars(@wrapped_string.gsub(Unicode::LEADERS_PAT, ''))
- end
-
- # Strips entire range of Unicode whitespace from the right and left of the string.
- def strip
- rstrip.lstrip
- end
-
- # Returns the codepoint of the first character in the string.
- #
- # Example:
- # 'こんにちは'.mb_chars.ord # => 12371
- def ord
- Unicode.u_unpack(@wrapped_string)[0]
- end
-
- # Works just like <tt>String#rjust</tt>, only integer specifies characters instead of bytes.
- #
- # Example:
- #
- # "¾ cup".mb_chars.rjust(8).to_s
- # # => " ¾ cup"
- #
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
- # # => "   ¾ cup"
- def rjust(integer, padstr=' ')
- justify(integer, :right, padstr)
- end
-
- # Works just like <tt>String#ljust</tt>, only integer specifies characters instead of bytes.
- #
- # Example:
- #
- # "¾ cup".mb_chars.rjust(8).to_s
- # # => "¾ cup "
- #
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
- # # => "¾ cup   "
- def ljust(integer, padstr=' ')
- justify(integer, :left, padstr)
- end
-
- # Works just like <tt>String#center</tt>, only integer specifies characters instead of bytes.
- #
- # Example:
- #
- # "¾ cup".mb_chars.center(8).to_s
- # # => " ¾ cup "
- #
- # "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
- # # => " ¾ cup  "
- def center(integer, padstr=' ')
- justify(integer, :center, padstr)
- end
-
- else
- def =~(other)
- @wrapped_string =~ other
- end
+ string.encoding == Encoding::UTF_8
end
# Works just like <tt>String#split</tt>, with the exception that the items in the resulting list are Chars
# instances instead of String. This makes chaining methods easier.
#
- # Example:
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
def split(*args)
@wrapped_string.split(*args).map { |i| i.mb_chars }
end
- # Like <tt>String#[]=</tt>, except instead of byte offsets you specify character offsets.
- #
- # Example:
- #
- # s = "Müller"
- # s.mb_chars[2] = "e" # Replace character with offset 2
- # s
- # # => "Müeler"
- #
- # s = "Müller"
- # s.mb_chars[1, 2] = "ö" # Replace 2 characters at character offset 1
- # s
- # # => "Möler"
- def []=(*args)
- replace_by = args.pop
- # Indexed replace with regular expressions already works
- if args.first.is_a?(Regexp)
- @wrapped_string[*args] = replace_by
- else
- result = Unicode.u_unpack(@wrapped_string)
- case args.first
- when Fixnum
- raise IndexError, "index #{args[0]} out of string" if args[0] >= result.length
- min = args[0]
- max = args[1].nil? ? min : (min + args[1] - 1)
- range = Range.new(min, max)
- replace_by = [replace_by].pack('U') if replace_by.is_a?(Fixnum)
- when Range
- raise RangeError, "#{args[0]} out of range" if args[0].min >= result.length
- range = args[0]
- else
- needle = args[0].to_s
- min = index(needle)
- max = min + Unicode.u_unpack(needle).length - 1
- range = Range.new(min, max)
- end
- result[range] = Unicode.u_unpack(replace_by)
- @wrapped_string.replace(result.pack('U*'))
- end
+ # Works like like <tt>String#slice!</tt>, but returns an instance of Chars, or nil if the string was not
+ # modified.
+ def slice!(*args)
+ chars(@wrapped_string.slice!(*args))
end
# Reverses all characters in the string.
#
- # Example:
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
def reverse
- chars(Unicode.g_unpack(@wrapped_string).reverse.flatten.pack('U*'))
+ chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*'))
end
- # Implements Unicode-aware slice with codepoints. Slicing on one point returns the codepoints for that
- # character.
- #
- # Example:
- # 'こんにちは'.mb_chars.slice(2..3).to_s # => "にち"
- def slice(*args)
- if args.size > 2
- raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
- elsif (args.size == 2 && !(args.first.is_a?(Numeric) || args.first.is_a?(Regexp)))
- raise TypeError, "cannot convert #{args.first.class} into Integer" # Do as if we were native
- elsif (args.size == 2 && !args[1].is_a?(Numeric))
- raise TypeError, "cannot convert #{args[1].class} into Integer" # Do as if we were native
- elsif args[0].kind_of? Range
- cps = Unicode.u_unpack(@wrapped_string).slice(*args)
- result = cps.nil? ? nil : cps.pack('U*')
- elsif args[0].kind_of? Regexp
- result = @wrapped_string.slice(*args)
- elsif args.size == 1 && args[0].kind_of?(Numeric)
- character = Unicode.u_unpack(@wrapped_string)[args[0]]
- result = character && [character].pack('U')
- else
- cps = Unicode.u_unpack(@wrapped_string).slice(*args)
- result = cps && cps.pack('U*')
- end
- result && chars(result)
- end
- alias_method :[], :slice
-
- # Limit the byte size of the string to a number of bytes without breaking characters. Usable
+ # Limits the byte size of the string to a number of bytes without breaking characters. Usable
# when the storage for a string is limited for some reason.
#
- # Example:
# 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
def limit(limit)
slice(0...translate_offset(limit))
end
- # Convert characters in the string to uppercase.
+ # Converts characters in the string to uppercase.
#
- # Example:
# 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
def upcase
- chars(Unicode.apply_mapping @wrapped_string, :uppercase_mapping)
+ chars Unicode.upcase(@wrapped_string)
end
- # Convert characters in the string to lowercase.
+ # Converts characters in the string to lowercase.
#
- # Example:
# 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
def downcase
- chars(Unicode.apply_mapping @wrapped_string, :lowercase_mapping)
+ chars Unicode.downcase(@wrapped_string)
+ end
+
+ # Converts characters in the string to the opposite case.
+ #
+ # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
+ def swapcase
+ chars Unicode.swapcase(@wrapped_string)
end
# Converts the first character to uppercase and the remainder to lowercase.
#
- # Example:
# 'über'.mb_chars.capitalize.to_s # => "Über"
def capitalize
(slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
@@ -362,11 +130,10 @@ module ActiveSupport #:nodoc:
# Capitalizes the first letter of every word, when possible.
#
- # Example:
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
# "日本語".mb_chars.titleize # => "日本語"
def titleize
- chars(downcase.to_s.gsub(/\b('?[\S])/u) { Unicode.apply_mapping $1, :uppercase_mapping })
+ chars(downcase.to_s.gsub(/\b('?[\S])/u) { Unicode.upcase($1)})
end
alias_method :titlecase, :titleize
@@ -382,29 +149,26 @@ module ActiveSupport #:nodoc:
# Performs canonical decomposition on all the characters.
#
- # Example:
# 'é'.length # => 2
# 'é'.mb_chars.decompose.to_s.length # => 3
def decompose
- chars(Unicode.decompose_codepoints(:canonical, Unicode.u_unpack(@wrapped_string)).pack('U*'))
+ chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*'))
end
# Performs composition on all the characters.
#
- # Example:
# 'é'.length # => 3
# 'é'.mb_chars.compose.to_s.length # => 2
def compose
- chars(Unicode.compose_codepoints(Unicode.u_unpack(@wrapped_string)).pack('U*'))
+ chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*'))
end
# Returns the number of grapheme clusters in the string.
#
- # Example:
# 'क्षि'.mb_chars.length # => 4
- # 'क्षि'.mb_chars.g_length # => 3
- def g_length
- Unicode.g_unpack(@wrapped_string).length
+ # 'क्षि'.mb_chars.grapheme_length # => 3
+ def grapheme_length
+ Unicode.unpack_graphemes(@wrapped_string).length
end
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent resulting in a valid UTF-8 string.
@@ -414,14 +178,14 @@ module ActiveSupport #:nodoc:
chars(Unicode.tidy_bytes(@wrapped_string, force))
end
- %w(capitalize downcase lstrip reverse rstrip slice strip tidy_bytes upcase).each do |method|
- # Only define a corresponding bang method for methods defined in the proxy; On 1.9 the proxy will
- # exclude lstrip!, rstrip! and strip! because they are already work as expected on multibyte strings.
- if public_method_defined?(method)
- define_method("#{method}!") do |*args|
- @wrapped_string = send(args.nil? ? method : method, *args).to_s
- self
- end
+ def as_json(options = nil) #:nodoc:
+ to_s.as_json(options)
+ end
+
+ %w(capitalize downcase reverse tidy_bytes upcase).each do |method|
+ define_method("#{method}!") do |*args|
+ @wrapped_string = send(method, *args).to_s
+ self
end
end
@@ -431,43 +195,14 @@ module ActiveSupport #:nodoc:
return nil if byte_offset.nil?
return 0 if @wrapped_string == ''
- if @wrapped_string.respond_to?(:force_encoding)
- @wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT)
- end
-
begin
- @wrapped_string[0...byte_offset].unpack('U*').length
+ @wrapped_string.byteslice(0...byte_offset).unpack('U*').length
rescue ArgumentError
byte_offset -= 1
retry
end
end
- def justify(integer, way, padstr=' ') #:nodoc:
- raise ArgumentError, "zero width padding" if padstr.length == 0
- padsize = integer - size
- padsize = padsize > 0 ? padsize : 0
- case way
- when :right
- result = @wrapped_string.dup.insert(0, padding(padsize, padstr))
- when :left
- result = @wrapped_string.dup.insert(-1, padding(padsize, padstr))
- when :center
- lpad = padding((padsize / 2.0).floor, padstr)
- rpad = padding((padsize / 2.0).ceil, padstr)
- result = @wrapped_string.dup.insert(0, lpad).insert(-1, rpad)
- end
- chars(result)
- end
-
- def padding(padsize, padstr=' ') #:nodoc:
- if padsize != 0
- chars(padstr * ((padsize / Unicode.u_unpack(padstr).size) + 1)).slice(0, padsize)
- else
- ''
- end
- end
-
def chars(string) #:nodoc:
self.class.new(string)
end
diff --git a/activesupport/lib/active_support/multibyte/exceptions.rb b/activesupport/lib/active_support/multibyte/exceptions.rb
deleted file mode 100644
index 62066e3c71..0000000000
--- a/activesupport/lib/active_support/multibyte/exceptions.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# encoding: utf-8
-
-module ActiveSupport #:nodoc:
- module Multibyte #:nodoc:
- # Raised when a problem with the encoding was found.
- class EncodingError < StandardError; end
- end
-end \ No newline at end of file
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 754ca9290b..678f551193 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -10,12 +10,11 @@ module ActiveSupport
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = '5.2.0'
+ UNICODE_VERSION = '6.1.0'
# The default normalization used for operations that require normalization. It can be set to any of the
# normalizations in NORMALIZATION_FORMS.
#
- # Example:
# ActiveSupport::Multibyte::Unicode.default_normalization_form = :c
attr_accessor :default_normalization_form
@default_normalization_form = :kc
@@ -61,19 +60,6 @@ module ActiveSupport
TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u
LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u
- # Unpack the string at codepoints boundaries. Raises an EncodingError when the encoding of the string isn't
- # valid UTF-8.
- #
- # Example:
- # Unicode.u_unpack('Café') # => [67, 97, 102, 233]
- def u_unpack(string)
- begin
- string.unpack 'U*'
- rescue ArgumentError
- raise EncodingError, 'malformed UTF-8 character'
- end
- end
-
# Detect whether the codepoint is in a certain character class. Returns +true+ when it's in the specified
# character class and +false+ otherwise. Valid character classes are: <tt>:cr</tt>, <tt>:lf</tt>, <tt>:l</tt>,
# <tt>:v</tt>, <tt>:lv</tt>, <tt>:lvt</tt> and <tt>:t</tt>.
@@ -85,11 +71,10 @@ module ActiveSupport
# Unpack the string at grapheme boundaries. Returns a list of character lists.
#
- # Example:
- # Unicode.g_unpack('क्षि') # => [[2325, 2381], [2359], [2367]]
- # Unicode.g_unpack('Café') # => [[67], [97], [102], [233]]
- def g_unpack(string)
- codepoints = u_unpack(string)
+ # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]]
+ # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]]
+ def unpack_graphemes(string)
+ codepoints = string.codepoints.to_a
unpacked = []
pos = 0
marker = 0
@@ -118,12 +103,11 @@ module ActiveSupport
unpacked
end
- # Reverse operation of g_unpack.
+ # Reverse operation of unpack_graphemes.
#
- # Example:
- # Unicode.g_pack(Unicode.g_unpack('क्षि')) # => 'क्षि'
- def g_pack(unpacked)
- (unpacked.flatten).pack('U*')
+ # Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि'
+ def pack_graphemes(unpacked)
+ unpacked.flatten.pack('U*')
end
# Re-order codepoints so the string becomes canonical.
@@ -143,7 +127,7 @@ module ActiveSupport
end
# Decompose composed characters to the decomposed form.
- def decompose_codepoints(type, codepoints)
+ def decompose(type, codepoints)
codepoints.inject([]) do |decomposed, cp|
# if it's a hangul syllable starter character
if HANGUL_SBASE <= cp and cp < HANGUL_SLAST
@@ -156,7 +140,7 @@ module ActiveSupport
decomposed.concat ncp
# if the codepoint is decomposable in with the current decomposition type
elsif (ncp = database.codepoints[cp].decomp_mapping) and (!database.codepoints[cp].decomp_type || type == :compatability)
- decomposed.concat decompose_codepoints(type, ncp.dup)
+ decomposed.concat decompose(type, ncp.dup)
else
decomposed << cp
end
@@ -164,7 +148,7 @@ module ActiveSupport
end
# Compose decomposed characters to the composed form.
- def compose_codepoints(codepoints)
+ def compose(codepoints)
pos = 0
eoa = codepoints.length - 1
starter_pos = 0
@@ -283,35 +267,40 @@ module ActiveSupport
def normalize(string, form=nil)
form ||= @default_normalization_form
# See http://www.unicode.org/reports/tr15, Table 1
- codepoints = u_unpack(string)
+ codepoints = string.codepoints.to_a
case form
when :d
- reorder_characters(decompose_codepoints(:canonical, codepoints))
+ reorder_characters(decompose(:canonical, codepoints))
when :c
- compose_codepoints(reorder_characters(decompose_codepoints(:canonical, codepoints)))
+ compose(reorder_characters(decompose(:canonical, codepoints)))
when :kd
- reorder_characters(decompose_codepoints(:compatability, codepoints))
+ reorder_characters(decompose(:compatability, codepoints))
when :kc
- compose_codepoints(reorder_characters(decompose_codepoints(:compatability, codepoints)))
+ compose(reorder_characters(decompose(:compatability, codepoints)))
else
raise ArgumentError, "#{form} is not a valid normalization variant", caller
end.pack('U*')
end
- def apply_mapping(string, mapping) #:nodoc:
- u_unpack(string).map do |codepoint|
- cp = database.codepoints[codepoint]
- if cp and (ncp = cp.send(mapping)) and ncp > 0
- ncp
- else
- codepoint
- end
- end.pack('U*')
+ def downcase(string)
+ apply_mapping string, :lowercase_mapping
+ end
+
+ def upcase(string)
+ apply_mapping string, :uppercase_mapping
+ end
+
+ def swapcase(string)
+ apply_mapping string, :swapcase_mapping
end
# Holds data about a codepoint in the Unicode database
class Codepoint
attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping
+
+ def swapcase_mapping
+ uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping
+ end
end
# Holds static data from the Unicode database
@@ -374,6 +363,17 @@ module ActiveSupport
private
+ def apply_mapping(string, mapping) #:nodoc:
+ string.each_codepoint.map do |codepoint|
+ cp = database.codepoints[codepoint]
+ if cp and (ncp = cp.send(mapping)) and ncp > 0
+ ncp
+ else
+ codepoint
+ end
+ end.pack('U*')
+ end
+
def tidy_byte(byte)
if byte < 160
[database.cp1252[byte] || byte].pack("U").unpack("C*")
diff --git a/activesupport/lib/active_support/multibyte/utils.rb b/activesupport/lib/active_support/multibyte/utils.rb
deleted file mode 100644
index 94b393cee2..0000000000
--- a/activesupport/lib/active_support/multibyte/utils.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# encoding: utf-8
-
-module ActiveSupport #:nodoc:
- module Multibyte #:nodoc:
- if Kernel.const_defined?(:Encoding)
- # Returns a regular expression that matches valid characters in the current encoding
- def self.valid_character
- VALID_CHARACTER[Encoding.default_external.to_s]
- end
- else
- def self.valid_character
- case $KCODE
- when 'UTF8'
- VALID_CHARACTER['UTF-8']
- when 'SJIS'
- VALID_CHARACTER['Shift_JIS']
- end
- end
- end
-
- if 'string'.respond_to?(:valid_encoding?)
- # Verifies the encoding of a string
- def self.verify(string)
- string.valid_encoding?
- end
- else
- def self.verify(string)
- if expression = valid_character
- # Splits the string on character boundaries, which are determined based on $KCODE.
- string.split(//).all? { |c| expression =~ c }
- else
- true
- end
- end
- end
-
- # Verifies the encoding of the string and raises an exception when it's not valid
- def self.verify!(string)
- raise EncodingError.new("Found characters with invalid encoding") unless verify(string)
- end
-
- if 'string'.respond_to?(:force_encoding)
- # Removes all invalid characters from the string.
- #
- # Note: this method is a no-op in Ruby 1.9
- def self.clean(string)
- string
- end
- else
- def self.clean(string)
- if expression = valid_character
- # Splits the string on character boundaries, which are determined based on $KCODE.
- string.split(//).grep(expression).join
- else
- string
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index b5a70d5933..6735c561d3 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -1,44 +1,140 @@
+require 'active_support/notifications/instrumenter'
+require 'active_support/notifications/fanout'
+
module ActiveSupport
- # Notifications provides an instrumentation API for Ruby. To instrument an
- # action in Ruby you just need to do:
+ # = Notifications
+ #
+ # <tt>ActiveSupport::Notifications</tt> provides an instrumentation API for Ruby.
+ #
+ # == Instrumenters
#
- # ActiveSupport::Notifications.instrument(:render, :extra => :information) do
+ # To instrument an event you just need to do:
+ #
+ # ActiveSupport::Notifications.instrument("render", :extra => :information) do
# render :text => "Foo"
# end
#
+ # That executes the block first and notifies all subscribers once done.
+ #
+ # In the example above "render" is the name of the event, and the rest is called
+ # the _payload_. The payload is a mechanism that allows instrumenters to pass
+ # extra information to subscribers. Payloads consist of a hash whose contents
+ # are arbitrary and generally depend on the event.
+ #
+ # == Subscribers
+ #
# You can consume those events and the information they provide by registering
- # a log subscriber. For instance, let's store all instrumented events in an array:
+ # a subscriber. For instance, let's store all "render" events in an array:
#
- # @events = []
+ # events = []
#
- # ActiveSupport::Notifications.subscribe do |*args|
- # @events << ActiveSupport::Notifications::Event.new(*args)
+ # ActiveSupport::Notifications.subscribe("render") do |*args|
+ # events << ActiveSupport::Notifications::Event.new(*args)
# end
#
- # ActiveSupport::Notifications.instrument(:render, :extra => :information) do
+ # That code returns right away, you are just subscribing to "render" events.
+ # The block will be called asynchronously whenever someone instruments "render":
+ #
+ # ActiveSupport::Notifications.instrument("render", :extra => :information) do
# render :text => "Foo"
# end
#
- # event = @events.first
- # event.name # => :render
+ # event = events.first
+ # event.name # => "render"
# event.duration # => 10 (in milliseconds)
# event.payload # => { :extra => :information }
#
- # When subscribing to Notifications, you can pass a pattern, to only consume
- # events that match the pattern:
+ # The block in the <tt>subscribe</tt> call gets the name of the event, start
+ # timestamp, end timestamp, a string with a unique identifier for that event
+ # (something like "535801666f04d0298cd6"), and a hash with the payload, in
+ # that order.
+ #
+ # If an exception happens during that particular instrumentation the payload will
+ # have a key <tt>:exception</tt> with an array of two elements as value: a string with
+ # the name of the exception class, and the exception message.
+ #
+ # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
+ # is able to take the arguments as they come and provide an object-oriented
+ # interface to that data.
+ #
+ # It is also possible to pass an object as the second parameter passed to the
+ # <tt>subscribe</tt> method instead of a block:
+ #
+ # module ActionController
+ # class PageRequest
+ # def call(name, started, finished, unique_id, payload)
+ # Rails.logger.debug ["notification:", name, started, finished, unique_id, payload].join(" ")
+ # end
+ # end
+ # end
+ #
+ # ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new)
+ #
+ # resulting in the following output within the logs including a hash with the payload:
+ #
+ # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 {
+ # :controller=>"Devise::SessionsController",
+ # :action=>"new",
+ # :params=>{"action"=>"new", "controller"=>"devise/sessions"},
+ # :format=>:html,
+ # :method=>"GET",
+ # :path=>"/login/sign_in",
+ # :status=>200,
+ # :view_runtime=>279.3080806732178,
+ # :db_runtime=>40.053
+ # }
+ #
+ # You can also subscribe to all events whose name matches a certain regexp:
+ #
+ # ActiveSupport::Notifications.subscribe(/render/) do |*args|
+ # ...
+ # end
+ #
+ # and even pass no argument to <tt>subscribe</tt>, in which case you are subscribing
+ # to all events.
+ #
+ # == Temporary Subscriptions
+ #
+ # Sometimes you do not want to subscribe to an event for the entire life of
+ # the application. There are two ways to unsubscribe.
+ #
+ # WARNING: The instrumentation framework is designed for long-running subscribers,
+ # use this feature sparingly because it wipes some internal caches and that has
+ # a negative impact on performance.
+ #
+ # === Subscribe While a Block Runs
+ #
+ # You can subscribe to some event temporarily while some block runs. For
+ # example, in
+ #
+ # callback = lambda {|*args| ... }
+ # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do
+ # ...
+ # end
#
- # ActiveSupport::Notifications.subscribe(/render/) do |event|
- # @render_events << event
+ # the callback will be called for all "sql.active_record" events instrumented
+ # during the execution of the block. The callback is unsubscribed automatically
+ # after that.
+ #
+ # === Manual Unsubscription
+ #
+ # The +subscribe+ method returns a subscriber object:
+ #
+ # subscriber = ActiveSupport::Notifications.subscribe("render") do |*args|
+ # ...
# end
#
+ # To prevent that block from being called anymore, just unsubscribe passing
+ # that reference:
+ #
+ # ActiveSupport::Notifications.unsubscribe(subscriber)
+ #
+ # == Default Queue
+ #
# Notifications ships with a queue implementation that consumes and publish events
# to log subscribers in a thread. You can use any queue implementation you want.
#
module Notifications
- autoload :Instrumenter, 'active_support/notifications/instrumenter'
- autoload :Event, 'active_support/notifications/instrumenter'
- autoload :Fanout, 'active_support/notifications/fanout'
-
@instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) }
class << self
@@ -62,6 +158,13 @@ module ActiveSupport
end
end
+ def subscribed(callback, *args, &block)
+ subscriber = subscribe(*args, &callback)
+ yield
+ ensure
+ unsubscribe(subscriber)
+ end
+
def unsubscribe(args)
notifier.unsubscribe(args)
@instrumenters.clear
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index a9aa5464e9..17c99089c1 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -9,18 +9,25 @@ module ActiveSupport
end
def subscribe(pattern = nil, block = Proc.new)
- subscriber = Subscriber.new(pattern, block).tap do |s|
- @subscribers << s
- end
+ subscriber = Subscribers.new pattern, block
+ @subscribers << subscriber
@listeners_for.clear
subscriber
end
def unsubscribe(subscriber)
- @subscribers.reject! {|s| s.matches?(subscriber)}
+ @subscribers.reject! { |s| s.matches?(subscriber) }
@listeners_for.clear
end
+ def start(name, id, payload)
+ listeners_for(name).each { |s| s.start(name, id, payload) }
+ end
+
+ def finish(name, id, payload)
+ listeners_for(name).each { |s| s.finish(name, id, payload) }
+ end
+
def publish(name, *args)
listeners_for(name).each { |s| s.publish(name, *args) }
end
@@ -37,23 +44,89 @@ module ActiveSupport
def wait
end
- class Subscriber #:nodoc:
- def initialize(pattern, delegate)
- @pattern = pattern
- @delegate = delegate
+ module Subscribers # :nodoc:
+ def self.new(pattern, listener)
+ if listener.respond_to?(:call)
+ subscriber = Timed.new pattern, listener
+ else
+ subscriber = Evented.new pattern, listener
+ end
+
+ unless pattern
+ AllMessages.new(subscriber)
+ else
+ subscriber
+ end
end
- def publish(message, *args)
- @delegate.call(message, *args)
+ class Evented #:nodoc:
+ def initialize(pattern, delegate)
+ @pattern = pattern
+ @delegate = delegate
+ end
+
+ def start(name, id, payload)
+ @delegate.start name, id, payload
+ end
+
+ def finish(name, id, payload)
+ @delegate.finish name, id, payload
+ end
+
+ def subscribed_to?(name)
+ @pattern === name.to_s
+ end
+
+ def matches?(subscriber_or_name)
+ self === subscriber_or_name ||
+ @pattern && @pattern === subscriber_or_name
+ end
end
- def subscribed_to?(name)
- !@pattern || @pattern === name.to_s
+ class Timed < Evented
+ def initialize(pattern, delegate)
+ @timestack = Hash.new { |h,id|
+ h[id] = Hash.new { |ids,name| ids[name] = [] }
+ }
+ super
+ end
+
+ def publish(name, *args)
+ @delegate.call name, *args
+ end
+
+ def start(name, id, payload)
+ @timestack[id][name].push Time.now
+ end
+
+ def finish(name, id, payload)
+ started = @timestack[id][name].pop
+ @delegate.call(name, started, Time.now, id, payload)
+ end
end
- def matches?(subscriber_or_name)
- self === subscriber_or_name ||
- @pattern && @pattern === subscriber_or_name
+ class AllMessages # :nodoc:
+ def initialize(delegate)
+ @delegate = delegate
+ end
+
+ def start(name, id, payload)
+ @delegate.start name, id, payload
+ end
+
+ def finish(name, id, payload)
+ @delegate.finish name, id, payload
+ end
+
+ def publish(name, *args)
+ @delegate.publish name, *args
+ end
+
+ def subscribed_to?(name)
+ true
+ end
+
+ alias :matches? :===
end
end
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 3941c285a2..58e292c658 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -1,7 +1,6 @@
-require 'active_support/core_ext/module/delegation'
-
module ActiveSupport
module Notifications
+ # Instrumentors are stored in a thread local.
class Instrumenter
attr_reader :id
@@ -14,15 +13,14 @@ module ActiveSupport
# and publish it. Notice that events get sent even if an error occurs
# in the passed-in block
def instrument(name, payload={})
- started = Time.now
-
+ @notifier.start(name, @id, payload)
begin
yield
rescue Exception => e
payload[:exception] = [e.class.name, e.message]
raise e
ensure
- @notifier.publish(name, started, Time.now, @id, payload)
+ @notifier.finish(name, @id, payload)
end
end
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 0264133581..1a3693f766 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -1,8 +1,3 @@
-begin
- require 'psych'
-rescue LoadError
-end
-
require 'yaml'
YAML.add_builtin_type("omap") do |type, val|
@@ -10,17 +5,21 @@ YAML.add_builtin_type("omap") do |type, val|
end
module ActiveSupport
- # The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the
- # order in which +keys+ will return keys, or +each+ yield pairs. <tt>ActiveSupport::OrderedHash</tt>
- # implements a hash that preserves insertion order, as in Ruby 1.9:
+ # <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
+ # insertion order.
#
# oh = ActiveSupport::OrderedHash.new
# oh[:a] = 1
# oh[:b] = 2
# oh.keys # => [:a, :b], this order is guaranteed
#
- # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations.
- class OrderedHash < ::Hash #:nodoc:
+ # Also, maps the +omap+ feature for YAML files
+ # (See http://yaml.org/type/omap.html) to support ordered items
+ # when loading from yaml.
+ #
+ # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts
+ # with other implementations.
+ class OrderedHash < ::Hash
def to_yaml_type
"!tag:yaml.org,2002:omap"
end
@@ -29,20 +28,6 @@ module ActiveSupport
coder.represent_seq '!omap', map { |k,v| { k => v } }
end
- def to_yaml(opts = {})
- if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
- return super
- end
-
- YAML.quick_emit(self, opts) do |out|
- out.seq(taguri) do |seq|
- each do |k, v|
- seq.add(k => v)
- end
- end
- end
- end
-
def nested_under_indifferent_access
self
end
@@ -51,172 +36,5 @@ module ActiveSupport
def extractable_options?
true
end
-
- # Hash is ordered in Ruby 1.9!
- if RUBY_VERSION < '1.9'
-
- # In MRI the Hash class is core and written in C. In particular, methods are
- # programmed with explicit C function calls and polymorphism is not honored.
- #
- # For example, []= is crucial in this implementation to maintain the @keys
- # array but hash.c invokes rb_hash_aset() originally. This prevents method
- # reuse through inheritance and forces us to reimplement stuff.
- #
- # For instance, we cannot use the inherited #merge! because albeit the algorithm
- # itself would work, our []= is not being called at all by the C code.
-
- def initialize(*args, &block)
- super
- @keys = []
- end
-
- def self.[](*args)
- ordered_hash = new
-
- if (args.length == 1 && args.first.is_a?(Array))
- args.first.each do |key_value_pair|
- next unless (key_value_pair.is_a?(Array))
- ordered_hash[key_value_pair[0]] = key_value_pair[1]
- end
-
- return ordered_hash
- end
-
- unless (args.size % 2 == 0)
- raise ArgumentError.new("odd number of arguments for Hash")
- end
-
- args.each_with_index do |val, ind|
- next if (ind % 2 != 0)
- ordered_hash[val] = args[ind + 1]
- end
-
- ordered_hash
- end
-
- def initialize_copy(other)
- super
- # make a deep copy of keys
- @keys = other.keys
- end
-
- def []=(key, value)
- @keys << key unless has_key?(key)
- super
- end
-
- def delete(key)
- if has_key? key
- index = @keys.index(key)
- @keys.delete_at index
- end
- super
- end
-
- def delete_if
- super
- sync_keys!
- self
- end
-
- def reject!
- super
- sync_keys!
- self
- end
-
- def reject(&block)
- dup.reject!(&block)
- end
-
- def keys
- @keys.dup
- end
-
- def values
- @keys.collect { |key| self[key] }
- end
-
- def to_hash
- self
- end
-
- def to_a
- @keys.map { |key| [ key, self[key] ] }
- end
-
- def each_key
- return to_enum(:each_key) unless block_given?
- @keys.each { |key| yield key }
- self
- end
-
- def each_value
- return to_enum(:each_value) unless block_given?
- @keys.each { |key| yield self[key]}
- self
- end
-
- def each
- return to_enum(:each) unless block_given?
- @keys.each {|key| yield [key, self[key]]}
- self
- end
-
- def each_pair
- return to_enum(:each_pair) unless block_given?
- @keys.each {|key| yield key, self[key]}
- self
- end
-
- alias_method :select, :find_all
-
- def clear
- super
- @keys.clear
- self
- end
-
- def shift
- k = @keys.first
- v = delete(k)
- [k, v]
- end
-
- def merge!(other_hash)
- if block_given?
- other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
- else
- other_hash.each { |k, v| self[k] = v }
- end
- self
- end
-
- alias_method :update, :merge!
-
- def merge(other_hash, &block)
- dup.merge!(other_hash, &block)
- end
-
- # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
- def replace(other)
- super
- @keys = other.keys
- self
- end
-
- def invert
- OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
- end
-
- def inspect
- "#<OrderedHash #{super}>"
- end
-
- private
- def sync_keys!
- @keys.delete_if {|k| !has_key?(k)}
- end
- end
end
end
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index bf81567d22..60e6cd55ad 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -1,5 +1,3 @@
-require 'active_support/ordered_hash'
-
# Usually key value pairs are handled something like this:
#
# h = {}
@@ -17,7 +15,7 @@ require 'active_support/ordered_hash'
# h.girl # => 'Mary'
#
module ActiveSupport #:nodoc:
- class OrderedOptions < OrderedHash
+ class OrderedOptions < Hash
alias_method :_get, :[] # preserve the original #[] method
protected :_get # make it protected
@@ -30,14 +28,15 @@ module ActiveSupport #:nodoc:
end
def method_missing(name, *args)
- if name.to_s =~ /(.*)=$/
- self[$1] = args.first
+ name_string = name.to_s
+ if name_string.chomp!('=')
+ self[name_string] = args.first
else
self[name]
end
end
- def respond_to?(name)
+ def respond_to_missing?(name, include_private)
true
end
end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 1638512af0..30ac881090 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -5,39 +5,9 @@ module ActiveSupport
class Railtie < Rails::Railtie
config.active_support = ActiveSupport::OrderedOptions.new
- # Loads support for "whiny nil" (noisy warnings when methods are invoked
- # on +nil+ values) if Configuration#whiny_nils is true.
- initializer "active_support.initialize_whiny_nils" do |app|
- require 'active_support/whiny_nil' if app.config.whiny_nils
- end
-
initializer "active_support.deprecation_behavior" do |app|
if deprecation = app.config.active_support.deprecation
ActiveSupport::Deprecation.behavior = deprecation
- else
- defaults = {"development" => :log,
- "production" => :notify,
- "test" => :stderr}
-
- env = Rails.env
-
- if defaults.key?(env)
- msg = "You did not specify how you would like Rails to report " \
- "deprecation notices for your #{env} environment, please " \
- "set config.active_support.deprecation to :#{defaults[env]} " \
- "at config/environments/#{env}.rb"
-
- warn msg
- ActiveSupport::Deprecation.behavior = defaults[env]
- else
- msg = "You did not specify how you would like Rails to report " \
- "deprecation notices for your #{env} environment, please " \
- "set config.active_support.deprecation to :log, :notify or " \
- ":stderr at config/environments/#{env}.rb"
-
- warn msg
- ActiveSupport::Deprecation.behavior = :stderr
- end
end
end
@@ -48,12 +18,18 @@ module ActiveSupport
zone_default = Time.find_zone!(app.config.time_zone)
unless zone_default
- raise \
- 'Value assigned to config.time_zone not recognized.' +
+ raise 'Value assigned to config.time_zone not recognized. ' \
'Run "rake -D time" for a list of tasks for finding appropriate time zone names.'
end
Time.zone_default = zone_default
end
+
+ initializer "active_support.set_configs" do |app|
+ app.config.active_support.each do |k, v|
+ k = "#{k}="
+ ActiveSupport.send(k, v) if ActiveSupport.respond_to? k
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index 0f4a06468a..7aecdd11d3 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -48,6 +48,7 @@ module ActiveSupport
# end
# end
#
+ # Exceptions raised inside exception handlers are not propagated up.
def rescue_from(*klasses, &block)
options = klasses.extract_options!
@@ -108,7 +109,11 @@ module ActiveSupport
when Symbol
method(rescuer)
when Proc
- rescuer.bind(self)
+ if rescuer.arity == 0
+ Proc.new { instance_exec(&rescuer) }
+ else
+ Proc.new { |_exception| instance_exec(_exception, &rescuer) }
+ end
end
end
end
diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb
deleted file mode 100644
index 608b3fe4b9..0000000000
--- a/activesupport/lib/active_support/ruby/shim.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# Backported Ruby builtins so you can code with the latest & greatest
-# but still run on any Ruby 1.8.x.
-#
-# Date next_year, next_month
-# DateTime to_date, to_datetime, xmlschema
-# Enumerable group_by, each_with_object, none?
-# Process Process.daemon
-# REXML security fix
-# String ord
-# Time to_date, to_time, to_datetime
-require 'active_support'
-require 'active_support/core_ext/date/calculations'
-require 'active_support/core_ext/date_time/conversions'
-require 'active_support/core_ext/enumerable'
-require 'active_support/core_ext/process/daemon'
-require 'active_support/core_ext/string/conversions'
-require 'active_support/core_ext/string/interpolation'
-require 'active_support/core_ext/string/encoding'
-require 'active_support/core_ext/rexml'
-require 'active_support/core_ext/time/conversions'
-require 'active_support/core_ext/file/path'
-require 'active_support/core_ext/module/method_names' \ No newline at end of file
diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb
index e6b1f39225..f3f3909a90 100644
--- a/activesupport/lib/active_support/string_inquirer.rb
+++ b/activesupport/lib/active_support/string_inquirer.rb
@@ -11,8 +11,8 @@ module ActiveSupport
#
class StringInquirer < String
def method_missing(method_name, *arguments)
- if method_name.to_s[-1,1] == "?"
- self == method_name.to_s[0..-2]
+ if method_name[-1, 1] == "?"
+ self == method_name[0..-2]
else
super
end
diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb
new file mode 100644
index 0000000000..5e080df518
--- /dev/null
+++ b/activesupport/lib/active_support/tagged_logging.rb
@@ -0,0 +1,58 @@
+require 'active_support/core_ext/object/blank'
+require 'logger'
+require 'active_support/logger'
+
+module ActiveSupport
+ # Wraps any standard Logger object to provide tagging capabilities.
+ #
+ # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
+ # logger.tagged("BCX") { logger.info "Stuff" } # Logs "[BCX] Stuff"
+ # logger.tagged("BCX", "Jason") { logger.info "Stuff" } # Logs "[BCX] [Jason] Stuff"
+ # logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"
+ #
+ # This is used by the default Rails.logger as configured by Railties to make it easy to stamp log lines
+ # with subdomains, request ids, and anything else to aid debugging of multi-user production applications.
+ module TaggedLogging
+ module Formatter # :nodoc:
+ # This method is invoked when a log event occurs
+ def call(severity, timestamp, progname, msg)
+ super(severity, timestamp, progname, "#{tags_text}#{msg}")
+ end
+
+ def clear!
+ current_tags.clear
+ end
+
+ def current_tags
+ Thread.current[:activesupport_tagged_logging_tags] ||= []
+ end
+
+ private
+ def tags_text
+ tags = current_tags
+ if tags.any?
+ tags.collect { |tag| "[#{tag}] " }.join
+ end
+ end
+ end
+
+ def self.new(logger)
+ logger.formatter.extend Formatter
+ logger.extend(self)
+ end
+
+ def tagged(*new_tags)
+ tags = formatter.current_tags
+ new_tags = new_tags.flatten.reject(&:blank?)
+ tags.concat new_tags
+ yield self
+ ensure
+ tags.pop(new_tags.size)
+ end
+
+ def flush
+ formatter.clear!
+ super if defined?(super)
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 8d6c27e381..9a52c916ec 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -1,34 +1,55 @@
-require 'test/unit/testcase'
+require 'minitest/spec'
require 'active_support/testing/setup_and_teardown'
require 'active_support/testing/assertions'
require 'active_support/testing/deprecation'
require 'active_support/testing/declarative'
-require 'active_support/testing/pending'
require 'active_support/testing/isolation'
require 'active_support/testing/mochaing'
require 'active_support/core_ext/kernel/reporting'
module ActiveSupport
- class TestCase < ::Test::Unit::TestCase
- if defined? MiniTest
- Assertion = MiniTest::Assertion
- alias_method :method_name, :name if method_defined? :name
- alias_method :method_name, :__name__ if method_defined? :__name__
- else
- Assertion = Test::Unit::AssertionFailedError
-
- undef :default_test
+ class TestCase < ::MiniTest::Spec
+
+ if MiniTest::Unit::VERSION < '2.6.1'
+ class << self
+ alias :name :to_s
+ end
+ end
+
+ # Use AS::TestCase for the base class when describing a model
+ register_spec_type(self) do |desc|
+ desc < ActiveRecord::Model
end
+ Assertion = MiniTest::Assertion
+ alias_method :method_name, :name if method_defined? :name
+ alias_method :method_name, :__name__ if method_defined? :__name__
+
$tags = {}
def self.for_tag(tag)
yield if $tags[tag]
end
+ # FIXME: we have tests that depend on run order, we should fix that and
+ # remove this method.
+ def self.test_order # :nodoc:
+ :sorted
+ end
+
include ActiveSupport::Testing::SetupAndTeardown
include ActiveSupport::Testing::Assertions
include ActiveSupport::Testing::Deprecation
- include ActiveSupport::Testing::Pending
extend ActiveSupport::Testing::Declarative
+
+ # test/unit backwards compatibility methods
+ alias :assert_raise :assert_raises
+ alias :assert_not_nil :refute_nil
+ alias :assert_not_equal :refute_equal
+ alias :assert_no_match :refute_match
+ alias :assert_not_same :refute_same
+
+ def assert_nothing_raised(*args)
+ yield
+ end
end
end
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index f3629ada5b..d84595fa8f 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
module ActiveSupport
@@ -45,7 +44,7 @@ module ActiveSupport
# post :delete, :id => ...
# end
def assert_difference(expression, difference = 1, message = nil, &block)
- expressions = Array.wrap expression
+ expressions = Array(expression)
exps = expressions.map { |e|
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
@@ -87,7 +86,7 @@ module ActiveSupport
# Test if an expression is not blank. Passes if object.present? is true.
#
- # assert_present {:data => 'x' } # => true
+ # assert_present({:data => 'x' }) # => true
def assert_present(object, message=nil)
message ||= "#{object.inspect} is blank"
assert object.present?, message
diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb
index 0135185a47..a8342904dc 100644
--- a/activesupport/lib/active_support/testing/deprecation.rb
+++ b/activesupport/lib/active_support/testing/deprecation.rb
@@ -34,22 +34,3 @@ module ActiveSupport
end
end
end
-
-begin
- require 'test/unit/error'
-rescue LoadError
- # Using miniunit, ignore.
-else
- module Test
- module Unit
- class Error #:nodoc:
- # Silence warnings when reporting test errors.
- def message_with_silenced_deprecation
- ActiveSupport::Deprecation.silence { message_without_silenced_deprecation }
- end
- alias_method :message_without_silenced_deprecation, :message
- alias_method :message, :message_with_silenced_deprecation
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 6b29ba4c10..1a0681e850 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -37,14 +37,6 @@ module ActiveSupport
!ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/))
end
- def self.included(base)
- if defined?(::MiniTest) && base < ::MiniTest::Unit::TestCase
- base.send :include, MiniTest
- elsif defined?(Test::Unit)
- base.send :include, TestUnit
- end
- end
-
def _run_class_setup # class setup method should only happen in parent
unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST']
self.class.setup if self.class.respond_to?(:setup)
@@ -52,42 +44,16 @@ module ActiveSupport
end
end
- module TestUnit
- def run(result)
- _run_class_setup
-
- yield(Test::Unit::TestCase::STARTED, name)
-
- @_result = result
-
- serialized = run_in_isolation do |proxy|
- begin
- super(proxy) { }
- rescue Exception => e
- proxy.add_error(Test::Unit::Error.new(name, e))
- end
- end
-
- retval, proxy = Marshal.load(serialized)
- proxy.__replay__(@_result)
+ def run(runner)
+ _run_class_setup
- yield(Test::Unit::TestCase::FINISHED, name)
- retval
+ serialized = run_in_isolation do |isolated_runner|
+ super(isolated_runner)
end
- end
-
- module MiniTest
- def run(runner)
- _run_class_setup
- serialized = run_in_isolation do |isolated_runner|
- super(isolated_runner)
- end
-
- retval, proxy = Marshal.load(serialized)
- proxy.__replay__(runner)
- retval
- end
+ retval, proxy = Marshal.load(serialized)
+ proxy.__replay__(runner)
+ retval
end
module Forking
@@ -145,13 +111,3 @@ module ActiveSupport
end
end
end
-
-# Only in subprocess for windows / jruby.
-if ENV['ISOLATION_TEST']
- require "test/unit/collector/objectspace"
- class Test::Unit::Collector::ObjectSpace
- def include?(test)
- super && test.method_name == ENV['ISOLATION_TEST']
- end
- end
-end
diff --git a/activesupport/lib/active_support/testing/pending.rb b/activesupport/lib/active_support/testing/pending.rb
deleted file mode 100644
index feac7bc347..0000000000
--- a/activesupport/lib/active_support/testing/pending.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# Some code from jeremymcanally's "pending"
-# https://github.com/jeremymcanally/pending/tree/master
-
-module ActiveSupport
- module Testing
- module Pending
-
- unless defined?(Spec)
-
- @@pending_cases = []
- @@at_exit = false
-
- def pending(description = "", &block)
- if defined?(::MiniTest)
- skip(description.blank? ? nil : description)
- else
- if description.is_a?(Symbol)
- is_pending = $tags[description]
- return block.call unless is_pending
- end
-
- if block_given?
- failed = false
-
- begin
- block.call
- rescue Exception
- failed = true
- end
-
- flunk("<#{description}> did not fail.") unless failed
- end
-
- caller[0] =~ (/(.*):(.*):in `(.*)'/)
- @@pending_cases << "#{$3} at #{$1}, line #{$2}"
- print "P"
-
- @@at_exit ||= begin
- at_exit do
- puts "\nPending Cases:"
- @@pending_cases.each do |test_case|
- puts test_case
- end
- end
- end
- end
- end
- end
-
- end
- end
-end
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index dd23f8d82d..2bea0f991a 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -13,12 +13,6 @@ module ActiveSupport
included do
superclass_delegating_accessor :profile_options
self.profile_options = {}
-
- if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
- include ForMiniTest
- else
- include ForClassicTestUnit
- end
end
# each implementation should define metrics and freeze the defaults
@@ -41,82 +35,38 @@ module ActiveSupport
"#{self.class.name}##{method_name}"
end
- module ForMiniTest
- def run(runner)
- @runner = runner
+ def run(runner)
+ @runner = runner
- run_warmup
- if full_profile_options && metrics = full_profile_options[:metrics]
- metrics.each do |metric_name|
- if klass = Metrics[metric_name.to_sym]
- run_profile(klass.new)
- end
+ run_warmup
+ if full_profile_options && metrics = full_profile_options[:metrics]
+ metrics.each do |metric_name|
+ if klass = Metrics[metric_name.to_sym]
+ run_profile(klass.new)
end
end
-
- return
end
- def run_test(metric, mode)
- result = '.'
- begin
- run_callbacks :setup
- setup
- metric.send(mode) { __send__ method_name }
- rescue Exception => e
- result = @runner.puke(self.class, method_name, e)
- ensure
- begin
- teardown
- run_callbacks :teardown, :enumerator => :reverse_each
- rescue Exception => e
- result = @runner.puke(self.class, method_name, e)
- end
- end
- result
- end
+ return
end
- module ForClassicTestUnit
- def run(result)
- return if method_name =~ /^default_test$/
-
- yield(self.class::STARTED, name)
- @_result = result
-
- run_warmup
- if full_profile_options && metrics = full_profile_options[:metrics]
- metrics.each do |metric_name|
- if klass = Metrics[metric_name.to_sym]
- run_profile(klass.new)
- result.add_run
- else
- puts '%20s: unsupported' % metric_name
- end
- end
- end
-
- yield(self.class::FINISHED, name)
- end
-
- def run_test(metric, mode)
+ def run_test(metric, mode)
+ result = '.'
+ begin
run_callbacks :setup
setup
- metric.send(mode) { __send__ @method_name }
- rescue ::Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue StandardError, ScriptError => e
- add_error(e)
+ metric.send(mode) { __send__ method_name }
+ rescue Exception => e
+ result = @runner.puke(self.class, method_name, e)
ensure
begin
teardown
- run_callbacks :teardown, :enumerator => :reverse_each
- rescue ::Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue StandardError, ScriptError => e
- add_error(e)
+ run_callbacks :teardown
+ rescue Exception => e
+ result = @runner.puke(self.class, method_name, e)
end
end
+ result
end
protected
@@ -176,7 +126,7 @@ module ActiveSupport
def record; end
end
- class Benchmarker < Performer
+ class Benchmarker < Performer
def initialize(*args)
super
@supported = @metric.respond_to?('measure')
@@ -208,8 +158,7 @@ module ActiveSupport
end
end
- ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
- ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
+ ruby = "#{RUBY_ENGINE}-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
@env = [app, rails, ruby, RUBY_PLATFORM] * ','
end
@@ -247,6 +196,7 @@ module ActiveSupport
class Base
include ActionView::Helpers::NumberHelper
+ include ActionView::Helpers::OutputSafetyHelper
attr_reader :total
@@ -258,7 +208,7 @@ module ActiveSupport
@name ||= self.class.name.demodulize.underscore
end
- def benchmark
+ def benchmark
with_gc_stats do
before = measure
yield
@@ -306,7 +256,6 @@ module ActiveSupport
end
end
-RUBY_ENGINE = 'ruby' unless defined?(RUBY_ENGINE) # mri 1.8
case RUBY_ENGINE
when 'ruby' then require 'active_support/testing/performance/ruby'
when 'rbx' then require 'active_support/testing/performance/rubinius'
diff --git a/activesupport/lib/active_support/testing/performance/jruby.rb b/activesupport/lib/active_support/testing/performance/jruby.rb
index b347539f13..34e3f9f45f 100644
--- a/activesupport/lib/active_support/testing/performance/jruby.rb
+++ b/activesupport/lib/active_support/testing/performance/jruby.rb
@@ -42,7 +42,7 @@ module ActiveSupport
klasses.each do |klass|
fname = output_filename(klass)
FileUtils.mkdir_p(File.dirname(fname))
- file = File.open(fname, 'wb') do |file|
+ File.open(fname, 'wb') do |file|
klass.new(@data).printProfile(file)
end
end
diff --git a/activesupport/lib/active_support/testing/performance/ruby.rb b/activesupport/lib/active_support/testing/performance/ruby.rb
index 7d6d047ef6..1104fc0a03 100644
--- a/activesupport/lib/active_support/testing/performance/ruby.rb
+++ b/activesupport/lib/active_support/testing/performance/ruby.rb
@@ -36,7 +36,7 @@ module ActiveSupport
RubyProf.pause
full_profile_options[:runs].to_i.times { run_test(@metric, :profile) }
@data = RubyProf.stop
- @total = @data.threads.values.sum(0) { |method_infos| method_infos.max.total_time }
+ @total = @data.threads.sum(0) { |thread| thread.methods.max.total_time }
end
def record
@@ -86,9 +86,12 @@ module ActiveSupport
end
protected
- # overridden by each implementation
def with_gc_stats
+ GC::Profiler.enable
+ GC.start
yield
+ ensure
+ GC::Profiler.disable
end
end
@@ -124,29 +127,42 @@ module ActiveSupport
class Memory < DigitalInformationUnit
Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY)
+
+ # Ruby 1.9 + GCdata patch
+ if GC.respond_to?(:malloc_allocated_size)
+ def measure
+ GC.malloc_allocated_size
+ end
+ end
end
class Objects < Amount
Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS)
+
+ # Ruby 1.9 + GCdata patch
+ if GC.respond_to?(:malloc_allocations)
+ def measure
+ GC.malloc_allocations
+ end
+ end
end
class GcRuns < Amount
Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS)
+
+ def measure
+ GC.count
+ end
end
class GcTime < Time
Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME)
+
+ def measure
+ GC::Profiler.total_time
+ end
end
end
end
end
end
-
-if RUBY_VERSION.between?('1.9.2', '2.0')
- require 'active_support/testing/performance/ruby/yarv'
-elsif RUBY_VERSION.between?('1.8.6', '1.9')
- require 'active_support/testing/performance/ruby/mri'
-else
- $stderr.puts 'Update your ruby interpreter to be able to run benchmarks.'
- exit
-end
diff --git a/activesupport/lib/active_support/testing/performance/ruby/mri.rb b/activesupport/lib/active_support/testing/performance/ruby/mri.rb
deleted file mode 100644
index 142279dd6e..0000000000
--- a/activesupport/lib/active_support/testing/performance/ruby/mri.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-module ActiveSupport
- module Testing
- module Performance
- module Metrics
- class Base
- protected
- # Ruby 1.8 + ruby-prof wrapper (enable/disable stats for Benchmarker)
- if GC.respond_to?(:enable_stats)
- def with_gc_stats
- GC.enable_stats
- GC.start
- yield
- ensure
- GC.disable_stats
- end
- end
- end
-
- class Memory < DigitalInformationUnit
- # Ruby 1.8 + ruby-prof wrapper
- if RubyProf.respond_to?(:measure_memory)
- def measure
- RubyProf.measure_memory
- end
- end
- end
-
- class Objects < Amount
- # Ruby 1.8 + ruby-prof wrapper
- if RubyProf.respond_to?(:measure_allocations)
- def measure
- RubyProf.measure_allocations
- end
- end
- end
-
- class GcRuns < Amount
- # Ruby 1.8 + ruby-prof wrapper
- if RubyProf.respond_to?(:measure_gc_runs)
- def measure
- RubyProf.measure_gc_runs
- end
- end
- end
-
- class GcTime < Time
- # Ruby 1.8 + ruby-prof wrapper
- if RubyProf.respond_to?(:measure_gc_time)
- def measure
- RubyProf.measure_gc_time / 1000.0 / 1000.0
- end
- end
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb b/activesupport/lib/active_support/testing/performance/ruby/yarv.rb
deleted file mode 100644
index 7873262331..0000000000
--- a/activesupport/lib/active_support/testing/performance/ruby/yarv.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-module ActiveSupport
- module Testing
- module Performance
- module Metrics
- class Base
- protected
- # Ruby 1.9 with GC::Profiler
- if defined?(GC::Profiler)
- def with_gc_stats
- GC::Profiler.enable
- GC.start
- yield
- ensure
- GC::Profiler.disable
- end
- end
- end
-
- class Memory < DigitalInformationUnit
- # Ruby 1.9 + GCdata patch
- if GC.respond_to?(:malloc_allocated_size)
- def measure
- GC.malloc_allocated_size
- end
- end
- end
-
- class Objects < Amount
- # Ruby 1.9 + GCdata patch
- if GC.respond_to?(:malloc_allocations)
- def measure
- GC.malloc_allocations
- end
- end
- end
-
- class GcRuns < Amount
- # Ruby 1.9
- if GC.respond_to?(:count)
- def measure
- GC.count
- end
- end
- end
-
- class GcTime < Time
- # Ruby 1.9 with GC::Profiler
- if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time)
- def measure
- GC::Profiler.total_time
- end
- end
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index 22e41fa905..772c7b4209 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -10,11 +10,6 @@ module ActiveSupport
include ActiveSupport::Callbacks
define_callbacks :setup, :teardown
- if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
- include ForMiniTest
- else
- include ForClassicTestUnit
- end
end
module ClassMethods
@@ -27,83 +22,22 @@ module ActiveSupport
end
end
- module ForMiniTest
- def run(runner)
- result = '.'
+ def run(runner)
+ result = '.'
+ begin
+ run_callbacks :setup do
+ result = super
+ end
+ rescue Exception => e
+ result = runner.puke(self.class, method_name, e)
+ ensure
begin
- run_callbacks :setup do
- result = super
- end
+ run_callbacks :teardown
rescue Exception => e
result = runner.puke(self.class, method_name, e)
- ensure
- begin
- run_callbacks :teardown
- rescue Exception => e
- result = runner.puke(self.class, method_name, e)
- end
- end
- result
- end
- end
-
- module ForClassicTestUnit
- # For compatibility with Ruby < 1.8.6
- PASSTHROUGH_EXCEPTIONS = Test::Unit::TestCase::PASSTHROUGH_EXCEPTIONS rescue [NoMemoryError, SignalException, Interrupt, SystemExit]
-
- # This redefinition is unfortunate but test/unit shows us no alternative.
- # Doubly unfortunate: hax to support Mocha's hax.
- def run(result)
- return if @method_name.to_s == "default_test"
-
- mocha_counter = retrieve_mocha_counter(result)
- yield(Test::Unit::TestCase::STARTED, name)
- @_result = result
-
- begin
- begin
- run_callbacks :setup do
- setup
- __send__(@method_name)
- mocha_verify(mocha_counter) if mocha_counter
- end
- rescue Mocha::ExpectationError => e
- add_failure(e.message, e.backtrace)
- rescue Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue Exception => e
- raise if PASSTHROUGH_EXCEPTIONS.include?(e.class)
- add_error(e)
- ensure
- begin
- teardown
- run_callbacks :teardown
- rescue Test::Unit::AssertionFailedError => e
- add_failure(e.message, e.backtrace)
- rescue Exception => e
- raise if PASSTHROUGH_EXCEPTIONS.include?(e.class)
- add_error(e)
- end
- end
- ensure
- mocha_teardown if mocha_counter
- end
-
- result.add_run
- yield(Test::Unit::TestCase::FINISHED, name)
- end
-
- protected
-
- def retrieve_mocha_counter(result) #:nodoc:
- if respond_to?(:mocha_verify) # using mocha
- if defined?(Mocha::TestCaseAdapter::AssertionCounter)
- Mocha::TestCaseAdapter::AssertionCounter.new(result)
- else
- Mocha::Integration::TestUnit::AssertionCounter.new(result)
- end
end
end
+ result
end
end
diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb
index 86f057d676..bcd5d78b54 100644
--- a/activesupport/lib/active_support/time.rb
+++ b/activesupport/lib/active_support/time.rb
@@ -4,16 +4,11 @@ module ActiveSupport
autoload :Duration, 'active_support/duration'
autoload :TimeWithZone, 'active_support/time_with_zone'
autoload :TimeZone, 'active_support/values/time_zone'
-
- on_load_all do
- [Duration, TimeWithZone, TimeZone]
- end
end
require 'date'
require 'time'
-require 'active_support/core_ext/time/publicize_conversion_methods'
require 'active_support/core_ext/time/marshal'
require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/time/calculations'
@@ -21,7 +16,6 @@ require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/date/acts_like'
-require 'active_support/core_ext/date/freeze'
require 'active_support/core_ext/date/calculations'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/date/zones'
diff --git a/activesupport/lib/active_support/time/autoload.rb b/activesupport/lib/active_support/time/autoload.rb
deleted file mode 100644
index c9a7731b39..0000000000
--- a/activesupport/lib/active_support/time/autoload.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module ActiveSupport
- autoload :Duration, 'active_support/duration'
- autoload :TimeWithZone, 'active_support/time_with_zone'
- autoload :TimeZone, 'active_support/values/time_zone'
-end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 63279d0e6d..451520ac5c 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,4 +1,4 @@
-require "active_support/values/time_zone"
+require 'active_support/values/time_zone'
require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/inclusion'
@@ -8,7 +8,6 @@ module ActiveSupport
#
# You shouldn't ever need to create a TimeWithZone instance directly via <tt>new</tt> . Instead use methods
# +local+, +parse+, +at+ and +now+ on TimeZone instances, and +in_time_zone+ on Time and DateTime instances.
- # Examples:
#
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
@@ -20,7 +19,6 @@ module ActiveSupport
# See Time and TimeZone for further documentation of these methods.
#
# TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangeable.
- # Examples:
#
# t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00
# t.hour # => 13
@@ -35,8 +33,10 @@ module ActiveSupport
# t.is_a?(ActiveSupport::TimeWithZone) # => true
#
class TimeWithZone
+
+ # Report class name as 'Time' to thwart type checking
def self.name
- 'Time' # Report class name as 'Time' to thwart type checking
+ 'Time'
end
include Comparable
@@ -120,8 +120,6 @@ module ActiveSupport
# %Y/%m/%d %H:%M:%S +offset style by setting <tt>ActiveSupport::JSON::Encoding.use_standard_json_time_format</tt>
# to false.
#
- # ==== Examples
- #
# # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
# Time.utc(2005,2,1,15,15,10).in_time_zone.to_json
# # => "2005-02-01T15:15:10Z"
@@ -146,12 +144,6 @@ module ActiveSupport
end
end
- def to_yaml(options = {})
- return super if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
-
- utc.to_yaml(options)
- end
-
def httpdate
utc.httpdate
end
@@ -203,7 +195,11 @@ module ActiveSupport
end
def eql?(other)
- utc == other
+ utc.eql?(other)
+ end
+
+ def hash
+ utc.hash
end
def +(other)
@@ -277,7 +273,6 @@ module ActiveSupport
def to_i
utc.to_i
end
- alias_method :hash, :to_i
alias_method :tv_sec, :to_i
# A TimeWithZone acts like a Time, so just return +self+.
@@ -314,16 +309,15 @@ module ActiveSupport
end
# Ensure proxy class responds to all methods that underlying time instance responds to.
- def respond_to?(sym, include_priv = false)
+ def respond_to_missing?(sym, include_priv)
# consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
- return false if sym.to_s == 'acts_like_date?'
- super || time.respond_to?(sym, include_priv)
+ return false if sym.to_sym == :acts_like_date?
+ time.respond_to?(sym, include_priv)
end
# Send the missing method to +time+ instance, and wrap result in a new TimeWithZone with the existing +time_zone+.
def method_missing(sym, *args, &block)
- result = time.__send__(sym, *args, &block)
- result.acts_like?(:time) ? self.class.new(nil, time_zone, result) : result
+ wrap_with_time_zone time.__send__(sym, *args, &block)
end
private
@@ -341,11 +335,21 @@ module ActiveSupport
end
def transfer_time_values_to_utc_constructor(time)
- ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:usec) ? time.usec : 0)
+ ::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:nsec) ? Rational(time.nsec, 1000) : 0)
end
def duration_of_variable_length?(obj)
ActiveSupport::Duration === obj && obj.parts.any? {|p| p[0].in?([:years, :months, :days]) }
end
+
+ def wrap_with_time_zone(time)
+ if time.acts_like?(:time)
+ self.class.new(nil, time_zone, time)
+ elsif time.is_a?(Range)
+ wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end)
+ else
+ time
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 35f400f9df..28bc06f103 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,34 +1,34 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
-# The TimeZone class serves as a wrapper around TZInfo::Timezone instances. It allows us to do the following:
-#
-# * Limit the set of zones provided by TZInfo to a meaningful subset of 142 zones.
-# * Retrieve and display zones with a friendlier name (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
-# * Lazily load TZInfo::Timezone instances only when they're needed.
-# * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods.
-#
-# If you set <tt>config.time_zone</tt> in the Rails Application, you can access this TimeZone object via <tt>Time.zone</tt>:
-#
-# # application.rb:
-# class Application < Rails::Application
-# config.time_zone = "Eastern Time (US & Canada)"
-# end
-#
-# Time.zone # => #<TimeZone:0x514834...>
-# Time.zone.name # => "Eastern Time (US & Canada)"
-# Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00
-#
-# The version of TZInfo bundled with Active Support only includes the definitions necessary to support the zones
-# defined by the TimeZone class. If you need to use zones that aren't defined by TimeZone, you'll need to install the TZInfo gem
-# (if a recent version of the gem is installed locally, this will be used instead of the bundled version.)
module ActiveSupport
+ # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. It allows us to do the following:
+ #
+ # * Limit the set of zones provided by TZInfo to a meaningful subset of 142 zones.
+ # * Retrieve and display zones with a friendlier name (e.g., "Eastern Time (US & Canada)" instead of "America/New_York").
+ # * Lazily load TZInfo::Timezone instances only when they're needed.
+ # * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods.
+ #
+ # If you set <tt>config.time_zone</tt> in the Rails Application, you can access this TimeZone object via <tt>Time.zone</tt>:
+ #
+ # # application.rb:
+ # class Application < Rails::Application
+ # config.time_zone = "Eastern Time (US & Canada)"
+ # end
+ #
+ # Time.zone # => #<TimeZone:0x514834...>
+ # Time.zone.name # => "Eastern Time (US & Canada)"
+ # Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00
+ #
+ # The version of TZInfo bundled with Active Support only includes the definitions necessary to support the zones
+ # defined by the TimeZone class. If you need to use zones that aren't defined by TimeZone, you'll need to install the TZInfo gem
+ # (if a recent version of the gem is installed locally, this will be used instead of the bundled version.)
class TimeZone
# Keys are Rails TimeZone names, values are TZInfo identifiers
MAPPING = {
"International Date Line West" => "Pacific/Midway",
"Midway Island" => "Pacific/Midway",
- "Samoa" => "Pacific/Pago_Pago",
+ "American Samoa" => "Pacific/Pago_Pago",
"Hawaii" => "Pacific/Honolulu",
"Alaska" => "America/Juneau",
"Pacific Time (US & Canada)" => "America/Los_Angeles",
@@ -167,15 +167,16 @@ module ActiveSupport
"Marshall Is." => "Pacific/Majuro",
"Auckland" => "Pacific/Auckland",
"Wellington" => "Pacific/Auckland",
- "Nuku'alofa" => "Pacific/Tongatapu"
- }.each { |name, zone| name.freeze; zone.freeze }
- MAPPING.freeze
+ "Nuku'alofa" => "Pacific/Tongatapu",
+ "Tokelau Is." => "Pacific/Fakaofo",
+ "Samoa" => "Pacific/Apia"
+ }
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
# Assumes self represents an offset from UTC in seconds (as returned from Time#utc_offset)
- # and turns this into an +HH:MM formatted string. Example:
+ # and turns this into an +HH:MM formatted string.
#
# TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
def self.seconds_to_utc_offset(seconds, colon = true)
@@ -203,6 +204,7 @@ module ActiveSupport
@current_period = nil
end
+ # Returns the offset of this time zone from UTC in seconds.
def utc_offset
if @utc_offset
@utc_offset
@@ -237,7 +239,7 @@ module ActiveSupport
"(GMT#{formatted_offset}) #{name}"
end
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values. Example:
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from given values.
#
# Time.zone = "Hawaii" # => "Hawaii"
# Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00
@@ -246,7 +248,7 @@ module ActiveSupport
ActiveSupport::TimeWithZone.new(nil, self, time)
end
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch. Example:
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from number of seconds since the Unix epoch.
#
# Time.zone = "Hawaii" # => "Hawaii"
# Time.utc(2000).to_f # => 946684800.0
@@ -256,7 +258,7 @@ module ActiveSupport
utc.in_time_zone(self)
end
- # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string. Example:
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone of +self+ from parsed string.
#
# Time.zone = "Hawaii" # => "Hawaii"
# Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
@@ -267,9 +269,14 @@ module ActiveSupport
# Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
def parse(str, now=now)
date_parts = Date._parse(str)
- return if date_parts.blank?
+ return if date_parts.empty?
time = Time.parse(str, now) rescue DateTime.parse(str)
+
if date_parts[:offset].nil?
+ if date_parts[:hour] && time.hour != date_parts[:hour]
+ time = DateTime.parse(str)
+ end
+
ActiveSupport::TimeWithZone.new(nil, self, time)
else
time.in_time_zone(self)
@@ -277,12 +284,12 @@ module ActiveSupport
end
# Returns an ActiveSupport::TimeWithZone instance representing the current time
- # in the time zone represented by +self+. Example:
+ # in the time zone represented by +self+.
#
# Time.zone = 'Hawaii' # => "Hawaii"
# Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00
def now
- Time.now.utc.in_time_zone(self)
+ time_now.utc.in_time_zone(self)
end
# Return the current date in this time zone.
@@ -391,5 +398,11 @@ module ActiveSupport
end
end
end
+
+ private
+
+ def time_now
+ Time.now
+ end
end
end
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index 4fe0268cca..df17a8cccf 100644
--- a/activesupport/lib/active_support/values/unicode_tables.dat
+++ b/activesupport/lib/active_support/values/unicode_tables.dat
Binary files differ
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index bd8e7f907a..8a8f8f946d 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -1,7 +1,7 @@
module ActiveSupport
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb
deleted file mode 100644
index 577db5018e..0000000000
--- a/activesupport/lib/active_support/whiny_nil.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# Extensions to +nil+ which allow for more helpful error messages for people who
-# are new to Rails.
-#
-# Ruby raises NoMethodError if you invoke a method on an object that does not
-# respond to it:
-#
-# $ ruby -e nil.destroy
-# -e:1: undefined method `destroy' for nil:NilClass (NoMethodError)
-#
-# With these extensions, if the method belongs to the public interface of the
-# classes in NilClass::WHINERS the error message suggests which could be the
-# actual intended class:
-#
-# $ rails runner nil.destroy
-# ...
-# You might have expected an instance of ActiveRecord::Base.
-# ...
-#
-# NilClass#id exists in Ruby 1.8 (though it is deprecated). Since +id+ is a fundamental
-# method of Active Record models NilClass#id is redefined as well to raise a RuntimeError
-# and warn the user. She probably wanted a model database identifier and the 4
-# returned by the original method could result in obscure bugs.
-#
-# The flag <tt>config.whiny_nils</tt> determines whether this feature is enabled.
-# By default it is on in development and test modes, and it is off in production
-# mode.
-class NilClass
- METHOD_CLASS_MAP = Hash.new
-
- def self.add_whiner(klass)
- methods = klass.public_instance_methods - public_instance_methods
- class_name = klass.name
- methods.each { |method| METHOD_CLASS_MAP[method.to_sym] = class_name }
- end
-
- add_whiner ::Array
-
- # Raises a RuntimeError when you attempt to call +id+ on +nil+.
- def id
- raise RuntimeError, "Called id for nil, which would mistakenly be #{object_id} -- if you really wanted the id of nil, use object_id", caller
- end
-
- private
- def method_missing(method, *args)
- if klass = METHOD_CLASS_MAP[method]
- raise_nil_warning_for klass, method, caller
- else
- super
- end
- end
-
- # Raises a NoMethodError when you attempt to call a method on +nil+.
- def raise_nil_warning_for(class_name = nil, selector = nil, with_caller = nil)
- message = "You have a nil object when you didn't expect it!"
- message << "\nYou might have expected an instance of #{class_name}." if class_name
- message << "\nThe error occurred while evaluating nil.#{selector}" if selector
-
- raise NoMethodError, message, with_caller || caller
- end
-end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index 1ea9a9d7e1..677e9910bb 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -1,4 +1,5 @@
require 'time'
+require 'base64'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/string/inflections'
@@ -48,7 +49,7 @@ module ActiveSupport
"symbol" => Proc.new { |symbol| symbol.to_s },
"date" => Proc.new { |date| date.to_s(:db) },
"datetime" => Proc.new { |time| time.xmlschema },
- "binary" => Proc.new { |binary| ActiveSupport::Base64.encode64(binary) },
+ "binary" => Proc.new { |binary| ::Base64.encode64(binary) },
"yaml" => Proc.new { |yaml| yaml.to_yaml }
} unless defined?(FORMATTING)
@@ -64,7 +65,7 @@ module ActiveSupport
"boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) },
"string" => Proc.new { |string| string.to_s },
"yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml },
- "base64Binary" => Proc.new { |bin| ActiveSupport::Base64.decode64(bin) },
+ "base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) },
"binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) },
"file" => Proc.new { |file, entity| _parse_file(file, entity) }
}
@@ -148,14 +149,14 @@ module ActiveSupport
def _parse_binary(bin, entity) #:nodoc:
case entity['encoding']
when 'base64'
- ActiveSupport::Base64.decode64(bin)
+ ::Base64.decode64(bin)
else
bin
end
end
def _parse_file(file, entity)
- f = StringIO.new(ActiveSupport::Base64.decode64(file))
+ f = StringIO.new(::Base64.decode64(file))
f.extend(FileLike)
f.original_filename = entity['name']
f.content_type = entity['content_type']
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index 6c222b83ba..4551dd2f2d 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -12,7 +12,6 @@ java_import org.xml.sax.InputSource unless defined? InputSource
java_import org.xml.sax.Attributes unless defined? Attributes
java_import org.w3c.dom.Node unless defined? Node
-# = XmlMini JRuby JDOM implementation
module ActiveSupport
module XmlMini_JDOM #:nodoc:
extend self
@@ -71,7 +70,7 @@ module ActiveSupport
child_nodes = element.child_nodes
if child_nodes.length > 0
- for i in 0...child_nodes.length
+ (0...child_nodes.length).each do |i|
child = child_nodes.item(i)
merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE
end
@@ -133,7 +132,7 @@ module ActiveSupport
def get_attributes(element)
attribute_hash = {}
attributes = element.attributes
- for i in 0...attributes.length
+ (0...attributes.length).each do |i|
attribute_hash[CONTENT_KEY] ||= ''
attribute_hash[attributes.item(i).name] = attributes.item(i).value
end
@@ -147,7 +146,7 @@ module ActiveSupport
def texts(element)
texts = []
child_nodes = element.child_nodes
- for i in 0...child_nodes.length
+ (0...child_nodes.length).each do |i|
item = child_nodes.item(i)
if item.node_type == Node.TEXT_NODE
texts << item.get_data
@@ -163,7 +162,7 @@ module ActiveSupport
def empty_content?(element)
text = ''
child_nodes = element.child_nodes
- for i in 0...child_nodes.length
+ (0...child_nodes.length).each do |i|
item = child_nodes.item(i)
if item.node_type == Node.TEXT_NODE
text << item.get_data.strip
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 16570c6aea..26556598fd 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -2,7 +2,6 @@ require 'libxml'
require 'active_support/core_ext/object/blank'
require 'stringio'
-# = XmlMini LibXML implementation
module ActiveSupport
module XmlMini_LibXML #:nodoc:
extend self
diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
index 2536b1f33e..acc018fd2d 100644
--- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb
+++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
@@ -2,9 +2,8 @@ require 'libxml'
require 'active_support/core_ext/object/blank'
require 'stringio'
-# = XmlMini LibXML implementation using a SAX-based parser
module ActiveSupport
- module XmlMini_LibXMLSAX
+ module XmlMini_LibXMLSAX #:nodoc:
extend self
# Class that will build the hash while the XML document
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index 04ec9e8ab8..bb0a52bdcf 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -7,7 +7,6 @@ end
require 'active_support/core_ext/object/blank'
require 'stringio'
-# = XmlMini Nokogiri implementation
module ActiveSupport
module XmlMini_Nokogiri #:nodoc:
extend self
diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
index 93fd3dfe57..30b94aac47 100644
--- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
@@ -7,9 +7,8 @@ end
require 'active_support/core_ext/object/blank'
require 'stringio'
-# = XmlMini Nokogiri implementation using a SAX-based parser
module ActiveSupport
- module XmlMini_NokogiriSAX
+ module XmlMini_NokogiriSAX #:nodoc:
extend self
# Class that will build the hash while the XML document
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index a13ad10118..a2a87337a6 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -2,7 +2,6 @@ require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/object/blank'
require 'stringio'
-# = XmlMini ReXML implementation
module ActiveSupport
module XmlMini_REXML #:nodoc:
extend self
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 0382739871..57ed4a6b60 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -7,22 +7,15 @@ ensure
$VERBOSE = old
end
-lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
-$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
-
require 'active_support/core_ext/kernel/reporting'
-
require 'active_support/core_ext/string/encoding'
-if "ruby".encoding_aware?
- # These are the normal settings that will be set up by Railties
- # TODO: Have these tests support other combinations of these values
- silence_warnings do
- Encoding.default_internal = "UTF-8"
- Encoding.default_external = "UTF-8"
- end
+
+silence_warnings do
+ Encoding.default_internal = "UTF-8"
+ Encoding.default_external = "UTF-8"
end
-require 'test/unit'
+require 'minitest/autorun'
require 'empty_bool'
silence_warnings { require 'mocha' }
@@ -30,9 +23,6 @@ silence_warnings { require 'mocha' }
ENV['NO_RELOAD'] = '1'
require 'active_support'
-# Include shims until we get off 1.8.6
-require 'active_support/ruby/shim' if RUBY_VERSION < '1.8.7'
-
def uses_memcached(test_name)
require 'memcache'
begin
@@ -43,22 +33,5 @@ def uses_memcached(test_name)
end
end
-def with_kcode(code)
- if RUBY_VERSION < '1.9'
- begin
- old_kcode, $KCODE = $KCODE, code
- yield
- ensure
- $KCODE = old_kcode
- end
- else
- yield
- end
-end
-
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
-
-if RUBY_VERSION < '1.9'
- $KCODE = 'UTF8'
-end
diff --git a/activesupport/test/benchmarkable_test.rb b/activesupport/test/benchmarkable_test.rb
index 06f5172e1f..04d4f5e503 100644
--- a/activesupport/test/benchmarkable_test.rb
+++ b/activesupport/test/benchmarkable_test.rb
@@ -3,8 +3,23 @@ require 'abstract_unit'
class BenchmarkableTest < ActiveSupport::TestCase
include ActiveSupport::Benchmarkable
- def teardown
- logger.send(:clear_buffer)
+ attr_reader :buffer, :logger
+
+ class Buffer
+ include Enumerable
+
+ def initialize; @lines = []; end
+ def each(&block); @lines.each(&block); end
+ def write(x); @lines << x; end
+ def close; end
+ def last; @lines.last; end
+ def size; @lines.size; end
+ def empty?; @lines.empty?; end
+ end
+
+ def setup
+ @buffer = Buffer.new
+ @logger = ActiveSupport::Logger.new(@buffer)
end
def test_without_block
@@ -27,48 +42,20 @@ class BenchmarkableTest < ActiveSupport::TestCase
end
def test_within_level
- logger.level = ActiveSupport::BufferedLogger::DEBUG
+ logger.level = ActiveSupport::Logger::DEBUG
benchmark('included_debug_run', :level => :debug) { }
assert_last_logged 'included_debug_run'
end
def test_outside_level
- logger.level = ActiveSupport::BufferedLogger::ERROR
+ logger.level = ActiveSupport::Logger::ERROR
benchmark('skipped_debug_run', :level => :debug) { }
assert_no_match(/skipped_debug_run/, buffer.last)
ensure
- logger.level = ActiveSupport::BufferedLogger::DEBUG
- end
-
- def test_without_silencing
- benchmark('debug_run', :silence => false) do
- logger.info "not silenced!"
- end
-
- assert_equal 2, buffer.size
- end
-
- def test_with_silencing
- benchmark('debug_run', :silence => true) do
- logger.info "silenced!"
- end
-
- assert_equal 1, buffer.size
+ logger.level = ActiveSupport::Logger::DEBUG
end
private
- def logger
- @logger ||= begin
- logger = ActiveSupport::BufferedLogger.new(StringIO.new)
- logger.auto_flushing = false
- logger
- end
- end
-
- def buffer
- logger.send(:buffer)
- end
-
def assert_last_logged(message = 'Benchmarking')
assert_match(/^#{message} \(.*\)$/, buffer.last)
end
diff --git a/activesupport/test/broadcast_logger_test.rb b/activesupport/test/broadcast_logger_test.rb
new file mode 100644
index 0000000000..6d4e3b74f7
--- /dev/null
+++ b/activesupport/test/broadcast_logger_test.rb
@@ -0,0 +1,82 @@
+require 'abstract_unit'
+
+module ActiveSupport
+ class BroadcastLoggerTest < TestCase
+ attr_reader :logger, :log1, :log2
+ def setup
+ @log1 = FakeLogger.new
+ @log2 = FakeLogger.new
+ @log1.extend Logger.broadcast @log2
+ @logger = @log1
+ end
+
+ def test_debug
+ logger.debug "foo"
+ assert_equal 'foo', log1.adds.first[2]
+ assert_equal 'foo', log2.adds.first[2]
+ end
+
+ def test_close
+ logger.close
+ assert log1.closed, 'should be closed'
+ assert log2.closed, 'should be closed'
+ end
+
+ def test_chevrons
+ logger << "foo"
+ assert_equal %w{ foo }, log1.chevrons
+ assert_equal %w{ foo }, log2.chevrons
+ end
+
+ def test_level
+ assert_nil logger.level
+ logger.level = 10
+ assert_equal 10, log1.level
+ assert_equal 10, log2.level
+ end
+
+ def test_progname
+ assert_nil logger.progname
+ logger.progname = 10
+ assert_equal 10, log1.progname
+ assert_equal 10, log2.progname
+ end
+
+ def test_formatter
+ assert_nil logger.formatter
+ logger.formatter = 10
+ assert_equal 10, log1.formatter
+ assert_equal 10, log2.formatter
+ end
+
+ class FakeLogger
+ attr_reader :adds, :closed, :chevrons
+ attr_accessor :level, :progname, :formatter
+
+ def initialize
+ @adds = []
+ @closed = false
+ @chevrons = []
+ @level = nil
+ @progname = nil
+ @formatter = nil
+ end
+
+ def debug msg, &block
+ add(:omg, nil, msg, &block)
+ end
+
+ def << x
+ @chevrons << x
+ end
+
+ def add(*args)
+ @adds << args
+ end
+
+ def close
+ @closed = true
+ end
+ end
+ end
+end
diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb
deleted file mode 100644
index 21049d685b..0000000000
--- a/activesupport/test/buffered_logger_test.rb
+++ /dev/null
@@ -1,201 +0,0 @@
-require 'abstract_unit'
-require 'multibyte_test_helpers'
-require 'stringio'
-require 'fileutils'
-require 'tempfile'
-require 'active_support/buffered_logger'
-
-class BufferedLoggerTest < Test::Unit::TestCase
- include MultibyteTestHelpers
-
- Logger = ActiveSupport::BufferedLogger
-
- def setup
- @message = "A debug message"
- @integer_message = 12345
- @output = StringIO.new
- @logger = Logger.new(@output)
- end
-
- def test_write_binary_data_to_existing_file
- t = Tempfile.new ['development', 'log']
- t.binmode
- t.write 'hi mom!'
- t.close
-
- logger = Logger.new t.path
- logger.level = Logger::DEBUG
-
- str = "\x80"
- if str.respond_to?(:force_encoding)
- str.force_encoding("ASCII-8BIT")
- end
-
- logger.add Logger::DEBUG, str
- logger.flush
- ensure
- logger.close
- t.close true
- end
-
- def test_write_binary_data_create_file
- fname = File.join Dir.tmpdir, 'lol', 'rofl.log'
- logger = Logger.new fname
- logger.level = Logger::DEBUG
-
- str = "\x80"
- if str.respond_to?(:force_encoding)
- str.force_encoding("ASCII-8BIT")
- end
-
- logger.add Logger::DEBUG, str
- logger.flush
- ensure
- logger.close
- File.unlink fname
- end
-
- def test_should_log_debugging_message_when_debugging
- @logger.level = Logger::DEBUG
- @logger.add(Logger::DEBUG, @message)
- assert @output.string.include?(@message)
- end
-
- def test_should_not_log_debug_messages_when_log_level_is_info
- @logger.level = Logger::INFO
- @logger.add(Logger::DEBUG, @message)
- assert ! @output.string.include?(@message)
- end
-
- def test_should_add_message_passed_as_block_when_using_add
- @logger.level = Logger::INFO
- @logger.add(Logger::INFO) {@message}
- assert @output.string.include?(@message)
- end
-
- def test_should_add_message_passed_as_block_when_using_shortcut
- @logger.level = Logger::INFO
- @logger.info {@message}
- assert @output.string.include?(@message)
- end
-
- def test_should_convert_message_to_string
- @logger.level = Logger::INFO
- @logger.info @integer_message
- assert @output.string.include?(@integer_message.to_s)
- end
-
- def test_should_convert_message_to_string_when_passed_in_block
- @logger.level = Logger::INFO
- @logger.info {@integer_message}
- assert @output.string.include?(@integer_message.to_s)
- end
-
- def test_should_not_evaluate_block_if_message_wont_be_logged
- @logger.level = Logger::INFO
- evaluated = false
- @logger.add(Logger::DEBUG) {evaluated = true}
- assert evaluated == false
- end
-
- def test_should_not_mutate_message
- message_copy = @message.dup
- @logger.info @message
- assert_equal message_copy, @message
- end
-
-
- [false, nil, 0].each do |disable|
- define_method "test_disabling_auto_flush_with_#{disable.inspect}_should_buffer_until_explicit_flush" do
- @logger.auto_flushing = disable
-
- 4.times do
- @logger.info 'wait for it..'
- assert @output.string.empty?, "@output.string should be empty but it is #{@output.string}"
- end
-
- @logger.flush
- assert !@output.string.empty?, "@logger.send(:buffer).size.to_s should not be empty but it is empty"
- end
-
- define_method "test_disabling_auto_flush_with_#{disable.inspect}_should_flush_at_max_buffer_size_as_failsafe" do
- @logger.auto_flushing = disable
- assert_equal Logger::MAX_BUFFER_SIZE, @logger.auto_flushing
-
- (Logger::MAX_BUFFER_SIZE - 1).times do
- @logger.info 'wait for it..'
- assert @output.string.empty?, "@output.string should be empty but is #{@output.string}"
- end
-
- @logger.info 'there it is.'
- assert !@output.string.empty?, "@logger.send(:buffer).size.to_s should not be empty but it is empty"
- end
- end
-
- def test_should_know_if_its_loglevel_is_below_a_given_level
- Logger::Severity.constants.each do |level|
- @logger.level = Logger::Severity.const_get(level) - 1
- assert @logger.send("#{level.downcase}?"), "didn't know if it was #{level.downcase}? or below"
- end
- end
-
- def test_should_auto_flush_every_n_messages
- @logger.auto_flushing = 5
-
- 4.times do
- @logger.info 'wait for it..'
- assert @output.string.empty?, "@output.string should be empty but it is #{@output.string}"
- end
-
- @logger.info 'there it is.'
- assert !@output.string.empty?, "@output.string should not be empty but it is empty"
- end
-
- def test_should_create_the_log_directory_if_it_doesnt_exist
- tmp_directory = File.join(File.dirname(__FILE__), "tmp")
- log_file = File.join(tmp_directory, "development.log")
- FileUtils.rm_rf(tmp_directory)
- @logger = Logger.new(log_file)
- assert File.exist?(tmp_directory)
- end
-
- def test_logger_should_maintain_separate_buffers_for_each_thread
- @logger.auto_flushing = false
-
- a = Thread.new do
- @logger.info("a"); Thread.pass;
- @logger.info("b"); Thread.pass;
- @logger.info("c"); @logger.flush
- end
-
- b = Thread.new do
- @logger.info("x"); Thread.pass;
- @logger.info("y"); Thread.pass;
- @logger.info("z"); @logger.flush
- end
-
- a.join
- b.join
-
- assert @output.string.include?("a\nb\nc\n")
- assert @output.string.include?("x\ny\nz\n")
- end
-
- def test_flush_should_remove_empty_buffers
- @logger.send :buffer
- @logger.expects :clear_buffer
- @logger.flush
- end
-
- def test_buffer_multibyte
- @logger.auto_flushing = 2
- @logger.info(UNICODE_STRING)
- @logger.info(BYTE_STRING)
- assert @output.string.include?(UNICODE_STRING)
- byte_string = @output.string.dup
- if byte_string.respond_to?(:force_encoding)
- byte_string.force_encoding("ASCII-8BIT")
- end
- assert byte_string.include?(BYTE_STRING)
- end
-end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index cb5362525f..d62b782e2d 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -13,10 +13,10 @@ class CacheKeyTest < ActiveSupport::TestCase
ENV['RAILS_CACHE_ID'] = 'c99'
assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo)
assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key([:foo])
- assert_equal 'c99/c99/foo/c99/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar])
+ assert_equal 'c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar])
assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key(:foo, :nm)
assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key([:foo], :nm)
- assert_equal 'nm/c99/c99/foo/c99/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
+ assert_equal 'nm/c99/foo/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
ensure
ENV['RAILS_CACHE_ID'] = nil
end
@@ -42,7 +42,7 @@ class CacheKeyTest < ActiveSupport::TestCase
end
end
- def test_respond_to_cache_key
+ def test_expand_cache_key_respond_to_cache_key
key = 'foo'
def key.cache_key
:foo_key
@@ -50,6 +50,29 @@ class CacheKeyTest < ActiveSupport::TestCase
assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key(key)
end
+ def test_expand_cache_key_array_with_something_that_responds_to_cache_key
+ key = 'foo'
+ def key.cache_key
+ :foo_key
+ end
+ assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key([key])
+ end
+
+ def test_expand_cache_key_of_nil
+ assert_equal '', ActiveSupport::Cache.expand_cache_key(nil)
+ end
+
+ def test_expand_cache_key_of_false
+ assert_equal 'false', ActiveSupport::Cache.expand_cache_key(false)
+ end
+
+ def test_expand_cache_key_of_true
+ assert_equal 'true', ActiveSupport::Cache.expand_cache_key(true)
+ end
+
+ def test_expand_cache_key_of_array_like_object
+ assert_equal 'foo/bar/baz', ActiveSupport::Cache.expand_cache_key(%w{foo bar baz}.to_enum)
+ end
end
class CacheStoreSettingTest < ActiveSupport::TestCase
@@ -122,8 +145,8 @@ class CacheStoreNamespaceTest < ActiveSupport::TestCase
cache.write("foo", "bar")
cache.write("fu", "baz")
cache.delete_matched(/^fo/)
- assert_equal false, cache.exist?("foo")
- assert_equal true, cache.exist?("fu")
+ assert !cache.exist?("foo")
+ assert cache.exist?("fu")
end
def test_delete_matched_key
@@ -131,15 +154,15 @@ class CacheStoreNamespaceTest < ActiveSupport::TestCase
cache.write("foo", "bar")
cache.write("fu", "baz")
cache.delete_matched(/OO/i)
- assert_equal false, cache.exist?("foo")
- assert_equal true, cache.exist?("fu")
+ assert !cache.exist?("foo")
+ assert cache.exist?("fu")
end
end
# Tests the base functionality that should be identical across all cache stores.
module CacheStoreBehavior
def test_should_read_and_write_strings
- assert_equal true, @cache.write('foo', 'bar')
+ assert @cache.write('foo', 'bar')
assert_equal 'bar', @cache.read('foo')
end
@@ -174,22 +197,22 @@ module CacheStoreBehavior
end
def test_should_read_and_write_hash
- assert_equal true, @cache.write('foo', {:a => "b"})
+ assert @cache.write('foo', {:a => "b"})
assert_equal({:a => "b"}, @cache.read('foo'))
end
def test_should_read_and_write_integer
- assert_equal true, @cache.write('foo', 1)
+ assert @cache.write('foo', 1)
assert_equal 1, @cache.read('foo')
end
def test_should_read_and_write_nil
- assert_equal true, @cache.write('foo', nil)
+ assert @cache.write('foo', nil)
assert_equal nil, @cache.read('foo')
end
def test_should_read_and_write_false
- assert_equal true, @cache.write('foo', false)
+ assert @cache.write('foo', false)
assert_equal false, @cache.read('foo')
end
@@ -199,7 +222,7 @@ module CacheStoreBehavior
@cache.write('fud', 'biz')
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
end
-
+
def test_read_multi_with_expires
@cache.write('foo', 'bar', :expires_in => 0.001)
@cache.write('fu', 'baz')
@@ -262,19 +285,19 @@ module CacheStoreBehavior
def test_exist
@cache.write('foo', 'bar')
- assert_equal true, @cache.exist?('foo')
- assert_equal false, @cache.exist?('bar')
+ assert @cache.exist?('foo')
+ assert !@cache.exist?('bar')
end
def test_nil_exist
@cache.write('foo', nil)
- assert_equal true, @cache.exist?('foo')
+ assert @cache.exist?('foo')
end
def test_delete
@cache.write('foo', 'bar')
assert @cache.exist?('foo')
- assert_equal true, @cache.delete('foo')
+ assert @cache.delete('foo')
assert !@cache.exist?('foo')
end
@@ -346,10 +369,10 @@ module CacheStoreBehavior
def test_crazy_key_characters
crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
- assert_equal true, @cache.write(crazy_key, "1", :raw => true)
+ assert @cache.write(crazy_key, "1", :raw => true)
assert_equal "1", @cache.read(crazy_key)
assert_equal "1", @cache.fetch(crazy_key)
- assert_equal true, @cache.delete(crazy_key)
+ assert @cache.delete(crazy_key)
assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" }
assert_equal 3, @cache.increment(crazy_key)
assert_equal 2, @cache.decrement(crazy_key)
@@ -358,12 +381,12 @@ module CacheStoreBehavior
def test_really_long_keys
key = ""
900.times{key << "x"}
- assert_equal true, @cache.write(key, "bar")
+ assert @cache.write(key, "bar")
assert_equal "bar", @cache.read(key)
assert_equal "bar", @cache.fetch(key)
assert_nil @cache.read("#{key}x")
assert_equal({key => "bar"}, @cache.read_multi(key))
- assert_equal true, @cache.delete(key)
+ assert @cache.delete(key)
end
end
@@ -371,36 +394,34 @@ end
# The error is caused by charcter encodings that can't be compared with ASCII-8BIT regular expressions and by special
# characters like the umlaut in UTF-8.
module EncodedKeyCacheBehavior
- if defined?(Encoding)
- Encoding.list.each do |encoding|
- define_method "test_#{encoding.name.underscore}_encoded_values" do
- key = "foo".force_encoding(encoding)
- assert_equal true, @cache.write(key, "1", :raw => true)
- assert_equal "1", @cache.read(key)
- assert_equal "1", @cache.fetch(key)
- assert_equal true, @cache.delete(key)
- assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
- assert_equal 3, @cache.increment(key)
- assert_equal 2, @cache.decrement(key)
- end
- end
-
- def test_common_utf8_values
- key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
- assert_equal true, @cache.write(key, "1", :raw => true)
+ Encoding.list.each do |encoding|
+ define_method "test_#{encoding.name.underscore}_encoded_values" do
+ key = "foo".force_encoding(encoding)
+ assert @cache.write(key, "1", :raw => true)
assert_equal "1", @cache.read(key)
assert_equal "1", @cache.fetch(key)
- assert_equal true, @cache.delete(key)
+ assert @cache.delete(key)
assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
assert_equal 3, @cache.increment(key)
assert_equal 2, @cache.decrement(key)
end
+ end
- def test_retains_encoding
- key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
- assert_equal true, @cache.write(key, "1", :raw => true)
- assert_equal Encoding::UTF_8, key.encoding
- end
+ def test_common_utf8_values
+ key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
+ assert @cache.write(key, "1", :raw => true)
+ assert_equal "1", @cache.read(key)
+ assert_equal "1", @cache.fetch(key)
+ assert @cache.delete(key)
+ assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
+ assert_equal 3, @cache.increment(key)
+ assert_equal 2, @cache.decrement(key)
+ end
+
+ def test_retains_encoding
+ key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
+ assert @cache.write(key, "1", :raw => true)
+ assert_equal Encoding::UTF_8, key.encoding
end
end
@@ -411,10 +432,10 @@ module CacheDeleteMatchedBehavior
@cache.write("foo/bar", "baz")
@cache.write("fu/baz", "bar")
@cache.delete_matched(/oo/)
- assert_equal false, @cache.exist?("foo")
- assert_equal true, @cache.exist?("fu")
- assert_equal false, @cache.exist?("foo/bar")
- assert_equal true, @cache.exist?("fu/baz")
+ assert !@cache.exist?("foo")
+ assert @cache.exist?("fu")
+ assert !@cache.exist?("foo/bar")
+ assert @cache.exist?("fu/baz")
end
end
@@ -443,7 +464,7 @@ module LocalCacheBehavior
retval = @cache.with_local_cache do
@cache.write('foo', 'bar')
end
- assert_equal true, retval
+ assert retval
assert_equal 'bar', @cache.read('foo')
end
@@ -547,6 +568,13 @@ class FileStoreTest < ActiveSupport::TestCase
include CacheDeleteMatchedBehavior
include CacheIncrementDecrementBehavior
+ def test_clear
+ filepath = File.join(cache_dir, ".gitkeep")
+ FileUtils.touch(filepath)
+ @cache.clear
+ assert File.exist?(filepath)
+ end
+
def test_key_transformation
key = @cache.send(:key_file_path, "views/index?id=1")
assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
@@ -557,12 +585,22 @@ class FileStoreTest < ActiveSupport::TestCase
key = @cache_with_pathname.send(:key_file_path, "views/index?id=1")
assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key)
end
-
+
+ # Test that generated cache keys are short enough to have Tempfile stuff added to them and
+ # remain valid
+ def test_filename_max_size
+ key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}"
+ path = @cache.send(:key_file_path, key)
+ Dir::Tmpname.create(path) do |tmpname, n, opts|
+ assert File.basename(tmpname+'.lock').length <= 255, "Temp filename too long: #{File.basename(tmpname+'.lock').length}"
+ end
+ end
+
# Because file systems have a maximum filename size, filenames > max size should be split in to directories
# If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B
def test_key_transformation_max_filename_size
key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B"
- path = @cache.send(:key_file_path, key)
+ path = @cache.send(:key_file_path, key)
assert path.split('/').all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}
assert_equal 'B', File.basename(path)
end
@@ -595,11 +633,11 @@ class MemoryStoreTest < ActiveSupport::TestCase
@cache.read(2) && sleep(0.001)
@cache.read(4)
@cache.prune(@record_size * 3)
- assert_equal true, @cache.exist?(5)
- assert_equal true, @cache.exist?(4)
- assert_equal false, @cache.exist?(3)
- assert_equal true, @cache.exist?(2)
- assert_equal false, @cache.exist?(1)
+ assert @cache.exist?(5)
+ assert @cache.exist?(4)
+ assert !@cache.exist?(3)
+ assert @cache.exist?(2)
+ assert !@cache.exist?(1)
end
def test_prune_size_on_write
@@ -616,17 +654,17 @@ class MemoryStoreTest < ActiveSupport::TestCase
@cache.read(2) && sleep(0.001)
@cache.read(4) && sleep(0.001)
@cache.write(11, "llllllllll")
- assert_equal true, @cache.exist?(11)
- assert_equal true, @cache.exist?(10)
- assert_equal true, @cache.exist?(9)
- assert_equal true, @cache.exist?(8)
- assert_equal true, @cache.exist?(7)
- assert_equal false, @cache.exist?(6)
- assert_equal false, @cache.exist?(5)
- assert_equal true, @cache.exist?(4)
- assert_equal false, @cache.exist?(3)
- assert_equal true, @cache.exist?(2)
- assert_equal false, @cache.exist?(1)
+ assert @cache.exist?(11)
+ assert @cache.exist?(10)
+ assert @cache.exist?(9)
+ assert @cache.exist?(8)
+ assert @cache.exist?(7)
+ assert !@cache.exist?(6)
+ assert !@cache.exist?(5)
+ assert @cache.exist?(4)
+ assert !@cache.exist?(3)
+ assert @cache.exist?(2)
+ assert !@cache.exist?(1)
end
def test_pruning_is_capped_at_a_max_time
@@ -640,11 +678,18 @@ class MemoryStoreTest < ActiveSupport::TestCase
@cache.write(4, "dddddddddd") && sleep(0.001)
@cache.write(5, "eeeeeeeeee") && sleep(0.001)
@cache.prune(30, 0.001)
- assert_equal true, @cache.exist?(5)
- assert_equal true, @cache.exist?(4)
- assert_equal true, @cache.exist?(3)
- assert_equal true, @cache.exist?(2)
- assert_equal false, @cache.exist?(1)
+ assert @cache.exist?(5)
+ assert @cache.exist?(4)
+ assert @cache.exist?(3)
+ assert @cache.exist?(2)
+ assert !@cache.exist?(1)
+ end
+
+ def test_write_with_unless_exist
+ assert_equal true, @cache.write(1, "aaaaaaaaaa")
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", :unless_exist => true)
+ @cache.write(1, nil)
+ assert_equal false, @cache.write(1, "aaaaaaaaaa", :unless_exist => true)
end
end
@@ -656,7 +701,7 @@ uses_memcached 'memcached backed store' do
@data = @cache.instance_variable_get(:@data)
@cache.clear
@cache.silence!
- @cache.logger = Logger.new("/dev/null")
+ @cache.logger = ActiveSupport::Logger.new("/dev/null")
end
include CacheStoreBehavior
@@ -670,14 +715,14 @@ uses_memcached 'memcached backed store' do
cache.write("foo", 2)
assert_equal "2", cache.read("foo")
end
-
+
def test_raw_values_with_marshal
cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
cache.clear
cache.write("foo", Marshal.dump([]))
- assert_equal [], cache.read("foo")
+ assert_equal [], cache.read("foo")
end
-
+
def test_local_cache_raw_values
cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
cache.clear
@@ -698,12 +743,70 @@ uses_memcached 'memcached backed store' do
end
end
+class NullStoreTest < ActiveSupport::TestCase
+ def setup
+ @cache = ActiveSupport::Cache.lookup_store(:null_store)
+ end
+
+ def test_clear
+ @cache.clear
+ end
+
+ def test_cleanup
+ @cache.cleanup
+ end
+
+ def test_write
+ assert_equal true, @cache.write("name", "value")
+ end
+
+ def test_read
+ @cache.write("name", "value")
+ assert_nil @cache.read("name")
+ end
+
+ def test_delete
+ @cache.write("name", "value")
+ assert_equal false, @cache.delete("name")
+ end
+
+ def test_increment
+ @cache.write("name", 1, :raw => true)
+ assert_nil @cache.increment("name")
+ end
+
+ def test_decrement
+ @cache.write("name", 1, :raw => true)
+ assert_nil @cache.increment("name")
+ end
+
+ def test_delete_matched
+ @cache.write("name", "value")
+ @cache.delete_matched(/name/)
+ end
+
+ def test_local_store_strategy
+ @cache.with_local_cache do
+ @cache.write("name", "value")
+ assert_equal "value", @cache.read("name")
+ @cache.delete("name")
+ assert_nil @cache.read("name")
+ @cache.write("name", "value")
+ end
+ assert_nil @cache.read("name")
+ end
+
+ def test_setting_nil_cache_store
+ assert ActiveSupport::Cache.lookup_store.class.name, ActiveSupport::Cache::NullStore.name
+ end
+end
+
class CacheStoreLoggerTest < ActiveSupport::TestCase
def setup
@cache = ActiveSupport::Cache.lookup_store(:memory_store)
@buffer = StringIO.new
- @cache.logger = Logger.new(@buffer)
+ @cache.logger = ActiveSupport::Logger.new(@buffer)
end
def test_logging
@@ -723,7 +826,7 @@ class CacheEntryTest < ActiveSupport::TestCase
entry = ActiveSupport::Cache::Entry.create("raw", time, :compress => false, :expires_in => 300)
assert_equal "raw", entry.raw_value
assert_equal time.to_f, entry.created_at
- assert_equal false, entry.compressed?
+ assert !entry.compressed?
assert_equal 300, entry.expires_in
end
@@ -740,7 +843,7 @@ class CacheEntryTest < ActiveSupport::TestCase
def test_compress_values
entry = ActiveSupport::Cache::Entry.new("value", :compress => true, :compress_threshold => 1)
assert_equal "value", entry.value
- assert_equal true, entry.compressed?
+ assert entry.compressed?
assert_equal "value", Marshal.load(Zlib::Inflate.inflate(entry.raw_value))
end
@@ -748,6 +851,6 @@ class CacheEntryTest < ActiveSupport::TestCase
entry = ActiveSupport::Cache::Entry.new("value")
assert_equal "value", entry.value
assert_equal "value", Marshal.load(entry.raw_value)
- assert_equal false, entry.compressed?
+ assert !entry.compressed?
end
end
diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb
index 06259c648c..6be8ea8b84 100644
--- a/activesupport/test/callback_inheritance_test.rb
+++ b/activesupport/test/callback_inheritance_test.rb
@@ -1,5 +1,4 @@
require 'abstract_unit'
-require 'test/unit'
class GrandParent
include ActiveSupport::Callbacks
@@ -10,8 +9,8 @@ class GrandParent
end
define_callbacks :dispatch
- set_callback :dispatch, :before, :before1, :before2, :per_key => {:if => proc {|c| c.action_name == "index" || c.action_name == "update" }}
- set_callback :dispatch, :after, :after1, :after2, :per_key => {:if => proc {|c| c.action_name == "update" || c.action_name == "delete" }}
+ set_callback :dispatch, :before, :before1, :before2, :if => proc {|c| c.action_name == "index" || c.action_name == "update" }
+ set_callback :dispatch, :after, :after1, :after2, :if => proc {|c| c.action_name == "update" || c.action_name == "delete" }
def before1
@log << "before1"
@@ -30,7 +29,7 @@ class GrandParent
end
def dispatch
- run_callbacks(:dispatch, action_name) do
+ run_callbacks :dispatch do
@log << action_name
end
self
@@ -38,12 +37,12 @@ class GrandParent
end
class Parent < GrandParent
- skip_callback :dispatch, :before, :before2, :per_key => {:unless => proc {|c| c.action_name == "update" }}
- skip_callback :dispatch, :after, :after2, :per_key => {:unless => proc {|c| c.action_name == "delete" }}
+ skip_callback :dispatch, :before, :before2, :unless => proc {|c| c.action_name == "update" }
+ skip_callback :dispatch, :after, :after2, :unless => proc {|c| c.action_name == "delete" }
end
class Child < GrandParent
- skip_callback :dispatch, :before, :before2, :per_key => {:unless => proc {|c| c.action_name == "update" }}, :if => :state_open?
+ skip_callback :dispatch, :before, :before2, :unless => proc {|c| c.action_name == "update" }, :if => :state_open?
def state_open?
@state == :open
@@ -105,7 +104,7 @@ end
class CountingChild < CountingParent
end
-class BasicCallbacksTest < Test::Unit::TestCase
+class BasicCallbacksTest < ActiveSupport::TestCase
def setup
@index = GrandParent.new("index").dispatch
@update = GrandParent.new("update").dispatch
@@ -113,20 +112,20 @@ class BasicCallbacksTest < Test::Unit::TestCase
@unknown = GrandParent.new("unknown").dispatch
end
- def test_basic_per_key1
+ def test_basic_conditional_callback1
assert_equal %w(before1 before2 index), @index.log
end
- def test_basic_per_key2
+ def test_basic_conditional_callback2
assert_equal %w(before1 before2 update after2 after1), @update.log
end
- def test_basic_per_key3
+ def test_basic_conditional_callback3
assert_equal %w(delete after2 after1), @delete.log
end
end
-class InheritedCallbacksTest < Test::Unit::TestCase
+class InheritedCallbacksTest < ActiveSupport::TestCase
def setup
@index = Parent.new("index").dispatch
@update = Parent.new("update").dispatch
@@ -147,7 +146,7 @@ class InheritedCallbacksTest < Test::Unit::TestCase
end
end
-class InheritedCallbacksTest2 < Test::Unit::TestCase
+class InheritedCallbacksTest2 < ActiveSupport::TestCase
def setup
@update1 = Child.new("update", :open).dispatch
@update2 = Child.new("update", :closed).dispatch
@@ -162,7 +161,7 @@ class InheritedCallbacksTest2 < Test::Unit::TestCase
end
end
-class DynamicInheritedCallbacks < Test::Unit::TestCase
+class DynamicInheritedCallbacks < ActiveSupport::TestCase
def test_callbacks_looks_to_the_superclass_before_running
child = EmptyChild.new.dispatch
assert !child.performed?
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 2b4adda4d1..b7c3b130c3 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -1,10 +1,9 @@
require 'abstract_unit'
-require 'test/unit'
module CallbacksTest
class Phone
include ActiveSupport::Callbacks
- define_callbacks :save, :rescuable => true
+ define_callbacks :save
set_callback :save, :before, :before_save1
set_callback :save, :after, :after_save1
@@ -96,7 +95,7 @@ module CallbacksTest
define_callbacks :dispatch
- set_callback :dispatch, :before, :log, :per_key => {:unless => proc {|c| c.action_name == :index || c.action_name == :show }}
+ set_callback :dispatch, :before, :log, :unless => proc {|c| c.action_name == :index || c.action_name == :show }
set_callback :dispatch, :after, :log2
attr_reader :action_name, :logger
@@ -113,7 +112,7 @@ module CallbacksTest
end
def dispatch
- run_callbacks :dispatch, action_name do
+ run_callbacks :dispatch do
@logger << "Done"
end
self
@@ -121,7 +120,7 @@ module CallbacksTest
end
class Child < ParentController
- skip_callback :dispatch, :before, :log, :per_key => {:if => proc {|c| c.action_name == :update} }
+ skip_callback :dispatch, :before, :log, :if => proc {|c| c.action_name == :update}
skip_callback :dispatch, :after, :log2
end
@@ -132,10 +131,10 @@ module CallbacksTest
super
end
- before_save Proc.new {|r| r.history << [:before_save, :starts_true, :if] }, :per_key => {:if => :starts_true}
- before_save Proc.new {|r| r.history << [:before_save, :starts_false, :if] }, :per_key => {:if => :starts_false}
- before_save Proc.new {|r| r.history << [:before_save, :starts_true, :unless] }, :per_key => {:unless => :starts_true}
- before_save Proc.new {|r| r.history << [:before_save, :starts_false, :unless] }, :per_key => {:unless => :starts_false}
+ before_save Proc.new {|r| r.history << [:before_save, :starts_true, :if] }, :if => :starts_true
+ before_save Proc.new {|r| r.history << [:before_save, :starts_false, :if] }, :if => :starts_false
+ before_save Proc.new {|r| r.history << [:before_save, :starts_true, :unless] }, :unless => :starts_true
+ before_save Proc.new {|r| r.history << [:before_save, :starts_false, :unless] }, :unless => :starts_false
def starts_true
if @@starts_true
@@ -154,11 +153,11 @@ module CallbacksTest
end
def save
- run_callbacks :save, :action
+ run_callbacks :save
end
end
- class OneTimeCompileTest < Test::Unit::TestCase
+ class OneTimeCompileTest < ActiveSupport::TestCase
def test_optimized_first_compile
around = OneTimeCompile.new
around.save
@@ -177,7 +176,7 @@ module CallbacksTest
end
end
- class AfterSaveConditionalPersonCallbackTest < Test::Unit::TestCase
+ class AfterSaveConditionalPersonCallbackTest < ActiveSupport::TestCase
def test_after_save_runs_in_the_reverse_order
person = AfterSaveConditionalPerson.new
person.save
@@ -330,7 +329,7 @@ module CallbacksTest
define_callbacks :save
attr_reader :stuff
- set_callback :save, :before, :action, :per_key => {:if => :yes}
+ set_callback :save, :before, :action, :if => :yes
def yes() true end
@@ -339,13 +338,61 @@ module CallbacksTest
end
def save
- run_callbacks :save, "hyphen-ated" do
+ run_callbacks :save do
@stuff
end
end
end
- class AroundCallbacksTest < Test::Unit::TestCase
+ module ExtendModule
+ def self.extended(base)
+ base.class_eval do
+ set_callback :save, :before, :record3
+ end
+ end
+ def record3
+ @recorder << 3
+ end
+ end
+
+ module IncludeModule
+ def self.included(base)
+ base.class_eval do
+ set_callback :save, :before, :record2
+ end
+ end
+ def record2
+ @recorder << 2
+ end
+ end
+
+ class ExtendCallbacks
+
+ include ActiveSupport::Callbacks
+
+ define_callbacks :save
+ set_callback :save, :before, :record1
+
+ include IncludeModule
+
+ def save
+ run_callbacks :save
+ end
+
+ attr_reader :recorder
+
+ def initialize
+ @recorder = []
+ end
+
+ private
+
+ def record1
+ @recorder << 1
+ end
+ end
+
+ class AroundCallbacksTest < ActiveSupport::TestCase
def test_save_around
around = AroundPerson.new
around.save
@@ -364,7 +411,7 @@ module CallbacksTest
end
end
- class AroundCallbackResultTest < Test::Unit::TestCase
+ class AroundCallbackResultTest < ActiveSupport::TestCase
def test_save_around
around = AroundPersonResult.new
around.save
@@ -372,7 +419,7 @@ module CallbacksTest
end
end
- class SkipCallbacksTest < Test::Unit::TestCase
+ class SkipCallbacksTest < ActiveSupport::TestCase
def test_skip_person
person = PersonSkipper.new
assert_equal [], person.history
@@ -391,14 +438,7 @@ module CallbacksTest
end
end
- class CallbacksTest < Test::Unit::TestCase
- def test_save_phone
- phone = Phone.new
- assert_raise RuntimeError do
- phone.save
- end
- assert_equal [:before, :after], phone.history
- end
+ class CallbacksTest < ActiveSupport::TestCase
def test_save_person
person = Person.new
@@ -419,7 +459,7 @@ module CallbacksTest
end
end
- class ConditionalCallbackTest < Test::Unit::TestCase
+ class ConditionalCallbackTest < ActiveSupport::TestCase
def test_save_conditional_person
person = ConditionalPerson.new
person.save
@@ -437,7 +477,7 @@ module CallbacksTest
- class ResetCallbackTest < Test::Unit::TestCase
+ class ResetCallbackTest < ActiveSupport::TestCase
def test_save_conditional_person
person = CleanPerson.new
person.save
@@ -461,7 +501,7 @@ module CallbacksTest
set_callback :save, :after, :third
- attr_reader :history, :saved
+ attr_reader :history, :saved, :halted
def initialize
@history = []
end
@@ -490,6 +530,10 @@ module CallbacksTest
@saved = true
end
end
+
+ def halted_callback_hook(filter)
+ @halted = filter
+ end
end
class CallbackObject
@@ -563,7 +607,7 @@ module CallbacksTest
end
end
- class UsingObjectTest < Test::Unit::TestCase
+ class UsingObjectTest < ActiveSupport::TestCase
def test_before_object
u = UsingObjectBefore.new
u.save
@@ -588,13 +632,19 @@ module CallbacksTest
end
end
- class CallbackTerminatorTest < Test::Unit::TestCase
+ class CallbackTerminatorTest < ActiveSupport::TestCase
def test_termination
terminator = CallbackTerminator.new
terminator.save
assert_equal ["first", "second", "third", "second", "first"], terminator.history
end
+ def test_termination_invokes_hook
+ terminator = CallbackTerminator.new
+ terminator.save
+ assert_equal ":second", terminator.halted
+ end
+
def test_block_never_called_if_terminated
obj = CallbackTerminator.new
obj.save
@@ -602,7 +652,7 @@ module CallbacksTest
end
end
- class HyphenatedKeyTest < Test::Unit::TestCase
+ class HyphenatedKeyTest < ActiveSupport::TestCase
def test_save
obj = HyphenatedCallbacks.new
obj.save
@@ -615,7 +665,7 @@ module CallbacksTest
skip_callback :save, :before, :before_save_method, :if => lambda {self.age > 21}
end
- class WriterCallbacksTest < Test::Unit::TestCase
+ class WriterCallbacksTest < ActiveSupport::TestCase
def test_skip_writer
writer = WriterSkipper.new
writer.age = 18
@@ -636,4 +686,28 @@ module CallbacksTest
end
end
+ class ExtendCallbacksTest < ActiveSupport::TestCase
+ def test_save
+ model = ExtendCallbacks.new.extend ExtendModule
+ model.save
+ assert_equal [1, 2, 3], model.recorder
+ end
+ end
+
+ class PerKeyOptionDeprecationTest < ActiveSupport::TestCase
+
+ def test_per_key_option_deprecaton
+ assert_raise NotImplementedError do
+ Phone.class_eval do
+ set_callback :save, :before, :before_save1, :per_key => {:if => "true"}
+ end
+ end
+ assert_raise NotImplementedError do
+ Phone.class_eval do
+ skip_callback :save, :before, :before_save1, :per_key => {:if => "true"}
+ end
+ end
+ end
+ end
+
end
diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb
index 752c0ee478..b96f476ce6 100644
--- a/activesupport/test/class_cache_test.rb
+++ b/activesupport/test/class_cache_test.rb
@@ -10,45 +10,58 @@ module ActiveSupport
def test_empty?
assert @cache.empty?
- @cache[ClassCacheTest] = ClassCacheTest
+ @cache.store(ClassCacheTest)
assert !@cache.empty?
end
def test_clear!
assert @cache.empty?
- @cache[ClassCacheTest] = ClassCacheTest
+ @cache.store(ClassCacheTest)
assert !@cache.empty?
@cache.clear!
assert @cache.empty?
end
def test_set_key
- @cache[ClassCacheTest] = ClassCacheTest
+ @cache.store(ClassCacheTest)
assert @cache.key?(ClassCacheTest.name)
end
- def test_set_rejects_strings
- @cache[ClassCacheTest.name] = ClassCacheTest
- assert @cache.empty?
- end
-
def test_get_with_class
- @cache[ClassCacheTest] = ClassCacheTest
- assert_equal ClassCacheTest, @cache[ClassCacheTest]
+ @cache.store(ClassCacheTest)
+ assert_equal ClassCacheTest, @cache.get(ClassCacheTest)
end
def test_get_with_name
- @cache[ClassCacheTest] = ClassCacheTest
- assert_equal ClassCacheTest, @cache[ClassCacheTest.name]
+ @cache.store(ClassCacheTest)
+ assert_equal ClassCacheTest, @cache.get(ClassCacheTest.name)
end
def test_get_constantizes
assert @cache.empty?
- assert_equal ClassCacheTest, @cache[ClassCacheTest.name]
+ assert_equal ClassCacheTest, @cache.get(ClassCacheTest.name)
end
- def test_get_is_an_alias
- assert_equal @cache[ClassCacheTest], @cache.get(ClassCacheTest.name)
+ def test_get_constantizes_fails_on_invalid_names
+ assert @cache.empty?
+ assert_raise NameError do
+ @cache.get("OmgTotallyInvalidConstantName")
+ end
+ end
+
+ def test_get_alias
+ assert @cache.empty?
+ assert_equal @cache[ClassCacheTest.name], @cache.get(ClassCacheTest.name)
+ end
+
+ def test_safe_get_constantizes
+ assert @cache.empty?
+ assert_equal ClassCacheTest, @cache.safe_get(ClassCacheTest.name)
+ end
+
+ def test_safe_get_constantizes_doesnt_fail_on_invalid_names
+ assert @cache.empty?
+ assert_equal nil, @cache.safe_get("OmgTotallyInvalidConstantName")
end
def test_new_rejects_strings
diff --git a/activesupport/test/clean_logger_test.rb b/activesupport/test/clean_logger_test.rb
index 2cc46904b4..02693a97dc 100644
--- a/activesupport/test/clean_logger_test.rb
+++ b/activesupport/test/clean_logger_test.rb
@@ -1,11 +1,11 @@
require 'abstract_unit'
require 'stringio'
-require 'active_support/core_ext/logger'
+require 'active_support/logger'
-class CleanLoggerTest < Test::Unit::TestCase
+class CleanLoggerTest < ActiveSupport::TestCase
def setup
@out = StringIO.new
- @logger = Logger.new(@out)
+ @logger = ActiveSupport::Logger.new(@out)
end
def test_format_message
@@ -13,40 +13,11 @@ class CleanLoggerTest < Test::Unit::TestCase
assert_equal "error\n", @out.string
end
- def test_silence
- # Without yielding self.
- @logger.silence do
- @logger.debug 'debug'
- @logger.info 'info'
- @logger.warn 'warn'
- @logger.error 'error'
- @logger.fatal 'fatal'
- end
-
- # Yielding self.
- @logger.silence do |logger|
- logger.debug 'debug'
- logger.info 'info'
- logger.warn 'warn'
- logger.error 'error'
- logger.fatal 'fatal'
- end
-
- # Silencer off.
- Logger.silencer = false
- @logger.silence do |logger|
- logger.warn 'unsilenced'
- end
- Logger.silencer = true
-
- assert_equal "error\nfatal\nerror\nfatal\nunsilenced\n", @out.string
- end
-
def test_datetime_format
@logger.formatter = Logger::Formatter.new
- @logger.datetime_format = "%Y-%m-%d"
+ @logger.formatter.datetime_format = "%Y-%m-%d"
@logger.debug 'debug'
- assert_equal "%Y-%m-%d", @logger.datetime_format
+ assert_equal "%Y-%m-%d", @logger.formatter.datetime_format
assert_match(/D, \[\d\d\d\d-\d\d-\d\d#\d+\] DEBUG -- : debug/, @out.string)
end
diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb
index 4cbe56a2d2..912ce30c29 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/concern'
-class ConcernTest < Test::Unit::TestCase
+class ConcernTest < ActiveSupport::TestCase
module Baz
extend ActiveSupport::Concern
@@ -19,9 +19,6 @@ class ConcernTest < Test::Unit::TestCase
end
end
- module InstanceMethods
- end
-
included do
self.included_ran = true
end
@@ -74,7 +71,7 @@ class ConcernTest < Test::Unit::TestCase
def test_instance_methods_are_included
@klass.send(:include, Baz)
assert_equal "baz", @klass.new.baz
- assert @klass.included_modules.include?(ConcernTest::Baz::InstanceMethods)
+ assert @klass.included_modules.include?(ConcernTest::Baz)
end
def test_included_block_is_ran
@@ -92,6 +89,6 @@ class ConcernTest < Test::Unit::TestCase
def test_dependencies_with_multiple_modules
@klass.send(:include, Foo)
- assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz::InstanceMethods, ConcernTest::Baz], @klass.included_modules[0..3]
+ assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2]
end
end
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index 81d200a0c8..ec05213409 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -1,16 +1,41 @@
module Ace
module Base
class Case
+ class Dice
+ end
end
+ class Fase < Case
+ end
+ end
+ class Gas
+ include Base
end
end
+class Object
+ module AddtlGlobalConstants
+ class Case
+ class Dice
+ end
+ end
+ end
+ include AddtlGlobalConstants
+end
+
module ConstantizeTestCases
def run_constantize_tests_on
assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") }
assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") }
+ assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") }
+ assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") }
+ assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") }
+ assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") }
+ assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") }
+ assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") }
assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") }
assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") }
+ assert_nothing_raised { assert_equal Object, yield("") }
+ assert_nothing_raised { assert_equal Object, yield("::") }
assert_raise(NameError) { yield("UnknownClass") }
assert_raise(NameError) { yield("UnknownClass::Ace") }
assert_raise(NameError) { yield("UnknownClass::Ace::Base") }
@@ -18,13 +43,23 @@ module ConstantizeTestCases
assert_raise(NameError) { yield("InvalidClass\n") }
assert_raise(NameError) { yield("Ace::ConstantizeTestCases") }
assert_raise(NameError) { yield("Ace::Base::ConstantizeTestCases") }
+ assert_raise(NameError) { yield("Ace::Gas::Base") }
+ assert_raise(NameError) { yield("Ace::Gas::ConstantizeTestCases") }
end
-
+
def run_safe_constantize_tests_on
assert_nothing_raised { assert_equal Ace::Base::Case, yield("Ace::Base::Case") }
assert_nothing_raised { assert_equal Ace::Base::Case, yield("::Ace::Base::Case") }
+ assert_nothing_raised { assert_equal Ace::Base::Case::Dice, yield("Ace::Base::Case::Dice") }
+ assert_nothing_raised { assert_equal Ace::Base::Fase::Dice, yield("Ace::Base::Fase::Dice") }
+ assert_nothing_raised { assert_equal Ace::Gas::Case, yield("Ace::Gas::Case") }
+ assert_nothing_raised { assert_equal Ace::Gas::Case::Dice, yield("Ace::Gas::Case::Dice") }
+ assert_nothing_raised { assert_equal Case::Dice, yield("Case::Dice") }
+ assert_nothing_raised { assert_equal Case::Dice, yield("Object::Case::Dice") }
assert_nothing_raised { assert_equal ConstantizeTestCases, yield("ConstantizeTestCases") }
assert_nothing_raised { assert_equal ConstantizeTestCases, yield("::ConstantizeTestCases") }
+ assert_nothing_raised { assert_equal Object, yield("") }
+ assert_nothing_raised { assert_equal Object, yield("::") }
assert_nothing_raised { assert_equal nil, yield("UnknownClass") }
assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace") }
assert_nothing_raised { assert_equal nil, yield("UnknownClass::Ace::Base") }
@@ -33,5 +68,8 @@ module ConstantizeTestCases
assert_nothing_raised { assert_equal nil, yield("blargle") }
assert_nothing_raised { assert_equal nil, yield("Ace::ConstantizeTestCases") }
assert_nothing_raised { assert_equal nil, yield("Ace::Base::ConstantizeTestCases") }
+ assert_nothing_raised { assert_equal nil, yield("Ace::Gas::Base") }
+ assert_nothing_raised { assert_equal nil, yield("Ace::Gas::ConstantizeTestCases") }
+ assert_nothing_raised { assert_equal nil, yield("#<Class:0x7b8b718b>::Nested_1") }
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 52231aaeb0..58835c0ac5 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -6,7 +6,7 @@ require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext' # FIXME: pulling in all to_xml extensions
require 'active_support/hash_with_indifferent_access'
-class ArrayExtAccessTests < Test::Unit::TestCase
+class ArrayExtAccessTests < ActiveSupport::TestCase
def test_from
assert_equal %w( a b c d ), %w( a b c d ).from(0)
assert_equal %w( c d ), %w( a b c d ).from(2)
@@ -30,7 +30,7 @@ class ArrayExtAccessTests < Test::Unit::TestCase
end
end
-class ArrayExtToParamTests < Test::Unit::TestCase
+class ArrayExtToParamTests < ActiveSupport::TestCase
class ToParam < String
def to_param
"#{self}1"
@@ -52,7 +52,7 @@ class ArrayExtToParamTests < Test::Unit::TestCase
end
end
-class ArrayExtToSentenceTests < Test::Unit::TestCase
+class ArrayExtToSentenceTests < ActiveSupport::TestCase
def test_plain_array_to_sentence
assert_equal "", [].to_sentence
assert_equal "one", ['one'].to_sentence
@@ -92,7 +92,7 @@ class ArrayExtToSentenceTests < Test::Unit::TestCase
end
end
-class ArrayExtToSTests < Test::Unit::TestCase
+class ArrayExtToSTests < ActiveSupport::TestCase
def test_to_s_db
collection = [
Class.new { def id() 1 end }.new,
@@ -105,7 +105,7 @@ class ArrayExtToSTests < Test::Unit::TestCase
end
end
-class ArrayExtGroupingTests < Test::Unit::TestCase
+class ArrayExtGroupingTests < ActiveSupport::TestCase
def test_in_groups_of_with_perfect_fit
groups = []
('a'..'i').to_a.in_groups_of(3) do |group|
@@ -188,7 +188,7 @@ class ArrayExtGroupingTests < Test::Unit::TestCase
end
end
-class ArraySplitTests < Test::Unit::TestCase
+class ArraySplitTests < ActiveSupport::TestCase
def test_split_with_empty_array
assert_equal [[]], [].split(0)
end
@@ -209,7 +209,7 @@ class ArraySplitTests < Test::Unit::TestCase
end
end
-class ArrayToXmlTests < Test::Unit::TestCase
+class ArrayToXmlTests < ActiveSupport::TestCase
def test_to_xml
xml = [
{ :name => "David", :age => 26, :age_in_millis => 820497600000 },
@@ -299,7 +299,7 @@ class ArrayToXmlTests < Test::Unit::TestCase
end
end
-class ArrayExtractOptionsTests < Test::Unit::TestCase
+class ArrayExtractOptionsTests < ActiveSupport::TestCase
class HashSubclass < Hash
end
@@ -341,57 +341,37 @@ class ArrayExtractOptionsTests < Test::Unit::TestCase
end
end
-class ArrayUniqByTests < Test::Unit::TestCase
+class ArrayUniqByTests < ActiveSupport::TestCase
def test_uniq_by
- assert_equal [1,2], [1,2,3,4].uniq_by { |i| i.odd? }
- assert_equal [1,2], [1,2,3,4].uniq_by(&:even?)
- assert_equal((-5..0).to_a, (-5..5).to_a.uniq_by{ |i| i**2 })
+ ActiveSupport::Deprecation.silence do
+ assert_equal [1,2], [1,2,3,4].uniq_by { |i| i.odd? }
+ assert_equal [1,2], [1,2,3,4].uniq_by(&:even?)
+ assert_equal((-5..0).to_a, (-5..5).to_a.uniq_by{ |i| i**2 })
+ end
end
def test_uniq_by!
a = [1,2,3,4]
- a.uniq_by! { |i| i.odd? }
+ ActiveSupport::Deprecation.silence do
+ a.uniq_by! { |i| i.odd? }
+ end
assert_equal [1,2], a
a = [1,2,3,4]
- a.uniq_by! { |i| i.even? }
+ ActiveSupport::Deprecation.silence do
+ a.uniq_by! { |i| i.even? }
+ end
assert_equal [1,2], a
a = (-5..5).to_a
- a.uniq_by! { |i| i**2 }
+ ActiveSupport::Deprecation.silence do
+ a.uniq_by! { |i| i**2 }
+ end
assert_equal((-5..0).to_a, a)
end
end
-class ArrayExtRandomTests < ActiveSupport::TestCase
- def test_sample_from_array
- assert_nil [].sample
- assert_equal [], [].sample(5)
- assert_equal 42, [42].sample
- assert_equal [42], [42].sample(5)
-
- a = [:foo, :bar, 42]
- s = a.sample(2)
- assert_equal 2, s.size
- assert_equal 1, (a-s).size
- assert_equal [], a-(0..20).sum{a.sample(2)}
-
- o = Object.new
- def o.to_int; 1; end
- assert_equal [0], [0].sample(o)
-
- o = Object.new
- assert_raises(TypeError) { [0].sample(o) }
-
- o = Object.new
- def o.to_int; ''; end
- assert_raises(TypeError) { [0].sample(o) }
-
- assert_raises(ArgumentError) { [0].sample(-7) }
- end
-end
-
-class ArrayWrapperTests < Test::Unit::TestCase
+class ArrayWrapperTests < ActiveSupport::TestCase
class FakeCollection
def to_ary
["foo", "bar"]
@@ -466,7 +446,7 @@ class ArrayWrapperTests < Test::Unit::TestCase
end
end
-class ArrayPrependAppendTest < Test::Unit::TestCase
+class ArrayPrependAppendTest < ActiveSupport::TestCase
def test_append
assert_equal [1, 2], [1].append(2)
end
diff --git a/activesupport/test/core_ext/base64_ext_test.rb b/activesupport/test/core_ext/base64_ext_test.rb
deleted file mode 100644
index bd0e9f843d..0000000000
--- a/activesupport/test/core_ext/base64_ext_test.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-require 'abstract_unit'
-
-class Base64Test < Test::Unit::TestCase
- def test_no_newline_in_encoded_value
- assert_match(/\n/, ActiveSupport::Base64.encode64("oneverylongstringthatwouldnormallybesplitupbynewlinesbytheregularbase64"))
- assert_no_match(/\n/, ActiveSupport::Base64.encode64s("oneverylongstringthatwouldnormallybesplitupbynewlinesbytheregularbase64"))
- end
-end
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index b38e08a9f4..e24a089650 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
require 'bigdecimal'
require 'active_support/core_ext/big_decimal'
-class BigDecimalTest < Test::Unit::TestCase
+class BigDecimalTest < ActiveSupport::TestCase
def test_to_yaml
assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml)
assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml)
diff --git a/activesupport/test/core_ext/blank_test.rb b/activesupport/test/core_ext/blank_test.rb
index a2cf298905..a68c074777 100644
--- a/activesupport/test/core_ext/blank_test.rb
+++ b/activesupport/test/core_ext/blank_test.rb
@@ -3,7 +3,7 @@
require 'abstract_unit'
require 'active_support/core_ext/object/blank'
-class BlankTest < Test::Unit::TestCase
+class BlankTest < ActiveSupport::TestCase
BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", ' ', [], {} ]
NOT = [ EmptyFalse.new, Object.new, true, 0, 1, 'a', [nil], { nil => 0 } ]
diff --git a/activesupport/test/core_ext/class/attribute_accessor_test.rb b/activesupport/test/core_ext/class/attribute_accessor_test.rb
index 6b50f8db37..8d827f054e 100644
--- a/activesupport/test/core_ext/class/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/class/attribute_accessor_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/class/attribute_accessors'
-class ClassAttributeAccessorTest < Test::Unit::TestCase
+class ClassAttributeAccessorTest < ActiveSupport::TestCase
def setup
@class = Class.new do
cattr_accessor :foo
@@ -42,4 +42,18 @@ class ClassAttributeAccessorTest < Test::Unit::TestCase
assert !@object.respond_to?(:camp)
assert !@object.respond_to?(:camp=)
end
+
+ def test_should_raise_name_error_if_attribute_name_is_invalid
+ assert_raises NameError do
+ Class.new do
+ cattr_reader "invalid attribute name"
+ end
+ end
+
+ assert_raises NameError do
+ Class.new do
+ cattr_writer "invalid attribute name"
+ end
+ end
+ end
end
diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb
index cbfb290c48..148f82946c 100644
--- a/activesupport/test/core_ext/class/delegating_attributes_test.rb
+++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb
@@ -20,7 +20,7 @@ module DelegatingFixtures
end
end
-class DelegatingAttributesTest < Test::Unit::TestCase
+class DelegatingAttributesTest < ActiveSupport::TestCase
include DelegatingFixtures
attr_reader :single_class
diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb
index 60ba3b8f88..9c6c579ef7 100644
--- a/activesupport/test/core_ext/class_test.rb
+++ b/activesupport/test/core_ext/class_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
require 'active_support/core_ext/class'
require 'set'
-class ClassTest < Test::Unit::TestCase
+class ClassTest < ActiveSupport::TestCase
class Parent; end
class Foo < Parent; end
class Bar < Foo; end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index b4f848cd44..e14a137f84 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -57,6 +57,16 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(2005,11,28), Date.new(2005,12,04).beginning_of_week #sunday
end
+ def test_monday
+ assert_equal Date.new(2005,11,28), Date.new(2005,11,28).monday
+ assert_equal Date.new(2005,11,28), Date.new(2005,12,01).monday
+ end
+
+ def test_sunday
+ assert_equal Date.new(2008,3,2), Date.new(2008,3,02).sunday
+ assert_equal Date.new(2008,3,2), Date.new(2008,2,29).sunday
+ end
+
def test_beginning_of_week_in_calendar_reform
assert_equal Date.new(1582,10,1), Date.new(1582,10,15).beginning_of_week #friday
end
@@ -165,6 +175,18 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(1582,10,4), Date.new(1583,10,14).prev_year
end
+ def test_last_year
+ assert_equal Date.new(2004,6,5), Date.new(2005,6,5).last_year
+ end
+
+ def test_last_year_in_leap_years
+ assert_equal Date.new(1999,2,28), Date.new(2000,2,29).last_year
+ end
+
+ def test_last_year_in_calendar_reform
+ assert_equal Date.new(1582,10,4), Date.new(1583,10,14).last_year
+ end
+
def test_next_year
assert_equal Date.new(2006,6,5), Date.new(2005,6,5).next_year
end
@@ -235,6 +257,14 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(2010,2,27), Date.new(2010,3,4).prev_week(:saturday)
end
+ def test_last_week
+ assert_equal Date.new(2005,5,9), Date.new(2005,5,17).last_week
+ assert_equal Date.new(2006,12,25), Date.new(2007,1,7).last_week
+ assert_equal Date.new(2010,2,12), Date.new(2010,2,19).last_week(:friday)
+ assert_equal Date.new(2010,2,13), Date.new(2010,2,19).last_week(:saturday)
+ assert_equal Date.new(2010,2,27), Date.new(2010,3,4).last_week(:saturday)
+ end
+
def test_next_week
assert_equal Date.new(2005,2,28), Date.new(2005,2,22).next_week
assert_equal Date.new(2005,3,4), Date.new(2005,2,22).next_week(:friday)
@@ -255,6 +285,10 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).prev_month
end
+ def test_last_month_on_31st
+ assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month
+ end
+
def test_yesterday_constructor
assert_equal Date.current - 1, Date.yesterday
end
@@ -340,14 +374,14 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_day
- assert_equal Time.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day
+ assert_equal Time.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day
end
def test_end_of_day_when_zone_is_set
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
with_env_tz 'UTC' do
with_tz_default zone do
- assert_equal zone.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day
+ assert_equal zone.local(2005,2,21,23,59,59,Rational(999999999, 1000)), Date.new(2005,2,21).end_of_day
assert_equal zone, Date.new(2005,2,21).end_of_day.time_zone
end
end
@@ -374,16 +408,6 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
- if RUBY_VERSION < '1.9'
- def test_rfc3339
- assert_equal('1980-02-28', Date.new(1980, 2, 28).rfc3339)
- end
-
- def test_iso8601
- assert_equal('1980-02-28', Date.new(1980, 2, 28).iso8601)
- end
- end
-
def test_today
Date.stubs(:current).returns(Date.new(2000, 1, 1))
assert_equal false, Date.new(1999, 12, 31).today?
@@ -444,7 +468,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
-class DateExtBehaviorTest < Test::Unit::TestCase
+class DateExtBehaviorTest < ActiveSupport::TestCase
def test_date_acts_like_date
assert Date.new.acts_like_date?
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 456736cbad..3da0825489 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
-class DateTimeExtCalculationsTest < Test::Unit::TestCase
+class DateTimeExtCalculationsTest < ActiveSupport::TestCase
def test_to_s
datetime = DateTime.new(2005, 2, 21, 14, 30, 0, 0)
assert_equal "2005-02-21 14:30:00", datetime.to_s(:db)
@@ -34,8 +34,8 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
end
def test_to_time
- assert_equal Time.utc(2005, 2, 21, 10, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0, 0).to_time
- assert_equal Time.utc_time(2039, 2, 21, 10, 11, 12), DateTime.new(2039, 2, 21, 10, 11, 12, 0, 0).to_time
+ assert_equal Time.utc(2005, 2, 21, 10, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
+ assert_equal Time.utc_time(2039, 2, 21, 10, 11, 12), DateTime.new(2039, 2, 21, 10, 11, 12, 0).to_time
# DateTimes with offsets other than 0 are returned unaltered
assert_equal DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)), DateTime.new(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).to_time
# Fractional seconds are preserved
@@ -43,8 +43,8 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
end
def test_civil_from_format
- assert_equal DateTime.civil(2010, 5, 4, 0, 0, 0, DateTime.local_offset), DateTime.civil_from_format(:local, 2010, 5, 4)
- assert_equal DateTime.civil(2010, 5, 4, 0, 0, 0, 0), DateTime.civil_from_format(:utc, 2010, 5, 4)
+ assert_equal Time.local(2010, 5, 4, 0, 0, 0), DateTime.civil_from_format(:local, 2010, 5, 4)
+ assert_equal Time.utc(2010, 5, 4, 0, 0, 0), DateTime.civil_from_format(:utc, 2010, 5, 4)
end
def test_seconds_since_midnight
@@ -54,6 +54,24 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_equal 86399,DateTime.civil(2005,1,1,23,59,59).seconds_since_midnight
end
+ def test_days_to_week_start
+ assert_equal 0, Time.local(2011,11,01,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 1, Time.local(2011,11,02,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 2, Time.local(2011,11,03,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 3, Time.local(2011,11,04,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 4, Time.local(2011,11,05,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 5, Time.local(2011,11,06,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 6, Time.local(2011,11,07,0,0,0).days_to_week_start(:tuesday)
+
+ assert_equal 3, Time.local(2011,11,03,0,0,0).days_to_week_start(:monday)
+ assert_equal 3, Time.local(2011,11,04,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 3, Time.local(2011,11,05,0,0,0).days_to_week_start(:wednesday)
+ assert_equal 3, Time.local(2011,11,06,0,0,0).days_to_week_start(:thursday)
+ assert_equal 3, Time.local(2011,11,07,0,0,0).days_to_week_start(:friday)
+ assert_equal 3, Time.local(2011,11,8,0,0,0).days_to_week_start(:saturday)
+ assert_equal 3, Time.local(2011,11,9,0,0,0).days_to_week_start(:sunday)
+ end
+
def test_beginning_of_week
assert_equal DateTime.civil(2005,1,31), DateTime.civil(2005,2,4,10,10,10).beginning_of_week
assert_equal DateTime.civil(2005,11,28), DateTime.civil(2005,11,28,0,0,0).beginning_of_week #monday
@@ -73,6 +91,14 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_equal DateTime.civil(2005,2,4,23,59,59), DateTime.civil(2005,2,4,10,10,10).end_of_day
end
+ def test_beginning_of_hour
+ assert_equal DateTime.civil(2005,2,4,19,0,0), DateTime.civil(2005,2,4,19,30,10).beginning_of_hour
+ end
+
+ def test_end_of_hour
+ assert_equal DateTime.civil(2005,2,4,19,59,59), DateTime.civil(2005,2,4,19,30,10).end_of_hour
+ end
+
def test_beginning_of_month
assert_equal DateTime.civil(2005,2,1,0,0,0), DateTime.civil(2005,2,22,10,10,10).beginning_of_month
end
@@ -99,7 +125,7 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_equal DateTime.civil(2005,5,1,10), DateTime.civil(2005,6,5,10,0,0).weeks_ago(5)
assert_equal DateTime.civil(2005,4,24,10), DateTime.civil(2005,6,5,10,0,0).weeks_ago(6)
assert_equal DateTime.civil(2005,2,27,10), DateTime.civil(2005,6,5,10,0,0).weeks_ago(14)
- assert_equal DateTime.civil(2004,12,25,10), DateTime.civil(2005,1,1,10,0,0).weeks_ago(1)
+ assert_equal DateTime.civil(2004,12,25,10), DateTime.civil(2005,1,1,10,0,0).weeks_ago(1)
end
def test_months_ago
@@ -141,6 +167,10 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_equal DateTime.civil(2004,6,5,10), DateTime.civil(2005,6,5,10,0,0).prev_year
end
+ def test_last_year
+ assert_equal DateTime.civil(2004,6,5,10), DateTime.civil(2005,6,5,10,0,0).last_year
+ end
+
def test_next_year
assert_equal DateTime.civil(2006,6,5,10), DateTime.civil(2005,6,5,10,0,0).next_year
end
@@ -214,6 +244,14 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_equal DateTime.civil(2006,11,15), DateTime.civil(2006,11,23,0,0,0).prev_week(:wednesday)
end
+ def test_last_week
+ assert_equal DateTime.civil(2005,2,21), DateTime.civil(2005,3,1,15,15,10).last_week
+ assert_equal DateTime.civil(2005,2,22), DateTime.civil(2005,3,1,15,15,10).last_week(:tuesday)
+ assert_equal DateTime.civil(2005,2,25), DateTime.civil(2005,3,1,15,15,10).last_week(:friday)
+ assert_equal DateTime.civil(2006,10,30), DateTime.civil(2006,11,6,0,0,0).last_week
+ assert_equal DateTime.civil(2006,11,15), DateTime.civil(2006,11,23,0,0,0).last_week(:wednesday)
+ end
+
def test_next_week
assert_equal DateTime.civil(2005,2,28), DateTime.civil(2005,2,22,15,15,10).next_week
assert_equal DateTime.civil(2005,3,4), DateTime.civil(2005,2,22,15,15,10).next_week(:friday)
@@ -229,6 +267,10 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).prev_month
end
+ def test_last_month_on_31st
+ assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month
+ end
+
def test_xmlschema
assert_match(/^1880-02-28T15:15:10\+00:?00$/, DateTime.civil(1880, 2, 28, 15, 15, 10).xmlschema)
assert_match(/^1980-02-28T15:15:10\+00:?00$/, DateTime.civil(1980, 2, 28, 15, 15, 10).xmlschema)
@@ -313,15 +355,6 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
assert DateTime.new.acts_like_time?
end
- def test_local_offset
- with_env_tz 'US/Eastern' do
- assert_equal Rational(-5, 24), DateTime.local_offset
- end
- with_env_tz 'US/Central' do
- assert_equal Rational(-6, 24), DateTime.local_offset
- end
- end
-
def test_utc?
assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12).utc?
assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc?
diff --git a/activesupport/test/core_ext/deep_dup_test.rb b/activesupport/test/core_ext/deep_dup_test.rb
new file mode 100644
index 0000000000..91d558dbb5
--- /dev/null
+++ b/activesupport/test/core_ext/deep_dup_test.rb
@@ -0,0 +1,53 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object'
+
+class DeepDupTest < ActiveSupport::TestCase
+
+ def test_array_deep_dup
+ array = [1, [2, 3]]
+ dup = array.deep_dup
+ dup[1][2] = 4
+ assert_equal nil, array[1][2]
+ assert_equal 4, dup[1][2]
+ end
+
+ def test_hash_deep_dup
+ hash = { :a => { :b => 'b' } }
+ dup = hash.deep_dup
+ dup[:a][:c] = 'c'
+ assert_equal nil, hash[:a][:c]
+ assert_equal 'c', dup[:a][:c]
+ end
+
+ def test_array_deep_dup_with_hash_inside
+ array = [1, { :a => 2, :b => 3 } ]
+ dup = array.deep_dup
+ dup[1][:c] = 4
+ assert_equal nil, array[1][:c]
+ assert_equal 4, dup[1][:c]
+ end
+
+ def test_hash_deep_dup_with_array_inside
+ hash = { :a => [1, 2] }
+ dup = hash.deep_dup
+ dup[:a][2] = 'c'
+ assert_equal nil, hash[:a][2]
+ assert_equal 'c', dup[:a][2]
+ end
+
+ def test_deep_dup_initialize
+ zero_hash = Hash.new 0
+ hash = { :a => zero_hash }
+ dup = hash.deep_dup
+ assert_equal 0, dup[:a][44]
+ end
+
+ def test_object_deep_dup
+ object = Object.new
+ dup = object.deep_dup
+ dup.instance_variable_set(:@a, 1)
+ assert !object.instance_variable_defined?(:@a)
+ assert dup.instance_variable_defined?(:@a)
+ end
+
+end
diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/duplicable_test.rb
index e48e6a7c45..e0566e012c 100644
--- a/activesupport/test/core_ext/duplicable_test.rb
+++ b/activesupport/test/core_ext/duplicable_test.rb
@@ -3,10 +3,18 @@ require 'bigdecimal'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/numeric/time'
-class DuplicableTest < Test::Unit::TestCase
- RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, BigDecimal.new('4.56'), 5.seconds]
- YES = ['1', Object.new, /foo/, [], {}, Time.now]
- NO = [Class.new, Module.new]
+class DuplicableTest < ActiveSupport::TestCase
+ RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds]
+ YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new]
+ NO = []
+
+ begin
+ bd = BigDecimal.new('4.56')
+ YES << bd.dup
+ rescue TypeError
+ RAISE_DUP << bd
+ end
+
def test_duplicable
(RAISE_DUP + NO).each do |v|
@@ -14,7 +22,7 @@ class DuplicableTest < Test::Unit::TestCase
end
YES.each do |v|
- assert v.duplicable?
+ assert v.duplicable?, "#{v.class} should be duplicable"
end
(YES + NO).each do |v|
@@ -22,7 +30,7 @@ class DuplicableTest < Test::Unit::TestCase
end
RAISE_DUP.each do |v|
- assert_raises(TypeError) do
+ assert_raises(TypeError, v.class.name) do
v.dup
end
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index cdfa991a34..0bf48dd378 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -7,7 +7,7 @@ class SummablePayment < Payment
def +(p) self.class.new(price + p.price) end
end
-class EnumerableTests < Test::Unit::TestCase
+class EnumerableTests < ActiveSupport::TestCase
Enumerator = [].each.class
class GenericEnumerable
@@ -86,15 +86,6 @@ class EnumerableTests < Test::Unit::TestCase
assert_equal 'abc', ('a'..'c').sum
end
- def test_each_with_object
- enum = GenericEnumerable.new(%w(foo bar))
- result = enum.each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
- assert_equal({'foo' => 'FOO', 'bar' => 'BAR'}, result)
- assert_equal Enumerator, enum.each_with_object({}).class
- result2 = enum.each_with_object({}).each{|str, hsh| hsh[str] = str.upcase}
- assert_equal result, result2
- end
-
def test_index_by
payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ])
assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) },
diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb
index e1258b872e..50c9c57aa6 100644
--- a/activesupport/test/core_ext/file_test.rb
+++ b/activesupport/test/core_ext/file_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/file'
-class AtomicWriteTest < Test::Unit::TestCase
+class AtomicWriteTest < ActiveSupport::TestCase
def test_atomic_write_without_errors
contents = "Atomic Text"
File.atomic_write(file_name, Dir.pwd) do |file|
@@ -57,10 +57,6 @@ class AtomicWriteTest < Test::Unit::TestCase
File.unlink(file_name) rescue nil
end
- def test_responds_to_to_path
- assert_equal __FILE__, File.open(__FILE__, "r").to_path
- end
-
private
def file_name
"atomic.file"
diff --git a/activesupport/test/core_ext/float_ext_test.rb b/activesupport/test/core_ext/float_ext_test.rb
deleted file mode 100644
index ac7e7a8ed6..0000000000
--- a/activesupport/test/core_ext/float_ext_test.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/float/rounding'
-
-class FloatExtRoundingTests < Test::Unit::TestCase
- def test_round_for_positive_number
- assert_equal 1, 1.4.round
- assert_equal 2, 1.6.round
- assert_equal 2, 1.6.round(0)
- assert_equal 1.4, 1.4.round(1)
- assert_equal 1.4, 1.4.round(3)
- assert_equal 1.5, 1.45.round(1)
- assert_equal 1.45, 1.445.round(2)
- end
-
- def test_round_for_negative_number
- assert_equal( -1, -1.4.round )
- assert_equal( -2, -1.6.round )
- assert_equal( -1.4, -1.4.round(1) )
- assert_equal( -1.5, -1.45.round(1) )
- end
-
- def test_round_with_negative_precision
- assert_equal 123460.0, 123456.0.round(-1)
- assert_equal 123500.0, 123456.0.round(-2)
- end
-end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index fa800eada2..8239054117 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -6,7 +6,7 @@ require 'active_support/ordered_hash'
require 'active_support/core_ext/object/conversions'
require 'active_support/inflections'
-class HashExtTest < Test::Unit::TestCase
+class HashExtTest < ActiveSupport::TestCase
class IndifferentHash < HashWithIndifferentAccess
end
@@ -27,11 +27,7 @@ class HashExtTest < Test::Unit::TestCase
@symbols = { :a => 1, :b => 2 }
@mixed = { :a => 1, 'b' => 2 }
@fixnums = { 0 => 1, 1 => 2 }
- if RUBY_VERSION < '1.9.0'
- @illegal_symbols = { "\0" => 1, "" => 2, [] => 3 }
- else
- @illegal_symbols = { [] => 3 }
- end
+ @illegal_symbols = { [] => 3 }
end
def test_methods
@@ -121,6 +117,9 @@ class HashExtTest < Test::Unit::TestCase
foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
assert_kind_of NonIndifferentHash, foo["foo"]
+
+ foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of IndifferentHash, foo["foo"]
end
def test_indifferent_assorted
@@ -364,21 +363,6 @@ class HashExtTest < Test::Unit::TestCase
assert_equal expected, hash_1
end
- def test_deep_dup
- hash = { :a => { :b => 'b' } }
- dup = hash.deep_dup
- dup[:a][:c] = 'c'
- assert_equal nil, hash[:a][:c]
- assert_equal 'c', dup[:a][:c]
- end
-
- def test_deep_dup_initialize
- zero_hash = Hash.new 0
- hash = { :a => zero_hash }
- dup = hash.deep_dup
- assert_equal 0, dup[:a][44]
- end
-
def test_store_on_indifferent_access
hash = HashWithIndifferentAccess.new
hash.store(:test1, 1)
@@ -389,6 +373,15 @@ class HashExtTest < Test::Unit::TestCase
assert_equal expected, hash
end
+ def test_constructor_on_indifferent_access
+ hash = HashWithIndifferentAccess[:foo, 1]
+ assert_equal 1, hash[:foo]
+ assert_equal 1, hash['foo']
+ hash[:foo] = 3
+ assert_equal 3, hash[:foo]
+ assert_equal 3, hash['foo']
+ end
+
def test_reverse_merge
defaults = { :a => "x", :b => "y", :c => 10 }.freeze
options = { :a => 1, :b => 2 }
@@ -487,19 +480,32 @@ class HashExtTest < Test::Unit::TestCase
assert_equal 'bender', slice['login']
end
+ def test_extract
+ original = {:a => 1, :b => 2, :c => 3, :d => 4}
+ expected = {:a => 1, :b => 2}
+
+ assert_equal expected, original.extract!(:a, :b)
+ end
+
def test_except
original = { :a => 'x', :b => 'y', :c => 10 }
expected = { :a => 'x', :b => 'y' }
- # Should return a new hash with only the given keys.
+ # Should return a new hash without the given keys.
assert_equal expected, original.except(:c)
assert_not_equal expected, original
- # Should replace the hash with only the given keys.
+ # Should replace the hash without the given keys.
assert_equal expected, original.except!(:c)
assert_equal expected, original
end
+ def test_except_with_more_than_one_argument
+ original = { :a => 'x', :b => 'y', :c => 10 }
+ expected = { :a => 'x' }
+ assert_equal expected, original.except(:b, :c)
+ end
+
def test_except_with_original_frozen
original = { :a => 'x', :b => 'y' }
original.freeze
@@ -524,7 +530,7 @@ class IWriteMyOwnXML
end
end
-class HashExtToParamTests < Test::Unit::TestCase
+class HashExtToParamTests < ActiveSupport::TestCase
class ToParam < String
def to_param
"#{self}-1"
@@ -551,11 +557,11 @@ class HashExtToParamTests < Test::Unit::TestCase
end
def test_to_param_orders_by_key_in_ascending_order
- assert_equal 'a=2&b=1&c=0', ActiveSupport::OrderedHash[*%w(b 1 c 0 a 2)].to_param
+ assert_equal 'a=2&b=1&c=0', Hash[*%w(b 1 c 0 a 2)].to_param
end
end
-class HashToXmlTest < Test::Unit::TestCase
+class HashToXmlTest < ActiveSupport::TestCase
def setup
@xml_options = { :root => :person, :skip_instruct => true, :indent => 0 }
end
diff --git a/activesupport/test/core_ext/integer_ext_test.rb b/activesupport/test/core_ext/integer_ext_test.rb
index fe8c7eb224..41736fb672 100644
--- a/activesupport/test/core_ext/integer_ext_test.rb
+++ b/activesupport/test/core_ext/integer_ext_test.rb
@@ -1,7 +1,9 @@
require 'abstract_unit'
require 'active_support/core_ext/integer'
-class IntegerExtTest < Test::Unit::TestCase
+class IntegerExtTest < ActiveSupport::TestCase
+ PRIME = 22953686867719691230002707821868552601124472329079
+
def test_multiple_of
[ -7, 0, 7, 14 ].each { |i| assert i.multiple_of?(7) }
[ -7, 7, 14 ].each { |i| assert ! i.multiple_of?(6) }
@@ -11,10 +13,7 @@ class IntegerExtTest < Test::Unit::TestCase
assert !5.multiple_of?(0)
# test with a prime
- assert !22953686867719691230002707821868552601124472329079.multiple_of?(2)
- assert !22953686867719691230002707821868552601124472329079.multiple_of?(3)
- assert !22953686867719691230002707821868552601124472329079.multiple_of?(5)
- assert !22953686867719691230002707821868552601124472329079.multiple_of?(7)
+ [2, 3, 5, 7].each { |i| assert !PRIME.multiple_of?(i) }
end
def test_ordinalize
@@ -22,6 +21,10 @@ class IntegerExtTest < Test::Unit::TestCase
# Its results are tested comprehensively in the inflector test cases.
assert_equal '1st', 1.ordinalize
assert_equal '8th', 8.ordinalize
- 1000000000000000000000000000000000000000000000000000000000000000000000.ordinalize
+ end
+
+ def test_ordinal
+ assert_equal 'st', 1.ordinal
+ assert_equal 'th', 8.ordinal
end
end
diff --git a/activesupport/test/core_ext/io_test.rb b/activesupport/test/core_ext/io_test.rb
deleted file mode 100644
index b9abf685da..0000000000
--- a/activesupport/test/core_ext/io_test.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'abstract_unit'
-
-require 'active_support/core_ext/io'
-
-class IOTest < Test::Unit::TestCase
- def test_binread_one_arg
- assert_equal File.read(__FILE__), IO.binread(__FILE__)
- end
-
- def test_binread_two_args
- assert_equal File.read(__FILE__).bytes.first(10).pack('C*'),
- IO.binread(__FILE__, 10)
- end
-
- def test_binread_three_args
- actual = IO.binread(__FILE__, 5, 10)
- expected = File.open(__FILE__, 'rb') { |f|
- f.seek 10
- f.read 5
- }
- assert_equal expected, actual
- end
-end
diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb
index 995bc0751a..e90b9d454f 100644
--- a/activesupport/test/core_ext/kernel_test.rb
+++ b/activesupport/test/core_ext/kernel_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/kernel'
-class KernelTest < Test::Unit::TestCase
+class KernelTest < ActiveSupport::TestCase
def test_silence_warnings
silence_warnings { assert_nil $VERBOSE }
assert_equal 1234, silence_warnings { 1234 }
@@ -42,11 +42,6 @@ class KernelTest < Test::Unit::TestCase
assert_equal 1, silence_stderr { 1 }
end
- def test_singleton_class
- o = Object.new
- assert_equal class << o; self end, o.singleton_class
- end
-
def test_class_eval
o = Object.new
class << o; @x = 1; end
@@ -59,7 +54,7 @@ class KernelTest < Test::Unit::TestCase
end
end
-class KernelSuppressTest < Test::Unit::TestCase
+class KernelSuppressTest < ActiveSupport::TestCase
def test_reraise
assert_raise(LoadError) do
suppress(ArgumentError) { raise LoadError }
@@ -90,7 +85,7 @@ class MockStdErr
end
end
-class KernelDebuggerTest < Test::Unit::TestCase
+class KernelDebuggerTest < ActiveSupport::TestCase
def test_debugger_not_available_message_to_stderr
old_stderr = $stderr
$stderr = MockStdErr.new
@@ -112,4 +107,4 @@ class KernelDebuggerTest < Test::Unit::TestCase
ensure
Object.send(:remove_const, "Rails")
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb
index d7b8f602ca..31863d0aca 100644
--- a/activesupport/test/core_ext/load_error_test.rb
+++ b/activesupport/test/core_ext/load_error_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/load_error'
-class TestMissingSourceFile < Test::Unit::TestCase
+class TestMissingSourceFile < ActiveSupport::TestCase
def test_with_require
assert_raise(MissingSourceFile) { require 'no_this_file_don\'t_exist' }
end
@@ -16,7 +16,7 @@ class TestMissingSourceFile < Test::Unit::TestCase
end
end
-class TestLoadError < Test::Unit::TestCase
+class TestLoadError < ActiveSupport::TestCase
def test_with_require
assert_raise(LoadError) { require 'no_this_file_don\'t_exist' }
end
diff --git a/activesupport/test/core_ext/module/attr_internal_test.rb b/activesupport/test/core_ext/module/attr_internal_test.rb
index 93578c9610..2aea14cf2b 100644
--- a/activesupport/test/core_ext/module/attr_internal_test.rb
+++ b/activesupport/test/core_ext/module/attr_internal_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/module/attr_internal'
-class AttrInternalTest < Test::Unit::TestCase
+class AttrInternalTest < ActiveSupport::TestCase
def setup
@target = Class.new
@instance = @target.new
diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb
index 29889b51e0..a577f90bdd 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/module/attribute_accessors'
-class ModuleAttributeAccessorTest < Test::Unit::TestCase
+class ModuleAttributeAccessorTest < ActiveSupport::TestCase
def setup
m = @module = Module.new do
mattr_accessor :foo
@@ -44,4 +44,18 @@ class ModuleAttributeAccessorTest < Test::Unit::TestCase
assert !@object.respond_to?(:camp)
assert !@object.respond_to?(:camp=)
end
+
+ def test_should_raise_name_error_if_attribute_name_is_invalid
+ assert_raises NameError do
+ Class.new do
+ mattr_reader "invalid attribute name"
+ end
+ end
+
+ assert_raises NameError do
+ Class.new do
+ mattr_writer "invalid attribute name"
+ end
+ end
+ end
end
diff --git a/activesupport/test/core_ext/module/attribute_aliasing_test.rb b/activesupport/test/core_ext/module/attribute_aliasing_test.rb
index 065c3531e0..29c3053b47 100644
--- a/activesupport/test/core_ext/module/attribute_aliasing_test.rb
+++ b/activesupport/test/core_ext/module/attribute_aliasing_test.rb
@@ -24,7 +24,7 @@ module AttributeAliasing
end
end
-class AttributeAliasingTest < Test::Unit::TestCase
+class AttributeAliasingTest < ActiveSupport::TestCase
def test_attribute_alias
e = AttributeAliasing::Email.new
diff --git a/activesupport/test/core_ext/module/qualified_const_test.rb b/activesupport/test/core_ext/module/qualified_const_test.rb
new file mode 100644
index 0000000000..8af0b9a023
--- /dev/null
+++ b/activesupport/test/core_ext/module/qualified_const_test.rb
@@ -0,0 +1,94 @@
+require 'abstract_unit'
+require 'active_support/core_ext/module/qualified_const'
+
+module QualifiedConstTestMod
+ X = false
+
+ module M
+ X = 1
+
+ class C
+ X = 2
+ end
+ end
+
+ module N
+ include M
+ end
+end
+
+class QualifiedConstTest < ActiveSupport::TestCase
+ test "Object.qualified_const_defined?" do
+ assert Object.qualified_const_defined?("QualifiedConstTestMod")
+ assert !Object.qualified_const_defined?("NonExistingQualifiedConstTestMod")
+
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::X")
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::Y")
+
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::M::X")
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::M::Y")
+
+ if Module.method(:const_defined?).arity == 1
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
+ else
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X", false)
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X", true)
+ end
+ end
+
+ test "mod.qualified_const_defined?" do
+ assert QualifiedConstTestMod.qualified_const_defined?("M")
+ assert !QualifiedConstTestMod.qualified_const_defined?("NonExistingM")
+
+ assert QualifiedConstTestMod.qualified_const_defined?("M::X")
+ assert !QualifiedConstTestMod.qualified_const_defined?("M::Y")
+
+ assert QualifiedConstTestMod.qualified_const_defined?("M::C::X")
+ assert !QualifiedConstTestMod.qualified_const_defined?("M::C::Y")
+
+ if Module.method(:const_defined?).arity == 1
+ assert !QualifiedConstTestMod.qualified_const_defined?("QualifiedConstTestMod::N::X")
+ else
+ assert QualifiedConstTestMod.qualified_const_defined?("N::X")
+ assert !QualifiedConstTestMod.qualified_const_defined?("N::X", false)
+ assert QualifiedConstTestMod.qualified_const_defined?("N::X", true)
+ end
+ end
+
+ test "qualified_const_get" do
+ assert_equal false, Object.qualified_const_get("QualifiedConstTestMod::X")
+ assert_equal false, QualifiedConstTestMod.qualified_const_get("X")
+ assert_equal 1, QualifiedConstTestMod.qualified_const_get("M::X")
+ assert_equal 1, QualifiedConstTestMod.qualified_const_get("N::X")
+ assert_equal 2, QualifiedConstTestMod.qualified_const_get("M::C::X")
+
+ assert_raise(NameError) { QualifiedConstTestMod.qualified_const_get("M::C::Y")}
+ end
+
+ test "qualified_const_set" do
+ m = Module.new
+ assert_equal m, Object.qualified_const_set("QualifiedConstTestMod2", m)
+ assert_equal m, ::QualifiedConstTestMod2
+
+ # We are going to assign to existing constants on purpose, so silence warnings.
+ silence_warnings do
+ assert_equal true, QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", true)
+ assert_equal true, QualifiedConstTestMod::X
+
+ assert_equal 10, QualifiedConstTestMod::M.qualified_const_set("X", 10)
+ assert_equal 10, QualifiedConstTestMod::M::X
+ end
+ end
+
+ test "reject absolute paths" do
+ assert_raise(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X")}
+ assert_raise(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X::Y")}
+
+ assert_raise(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X")}
+ assert_raise(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X::Y")}
+
+ assert_raise(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X", nil)}
+ assert_raise(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X::Y", nil)}
+ end
+end
diff --git a/activesupport/test/core_ext/module/remove_method_test.rb b/activesupport/test/core_ext/module/remove_method_test.rb
new file mode 100644
index 0000000000..4657f0c175
--- /dev/null
+++ b/activesupport/test/core_ext/module/remove_method_test.rb
@@ -0,0 +1,29 @@
+require 'abstract_unit'
+require 'active_support/core_ext/module/remove_method'
+
+module RemoveMethodTests
+ class A
+ def do_something
+ return 1
+ end
+
+ end
+end
+
+class RemoveMethodTest < ActiveSupport::TestCase
+
+ def test_remove_method_from_an_object
+ RemoveMethodTests::A.class_eval{
+ self.remove_possible_method(:do_something)
+ }
+ assert !RemoveMethodTests::A.new.respond_to?(:do_something)
+ end
+
+ def test_redefine_method_in_an_object
+ RemoveMethodTests::A.class_eval{
+ self.redefine_method(:do_something) { return 100 }
+ }
+ assert_equal 100, RemoveMethodTests::A.new.do_something
+ end
+
+end \ No newline at end of file
diff --git a/activesupport/test/core_ext/module/synchronization_test.rb b/activesupport/test/core_ext/module/synchronization_test.rb
deleted file mode 100644
index 6c407e2260..0000000000
--- a/activesupport/test/core_ext/module/synchronization_test.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'thread'
-require 'abstract_unit'
-
-require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/module/synchronization'
-
-class SynchronizationTest < Test::Unit::TestCase
- def setup
- @target = Class.new
- @target.cattr_accessor :mutex, :instance_writer => false
- @target.mutex = Mutex.new
- @instance = @target.new
- end
-
- def test_synchronize_aliases_method_chain_with_synchronize
- @target.module_eval do
- attr_accessor :value
- synchronize :value, :with => :mutex
- end
- assert_respond_to @instance, :value_with_synchronization
- assert_respond_to @instance, :value_without_synchronization
- end
-
- def test_synchronize_does_not_change_behavior
- @target.module_eval do
- attr_accessor :value
- synchronize :value, :with => :mutex
- end
- expected = "some state"
- @instance.value = expected
- assert_equal expected, @instance.value
- end
-
- def test_synchronize_with_no_mutex_raises_an_argument_error
- assert_raise(ArgumentError) do
- @target.synchronize :to_s
- end
- end
-
- def test_double_synchronize_raises_an_argument_error
- @target.synchronize :to_s, :with => :mutex
- assert_raise(ArgumentError) do
- @target.synchronize :to_s, :with => :mutex
- end
- end
-
- def dummy_sync
- dummy = Object.new
- def dummy.synchronize
- @sync_count ||= 0
- @sync_count += 1
- yield
- end
- def dummy.sync_count; @sync_count; end
- dummy
- end
-
- def test_mutex_is_entered_during_method_call
- @target.mutex = dummy_sync
- @target.synchronize :to_s, :with => :mutex
- @instance.to_s
- @instance.to_s
- assert_equal 2, @target.mutex.sync_count
- end
-
- def test_can_synchronize_method_with_punctuation
- @target.module_eval do
- def dangerous?
- @dangerous
- end
- def dangerous!
- @dangerous = true
- end
- end
- @target.synchronize :dangerous?, :dangerous!, :with => :mutex
- @instance.dangerous!
- assert @instance.dangerous?
- end
-
- def test_can_synchronize_singleton_methods
- @target.mutex = dummy_sync
- class << @target
- synchronize :to_s, :with => :mutex
- end
- assert_respond_to @target, :to_s_without_synchronization
- assert_nothing_raised { @target.to_s; @target.to_s }
- assert_equal 2, @target.mutex.sync_count
- end
-end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index f11bf3dc69..6e1b3ca010 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -60,6 +60,14 @@ Tester = Struct.new(:client) do
delegate :name, :to => :client, :prefix => false
end
+class ParameterSet
+ delegate :[], :[]=, :to => :@params
+
+ def initialize
+ @params = {:foo => "bar"}
+ end
+end
+
class Name
delegate :upcase, :to => :@full_name
@@ -68,7 +76,7 @@ class Name
end
end
-class ModuleTest < Test::Unit::TestCase
+class ModuleTest < ActiveSupport::TestCase
def setup
@david = Someone.new("David", Somewhere.new("Paulina", "Chicago"))
end
@@ -83,6 +91,17 @@ class ModuleTest < Test::Unit::TestCase
assert_equal "Fred", @david.place.name
end
+ def test_delegation_to_index_get_method
+ @params = ParameterSet.new
+ assert_equal "bar", @params[:foo]
+ end
+
+ def test_delegation_to_index_set_method
+ @params = ParameterSet.new
+ @params[:foo] = "baz"
+ assert_equal "baz", @params[:foo]
+ end
+
def test_delegation_down_hierarchy
assert_equal "CHICAGO", @david.upcase
end
@@ -212,6 +231,12 @@ class ModuleTest < Test::Unit::TestCase
def test_local_constants
assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s)
end
+
+ def test_local_constant_names
+ ActiveSupport::Deprecation.silence do
+ assert_equal %w(Constant1 Constant3), Ab.local_constant_names
+ end
+ end
end
module BarMethodAliaser
@@ -245,7 +270,7 @@ module BarMethods
end
end
-class MethodAliasingTest < Test::Unit::TestCase
+class MethodAliasingTest < ActiveSupport::TestCase
def setup
Object.const_set :FooClassWithBarMethod, Class.new { def bar() 'bar' end }
@instance = FooClassWithBarMethod.new
diff --git a/activesupport/test/core_ext/name_error_test.rb b/activesupport/test/core_ext/name_error_test.rb
index 6352484d04..03ce09f22a 100644
--- a/activesupport/test/core_ext/name_error_test.rb
+++ b/activesupport/test/core_ext/name_error_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/name_error'
-class NameErrorTest < Test::Unit::TestCase
+class NameErrorTest < ActiveSupport::TestCase
def test_name_error_should_set_missing_name
SomeNameThatNobodyWillUse____Really ? 1 : 0
flunk "?!?!"
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 3a2452b4b0..1cb1e25d4c 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -3,7 +3,7 @@ require 'active_support/time'
require 'active_support/core_ext/numeric'
require 'active_support/core_ext/integer'
-class NumericExtTimeAndDateTimeTest < Test::Unit::TestCase
+class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase
def setup
@now = Time.local(2005,2,10,15,30,45)
@dtnow = DateTime.civil(2005,2,10,15,30,45)
@@ -128,7 +128,7 @@ class NumericExtTimeAndDateTimeTest < Test::Unit::TestCase
end
end
-class NumericExtDateTest < Test::Unit::TestCase
+class NumericExtDateTest < ActiveSupport::TestCase
def setup
@today = Date.today
end
@@ -151,7 +151,7 @@ class NumericExtDateTest < Test::Unit::TestCase
end
end
-class NumericExtSizeTest < Test::Unit::TestCase
+class NumericExtSizeTest < ActiveSupport::TestCase
def test_unit_in_terms_of_another
relationships = {
1024.bytes => 1.kilobyte,
diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb
index 1de857d678..22888333f5 100644
--- a/activesupport/test/core_ext/object/inclusion_test.rb
+++ b/activesupport/test/core_ext/object/inclusion_test.rb
@@ -1,7 +1,17 @@
require 'abstract_unit'
require 'active_support/core_ext/object/inclusion'
-class InTest < Test::Unit::TestCase
+class InTest < ActiveSupport::TestCase
+ def test_in_multiple_args
+ assert :b.in?(:a,:b)
+ assert !:c.in?(:a,:b)
+ end
+
+ def test_in_multiple_arrays
+ assert [1,2].in?([1,2],[2,3])
+ assert ![1,2].in?([1,3],[2,1])
+ end
+
def test_in_array
assert 1.in?([1,2])
assert !3.in?([1,2])
diff --git a/activesupport/test/core_ext/object/to_param_test.rb b/activesupport/test/core_ext/object/to_param_test.rb
index c3efefddb5..bd7c6c422a 100644
--- a/activesupport/test/core_ext/object/to_param_test.rb
+++ b/activesupport/test/core_ext/object/to_param_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/object/to_param'
-class ToParamTest < Test::Unit::TestCase
+class ToParamTest < ActiveSupport::TestCase
def test_object
foo = Object.new
def foo.to_s; 'foo' end
diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb
index c146f6cc9b..c34647c1df 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -3,7 +3,7 @@ require 'active_support/ordered_hash'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/string/output_safety.rb'
-class ToQueryTest < Test::Unit::TestCase
+class ToQueryTest < ActiveSupport::TestCase
def test_simple_conversion
assert_query_equal 'a=10', :a => 10
end
@@ -28,12 +28,12 @@ class ToQueryTest < Test::Unit::TestCase
def test_nested_conversion
assert_query_equal 'person%5Blogin%5D=seckar&person%5Bname%5D=Nicholas',
- :person => ActiveSupport::OrderedHash[:login, 'seckar', :name, 'Nicholas']
+ :person => Hash[:login, 'seckar', :name, 'Nicholas']
end
def test_multiple_nested
assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10',
- ActiveSupport::OrderedHash[:account, {:person => {:id => 20}}, :person, {:id => 10}]
+ Hash[:account, {:person => {:id => 20}}, :person, {:id => 10}]
end
def test_array_values
diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb
index 782a01213d..98ab82609e 100644
--- a/activesupport/test/core_ext/object_and_class_ext_test.rb
+++ b/activesupport/test/core_ext/object_and_class_ext_test.rb
@@ -59,7 +59,7 @@ class ObjectTests < ActiveSupport::TestCase
end
end
-class ObjectInstanceVariableTest < Test::Unit::TestCase
+class ObjectInstanceVariableTest < ActiveSupport::TestCase
def setup
@source, @dest = Object.new, Object.new
@source.instance_variable_set(:@bar, 'bar')
@@ -91,7 +91,7 @@ class ObjectInstanceVariableTest < Test::Unit::TestCase
end
end
-class ObjectTryTest < Test::Unit::TestCase
+class ObjectTryTest < ActiveSupport::TestCase
def setup
@string = "Hello"
end
@@ -101,7 +101,7 @@ class ObjectTryTest < Test::Unit::TestCase
assert !@string.respond_to?(method)
assert_raise(NoMethodError) { @string.try(method) }
end
-
+
def test_nonexisting_method_with_arguments
method = :undefined_method
assert !@string.respond_to?(method)
@@ -138,4 +138,16 @@ class ObjectTryTest < Test::Unit::TestCase
nil.try { ran = true }
assert_equal false, ran
end
+
+ def test_try_with_private_method
+ klass = Class.new do
+ private
+
+ def private_method
+ 'private method'
+ end
+ end
+
+ assert_raise(NoMethodError) { klass.new.try(:private_method) }
+ end
end
diff --git a/activesupport/test/core_ext/proc_test.rb b/activesupport/test/core_ext/proc_test.rb
index dc7b2c957d..c4d5592196 100644
--- a/activesupport/test/core_ext/proc_test.rb
+++ b/activesupport/test/core_ext/proc_test.rb
@@ -1,12 +1,14 @@
require 'abstract_unit'
require 'active_support/core_ext/proc'
-class ProcTests < Test::Unit::TestCase
+class ProcTests < ActiveSupport::TestCase
def test_bind_returns_method_with_changed_self
- block = Proc.new { self }
- assert_equal self, block.call
- bound_block = block.bind("hello")
- assert_not_equal block, bound_block
- assert_equal "hello", bound_block.call
+ assert_deprecated do
+ block = Proc.new { self }
+ assert_equal self, block.call
+ bound_block = block.bind("hello")
+ assert_not_equal block, bound_block
+ assert_equal "hello", bound_block.call
+ end
end
end
diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index 1424fa4aca..f0cdc0bfd4 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
require 'active_support/time'
require 'active_support/core_ext/range'
-class RangeTest < Test::Unit::TestCase
+class RangeTest < ActiveSupport::TestCase
def test_to_s_from_dates
date_range = Date.new(2005, 12, 10)..Date.new(2005, 12, 12)
assert_equal "BETWEEN '2005-12-10' AND '2005-12-12'", date_range.to_s(:db)
@@ -41,6 +41,18 @@ class RangeTest < Test::Unit::TestCase
assert((1..10).include?(1...10))
end
+ def test_should_compare_identical_inclusive
+ assert((1..10) === (1..10))
+ end
+
+ def test_should_compare_identical_exclusive
+ assert((1...10) === (1...10))
+ end
+
+ def test_should_compare_other_with_exlusive_end
+ assert((1..10) === (1...10))
+ end
+
def test_exclusive_end_should_not_include_identical_with_inclusive_end
assert !(1...10).include?(1..10)
end
@@ -53,25 +65,24 @@ class RangeTest < Test::Unit::TestCase
assert !(2..8).include?(5..9)
end
- def test_blockless_step
- assert_equal [1,3,5,7,9], (1..10).step(2)
+ def test_should_include_identical_exclusive_with_floats
+ assert((1.0...10.0).include?(1.0...10.0))
+ end
+
+ def test_cover_is_not_override
+ range = (1..3)
+ assert range.method(:include?) != range.method(:cover?)
end
- def test_original_step
- array = []
- (1..10).step(2) {|i| array << i }
- assert_equal [1,3,5,7,9], array
+ def test_overlaps_on_time
+ time_range_1 = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30)
+ time_range_2 = Time.utc(2005, 12, 10, 17, 00)..Time.utc(2005, 12, 10, 18, 00)
+ assert time_range_1.overlaps?(time_range_2)
end
- if RUBY_VERSION < '1.9'
- def test_cover
- assert((1..3).cover?(2))
- assert !(1..3).cover?(4)
- end
- else
- def test_cover_is_not_override
- range = (1..3)
- assert range.method(:include?) != range.method(:cover?)
- end
+ def test_no_overlaps_on_time
+ time_range_1 = Time.utc(2005, 12, 10, 15, 30)..Time.utc(2005, 12, 10, 17, 30)
+ time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00)
+ assert !time_range_1.overlaps?(time_range_2)
end
end
diff --git a/activesupport/test/core_ext/regexp_ext_test.rb b/activesupport/test/core_ext/regexp_ext_test.rb
index 68b089d5b4..c2398d31bd 100644
--- a/activesupport/test/core_ext/regexp_ext_test.rb
+++ b/activesupport/test/core_ext/regexp_ext_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/regexp'
-class RegexpExtAccessTests < Test::Unit::TestCase
+class RegexpExtAccessTests < ActiveSupport::TestCase
def test_multiline
assert_equal true, //m.multiline?
assert_equal false, //.multiline?
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 4d876954cf..8437ef1347 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -17,16 +17,10 @@ module Ace
end
end
-class StringInflectionsTest < Test::Unit::TestCase
+class StringInflectionsTest < ActiveSupport::TestCase
include InflectorTestCases
include ConstantizeTestCases
- def test_erb_escape
- string = [192, 60].pack('CC')
- expected = 192.chr + "&lt;"
- assert_equal expected, ERB::Util.html_escape(string)
- end
-
def test_strip_heredoc_on_an_empty_string
assert_equal '', ''.strip_heredoc
end
@@ -111,6 +105,10 @@ class StringInflectionsTest < Test::Unit::TestCase
assert_equal "Account", "MyApplication::Billing::Account".demodulize
end
+ def test_deconstantize
+ assert_equal "MyApplication::Billing", "MyApplication::Billing::Account".deconstantize
+ end
+
def test_foreign_key
ClassNameToForeignKeyWithUnderscore.each do |klass, foreign_key|
assert_equal(foreign_key, klass.foreign_key)
@@ -162,14 +160,6 @@ class StringInflectionsTest < Test::Unit::TestCase
assert_equal 97, 'abc'.ord
end
- if RUBY_VERSION < '1.9'
- def test_getbyte
- assert_equal 97, 'a'.getbyte(0)
- assert_equal 99, 'abc'.getbyte(2)
- assert_nil 'abc'.getbyte(3)
- end
- end
-
def test_string_to_time
assert_equal Time.utc(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time
assert_equal Time.local(2005, 2, 27, 23, 50), "2005-02-27 23:50".to_time(:local)
@@ -289,21 +279,15 @@ class StringInflectionsTest < Test::Unit::TestCase
assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => ' ')
end
- if RUBY_VERSION < '1.9.0'
- def test_truncate_multibyte
- with_kcode 'none' do
- assert_equal "\354\225\210\353\205\225\355...", "\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224".truncate(10)
- end
- with_kcode 'u' do
- assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...",
- "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".truncate(10)
- end
- end
- else
- def test_truncate_multibyte
- assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'),
- "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10)
- end
+ def test_truncate_with_omission_and_regexp_seperator
+ assert_equal "Hello[...]", "Hello Big World!".truncate(13, :omission => "[...]", :separator => /\s/)
+ assert_equal "Hello Big[...]", "Hello Big World!".truncate(14, :omission => "[...]", :separator => /\s/)
+ assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, :omission => "[...]", :separator => /\s/)
+ end
+
+ def test_truncate_multibyte
+ assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding('UTF-8'),
+ "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding('UTF-8').truncate(10)
end
def test_constantize
@@ -319,7 +303,7 @@ class StringInflectionsTest < Test::Unit::TestCase
end
end
-class StringBehaviourTest < Test::Unit::TestCase
+class StringBehaviourTest < ActiveSupport::TestCase
def test_acts_like_string
assert 'Bambi'.acts_like_string?
end
@@ -340,22 +324,8 @@ class CoreExtStringMultibyteTest < ActiveSupport::TestCase
assert !BYTE_STRING.is_utf8?
end
- if RUBY_VERSION < '1.9'
- def test_mb_chars_returns_self_when_kcode_not_set
- with_kcode('none') do
- assert_kind_of String, UNICODE_STRING.mb_chars
- end
- end
-
- def test_mb_chars_returns_an_instance_of_the_chars_proxy_when_kcode_utf8
- with_kcode('UTF8') do
- assert_kind_of ActiveSupport::Multibyte.proxy_class, UNICODE_STRING.mb_chars
- end
- end
- else
- def test_mb_chars_returns_instance_of_proxy_class
- assert_kind_of ActiveSupport::Multibyte.proxy_class, UNICODE_STRING.mb_chars
- end
+ def test_mb_chars_returns_instance_of_proxy_class
+ assert_kind_of ActiveSupport::Multibyte.proxy_class, UNICODE_STRING.mb_chars
end
end
@@ -469,6 +439,37 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert @other_string.html_safe?
end
+ test "Concatting safe onto unsafe with % yields unsafe" do
+ @other_string = "other%s"
+ string = @string.html_safe
+
+ @other_string = @other_string % string
+ assert !@other_string.html_safe?
+ end
+
+ test "Concatting unsafe onto safe with % yields escaped safe" do
+ @other_string = "other%s".html_safe
+ string = @other_string % "<foo>"
+
+ assert_equal "other&lt;foo&gt;", string
+ assert string.html_safe?
+ end
+
+ test "Concatting safe onto safe with % yields safe" do
+ @other_string = "other%s".html_safe
+ string = @string.html_safe
+
+ @other_string = @other_string % string
+ assert @other_string.html_safe?
+ end
+
+ test "Concatting with % doesn't modify a string" do
+ @other_string = ["<p>", "<b>", "<h1>"]
+ _ = "%s %s %s".html_safe % @other_string
+
+ assert_equal ["<p>", "<b>", "<h1>"], @other_string
+ end
+
test "Concatting a fixnum to safe always yields safe" do
string = @string.html_safe
string = string.concat(13)
@@ -481,10 +482,8 @@ class OutputSafetyTest < ActiveSupport::TestCase
end
test 'knows whether it is encoding aware' do
- if RUBY_VERSION >= "1.9"
+ assert_deprecated do
assert 'ruby'.encoding_aware?
- else
- assert !'ruby'.encoding_aware?
end
end
@@ -493,6 +492,23 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert string.html_safe?
assert !string.to_param.html_safe?
end
+
+ test "ERB::Util.html_escape should escape unsafe characters" do
+ string = '<>&"'
+ expected = '&lt;&gt;&amp;&quot;'
+ assert_equal expected, ERB::Util.html_escape(string)
+ end
+
+ test "ERB::Util.html_escape should correctly handle invalid UTF-8 strings" do
+ string = [192, 60].pack('CC')
+ expected = 192.chr + "&lt;"
+ assert_equal expected, ERB::Util.html_escape(string)
+ end
+
+ test "ERB::Util.html_escape should not escape safe strings" do
+ string = "<b>hello</b>".html_safe
+ assert_equal string, ERB::Util.html_escape(string)
+ end
end
class StringExcludeTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index ab9be4b18b..15c04bedf7 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -59,8 +59,28 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005,11,28), Time.local(2005,12,02,0,0,0).beginning_of_week #friday
assert_equal Time.local(2005,11,28), Time.local(2005,12,03,0,0,0).beginning_of_week #saturday
assert_equal Time.local(2005,11,28), Time.local(2005,12,04,0,0,0).beginning_of_week #sunday
+
+ end
+
+ def test_days_to_week_start
+ assert_equal 0, Time.local(2011,11,01,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 1, Time.local(2011,11,02,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 2, Time.local(2011,11,03,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 3, Time.local(2011,11,04,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 4, Time.local(2011,11,05,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 5, Time.local(2011,11,06,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 6, Time.local(2011,11,07,0,0,0).days_to_week_start(:tuesday)
+
+ assert_equal 3, Time.local(2011,11,03,0,0,0).days_to_week_start(:monday)
+ assert_equal 3, Time.local(2011,11,04,0,0,0).days_to_week_start(:tuesday)
+ assert_equal 3, Time.local(2011,11,05,0,0,0).days_to_week_start(:wednesday)
+ assert_equal 3, Time.local(2011,11,06,0,0,0).days_to_week_start(:thursday)
+ assert_equal 3, Time.local(2011,11,07,0,0,0).days_to_week_start(:friday)
+ assert_equal 3, Time.local(2011,11,8,0,0,0).days_to_week_start(:saturday)
+ assert_equal 3, Time.local(2011,11,9,0,0,0).days_to_week_start(:sunday)
end
+
def test_beginning_of_day
assert_equal Time.local(2005,2,4,0,0,0), Time.local(2005,2,4,10,10,10).beginning_of_day
with_env_tz 'US/Eastern' do
@@ -73,6 +93,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_beginning_of_hour
+ assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour
+ end
+
def test_beginning_of_month
assert_equal Time.local(2005,2,1,0,0,0), Time.local(2005,2,22,10,10,10).beginning_of_month
end
@@ -85,45 +109,49 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_day
- assert_equal Time.local(2007,8,12,23,59,59,999999.999), Time.local(2007,8,12,10,10,10).end_of_day
+ assert_equal Time.local(2007,8,12,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,12,10,10,10).end_of_day
with_env_tz 'US/Eastern' do
- assert_equal Time.local(2007,4,2,23,59,59,999999.999), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST'
- assert_equal Time.local(2007,10,29,23,59,59,999999.999), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST'
+ assert_equal Time.local(2007,4,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST'
+ assert_equal Time.local(2007,10,29,23,59,59,Rational(999999999, 1000)), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST'
end
with_env_tz 'NZ' do
- assert_equal Time.local(2006,3,19,23,59,59,999999.999), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST'
- assert_equal Time.local(2006,10,1,23,59,59,999999.999), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST'
+ assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST'
+ assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST'
end
end
def test_end_of_week
- assert_equal Time.local(2008,1,6,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_week
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,27,0,0,0).end_of_week #monday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,28,0,0,0).end_of_week #tuesday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,29,0,0,0).end_of_week #wednesday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,30,0,0,0).end_of_week #thursday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,31,0,0,0).end_of_week #friday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,01,0,0,0).end_of_week #saturday
- assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,02,0,0,0).end_of_week #sunday
+ assert_equal Time.local(2008,1,6,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,31,10,10,10).end_of_week
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,27,0,0,0).end_of_week #monday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,28,0,0,0).end_of_week #tuesday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,29,0,0,0).end_of_week #wednesday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,30,0,0,0).end_of_week #thursday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,8,31,0,0,0).end_of_week #friday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,9,01,0,0,0).end_of_week #saturday
+ assert_equal Time.local(2007,9,2,23,59,59,Rational(999999999, 1000)), Time.local(2007,9,02,0,0,0).end_of_week #sunday
+ end
+
+ def test_end_of_hour
+ assert_equal Time.local(2005,2,4,19,59,59,Rational(999999999, 1000)), Time.local(2005,2,4,19,30,10).end_of_hour
end
def test_end_of_month
- assert_equal Time.local(2005,3,31,23,59,59,999999.999), Time.local(2005,3,20,10,10,10).end_of_month
- assert_equal Time.local(2005,2,28,23,59,59,999999.999), Time.local(2005,2,20,10,10,10).end_of_month
- assert_equal Time.local(2005,4,30,23,59,59,999999.999), Time.local(2005,4,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2005,3,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,2,28,23,59,59,Rational(999999999, 1000)), Time.local(2005,2,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,4,30,23,59,59,Rational(999999999, 1000)), Time.local(2005,4,20,10,10,10).end_of_month
end
def test_end_of_quarter
- assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,2,15,10,10,10).end_of_quarter
- assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,3,31,0,0,0).end_of_quarter
- assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,21,10,10,10).end_of_quarter
- assert_equal Time.local(2007,6,30,23,59,59,999999.999), Time.local(2007,4,1,0,0,0).end_of_quarter
- assert_equal Time.local(2008,6,30,23,59,59,999999.999), Time.local(2008,5,31,0,0,0).end_of_quarter
+ assert_equal Time.local(2007,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,2,15,10,10,10).end_of_quarter
+ assert_equal Time.local(2007,3,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,3,31,0,0,0).end_of_quarter
+ assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,21,10,10,10).end_of_quarter
+ assert_equal Time.local(2007,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2007,4,1,0,0,0).end_of_quarter
+ assert_equal Time.local(2008,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2008,5,31,0,0,0).end_of_quarter
end
def test_end_of_year
- assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,2,22,10,10,10).end_of_year
- assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_year
+ assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,2,22,10,10,10).end_of_year
+ assert_equal Time.local(2007,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2007,12,31,10,10,10).end_of_year
end
def test_beginning_of_year
@@ -135,7 +163,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005,5,1,10), Time.local(2005,6,5,10,0,0).weeks_ago(5)
assert_equal Time.local(2005,4,24,10), Time.local(2005,6,5,10,0,0).weeks_ago(6)
assert_equal Time.local(2005,2,27,10), Time.local(2005,6,5,10,0,0).weeks_ago(14)
- assert_equal Time.local(2004,12,25,10), Time.local(2005,1,1,10,0,0).weeks_ago(1)
+ assert_equal Time.local(2004,12,25,10), Time.local(2005,1,1,10,0,0).weeks_ago(1)
end
def test_months_ago
@@ -178,6 +206,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).prev_year
end
+ def test_last_year
+ assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).last_year
+ end
+
def test_next_year
assert_equal Time.local(2006,6,5,10), Time.local(2005,6,5,10,0,0).next_year
end
@@ -471,6 +503,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.utc(2013,10,17,20,22,19), Time.utc(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9)
end
+ def test_advance_with_nsec
+ t = Time.at(0, Rational(108635108, 1000))
+ assert_equal t, t.advance(:months => 0)
+ end
+
def test_prev_week
with_env_tz 'US/Eastern' do
assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).prev_week
@@ -479,7 +516,17 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2006,10,30), Time.local(2006,11,6,0,0,0).prev_week
assert_equal Time.local(2006,11,15), Time.local(2006,11,23,0,0,0).prev_week(:wednesday)
end
- end
+ end
+
+ def test_last_week
+ with_env_tz 'US/Eastern' do
+ assert_equal Time.local(2005,2,21), Time.local(2005,3,1,15,15,10).last_week
+ assert_equal Time.local(2005,2,22), Time.local(2005,3,1,15,15,10).last_week(:tuesday)
+ assert_equal Time.local(2005,2,25), Time.local(2005,3,1,15,15,10).last_week(:friday)
+ assert_equal Time.local(2006,10,30), Time.local(2006,11,6,0,0,0).last_week
+ assert_equal Time.local(2006,11,15), Time.local(2006,11,23,0,0,0).last_week(:wednesday)
+ end
+ end
def test_next_week
with_env_tz 'US/Eastern' do
@@ -510,12 +557,14 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_to_s
- time = Time.utc(2005, 2, 21, 17, 44, 30)
+ time = Time.utc(2005, 2, 21, 17, 44, 30.12345678901)
assert_equal time.to_default_s, time.to_s
assert_equal time.to_default_s, time.to_s(:doesnt_exist)
assert_equal "2005-02-21 17:44:30", time.to_s(:db)
assert_equal "21 Feb 17:44", time.to_s(:short)
assert_equal "17:44", time.to_s(:time)
+ assert_equal "20050221174430", time.to_s(:number)
+ assert_equal "20050221174430123456789", time.to_s(:nsec)
assert_equal "February 21, 2005 17:44", time.to_s(:long)
assert_equal "February 21st, 2005 17:44", time.to_s(:long_ordinal)
with_env_tz "UTC" do
@@ -538,12 +587,12 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_to_datetime
- assert_equal Time.utc(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, 0, 0)
+ assert_equal Time.utc(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, 0)
with_env_tz 'US/Eastern' do
- assert_equal Time.local(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, Rational(Time.local(2005, 2, 21, 17, 44, 30).utc_offset, 86400), 0)
+ assert_equal Time.local(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, Rational(Time.local(2005, 2, 21, 17, 44, 30).utc_offset, 86400))
end
with_env_tz 'NZ' do
- assert_equal Time.local(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, Rational(Time.local(2005, 2, 21, 17, 44, 30).utc_offset, 86400), 0)
+ assert_equal Time.local(2005, 2, 21, 17, 44, 30).to_datetime, DateTime.civil(2005, 2, 21, 17, 44, 30, Rational(Time.local(2005, 2, 21, 17, 44, 30).utc_offset, 86400))
end
assert_equal ::Date::ITALY, Time.utc(2005, 2, 21, 17, 44, 30).to_datetime.start # use Ruby's default start value
end
@@ -592,15 +641,15 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
def test_time_with_datetime_fallback
assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30)
assert_equal Time.time_with_datetime_fallback(:local, 2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30)
- assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
- assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
- assert_equal Time.time_with_datetime_fallback(:utc, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, 0, 0)
+ assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0)
+ assert_equal Time.time_with_datetime_fallback(:local, 2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30)
+ assert_equal Time.time_with_datetime_fallback(:utc, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, 0)
assert_equal Time.time_with_datetime_fallback(:utc, 2005), Time.utc(2005)
- assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0, 0)
+ assert_equal Time.time_with_datetime_fallback(:utc, 2039), DateTime.civil(2039, 1, 1, 0, 0, 0, 0)
assert_equal Time.time_with_datetime_fallback(:utc, 2005, 2, 21, 17, 44, 30, 1), Time.utc(2005, 2, 21, 17, 44, 30, 1) #with usec
# This won't overflow on 64bit linux
unless time_is_64bits?
- assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil(1900, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
+ assert_equal Time.time_with_datetime_fallback(:local, 1900, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1900, 2, 21, 17, 44, 30)
assert_equal Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1),
DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
assert_equal ::Date::ITALY, Time.time_with_datetime_fallback(:utc, 2039, 2, 21, 17, 44, 30, 1).start # use Ruby's default start value
@@ -616,16 +665,16 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
def test_utc_time
assert_equal Time.utc_time(2005, 2, 21, 17, 44, 30), Time.utc(2005, 2, 21, 17, 44, 30)
- assert_equal Time.utc_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0, 0)
- assert_equal Time.utc_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, 0, 0)
+ assert_equal Time.utc_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, 0)
+ assert_equal Time.utc_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, 0)
end
def test_local_time
assert_equal Time.local_time(2005, 2, 21, 17, 44, 30), Time.local(2005, 2, 21, 17, 44, 30)
- assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil(2039, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
+ assert_equal Time.local_time(2039, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 2039, 2, 21, 17, 44, 30)
unless time_is_64bits?
- assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil(1901, 2, 21, 17, 44, 30, DateTime.local_offset, 0)
+ assert_equal Time.local_time(1901, 2, 21, 17, 44, 30), DateTime.civil_from_format(:local, 1901, 2, 21, 17, 44, 30)
end
end
@@ -637,6 +686,10 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).prev_month
end
+ def test_last_month_on_31st
+ assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month
+ end
+
def test_xmlschema_is_available
assert_nothing_raised { Time.now.xmlschema }
end
@@ -744,6 +797,12 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] ))
end
+ def test_eql?
+ assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) )
+ assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) )
+ assert_equal false,Time.utc(2000, 1, 1, 0, 0, 1).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) )
+ end
+
def test_minus_with_time_with_zone
assert_equal 86_400.0, Time.utc(2000, 1, 2) - ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1), ActiveSupport::TimeZone['UTC'] )
end
@@ -771,23 +830,32 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_all_day
- assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_day
+ assert_equal Time.local(2011,6,7,0,0,0)..Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_day
+ end
+
+ def test_all_day_with_timezone
+ beginning_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,0,0,0))
+ end_of_day = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)))
+
+ assert_equal beginning_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.begin
+ assert_equal end_of_day, ActiveSupport::TimeWithZone.new(Time.local(2011,6,7,10,10,10), ActiveSupport::TimeZone["Hawaii"]).all_day.end
end
def test_all_week
- assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_week
+ assert_equal Time.local(2011,6,6,0,0,0)..Time.local(2011,6,12,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week
+ assert_equal Time.local(2011,6,5,0,0,0)..Time.local(2011,6,11,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_week(:sunday)
end
def test_all_month
- assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_month
+ assert_equal Time.local(2011,6,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_month
end
def test_all_quarter
- assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_quarter
+ assert_equal Time.local(2011,4,1,0,0,0)..Time.local(2011,6,30,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_quarter
end
def test_all_year
- assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,999999.999), Time.local(2011,6,7,10,10,10).all_year
+ assert_equal Time.local(2011,1,1,0,0,0)..Time.local(2011,12,31,23,59,59,Rational(999999999, 1000)), Time.local(2011,6,7,10,10,10).all_year
end
protected
@@ -803,7 +871,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
-class TimeExtMarshalingTest < Test::Unit::TestCase
+class TimeExtMarshalingTest < ActiveSupport::TestCase
def test_marshaling_with_utc_instance
t = Time.utc(2000)
unmarshaled = Marshal.load(Marshal.dump(t))
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index b2309ae806..1293f104e5 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
require 'active_support/time'
require 'active_support/json'
-class TimeWithZoneTest < Test::Unit::TestCase
+class TimeWithZoneTest < ActiveSupport::TestCase
def setup
@utc = Time.utc(2000, 1, 1, 0)
@@ -80,6 +80,14 @@ class TimeWithZoneTest < Test::Unit::TestCase
ActiveSupport.use_standard_json_time_format = old
end
+ def test_nsec
+ local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))
+ with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local)
+
+ assert_equal local.nsec, with_zone.nsec
+ assert_equal with_zone.nsec, 999999999
+ end
+
def test_strftime
assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z')
end
@@ -191,7 +199,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
end
end
- def future_with_time_current_as_time_with_zone
+ def test_future_with_time_current_as_time_with_zone
twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) )
Time.stubs(:current).returns(twz)
assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
@@ -200,8 +208,15 @@ class TimeWithZoneTest < Test::Unit::TestCase
end
def test_eql?
- assert @twz.eql?(Time.utc(2000))
- assert @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) )
+ assert_equal true, @twz.eql?(Time.utc(2000))
+ assert_equal true, @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) )
+ assert_equal false, @twz.eql?( Time.utc(2000, 1, 1, 0, 0, 1) )
+ assert_equal false, @twz.eql?( DateTime.civil(1999, 12, 31, 23, 59, 59) )
+ end
+
+ def test_hash
+ assert_equal Time.utc(2000).hash, @twz.hash
+ assert_equal Time.utc(2000).hash, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]).hash
end
def test_plus_with_integer
@@ -441,9 +456,9 @@ class TimeWithZoneTest < Test::Unit::TestCase
end
def test_ruby_19_weekday_name_query_methods
- ruby_19_or_greater = RUBY_VERSION >= '1.9'
%w(sunday? monday? tuesday? wednesday? thursday? friday? saturday?).each do |name|
- assert_equal ruby_19_or_greater, @twz.respond_to?(name)
+ assert_respond_to @twz, name
+ assert_equal @twz.send(name), @twz.method(name).call
end
end
@@ -476,36 +491,50 @@ class TimeWithZoneTest < Test::Unit::TestCase
assert_equal "Fri, 31 Dec 1999 19:00:30 EST -05:00", @twz.advance(:seconds => 30).inspect
end
- def beginning_of_year
+ def test_beginning_of_year
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 01 Jan 1999 00:00:00 EST -05:00", @twz.beginning_of_year.inspect
end
- def end_of_year
+ def test_end_of_year
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_year.inspect
end
- def beginning_of_month
+ def test_beginning_of_month
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
- assert_equal "Fri, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect
+ assert_equal "Wed, 01 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_month.inspect
end
- def end_of_month
+ def test_end_of_month
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_month.inspect
end
- def beginning_of_day
+ def test_beginning_of_day
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 00:00:00 EST -05:00", @twz.beginning_of_day.inspect
end
- def end_of_day
+ def test_end_of_day
assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", @twz.inspect
assert_equal "Fri, 31 Dec 1999 23:59:59 EST -05:00", @twz.end_of_day.inspect
end
+ def test_beginning_of_hour
+ utc = Time.utc(2000, 1, 1, 0, 30)
+ twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
+ assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect
+ assert_equal "Fri, 31 Dec 1999 19:00:00 EST -05:00", twz.beginning_of_hour.inspect
+ end
+
+ def test_end_of_hour
+ utc = Time.utc(2000, 1, 1, 0, 30)
+ twz = ActiveSupport::TimeWithZone.new(utc, @time_zone)
+ assert_equal "Fri, 31 Dec 1999 19:30:00 EST -05:00", twz.inspect
+ assert_equal "Fri, 31 Dec 1999 19:59:59 EST -05:00", twz.end_of_hour.inspect
+ end
+
def test_since
assert_equal "Fri, 31 Dec 1999 19:00:01 EST -05:00", @twz.since(1).inspect
end
@@ -730,7 +759,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
end
end
-class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
+class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
def setup
@t, @dt = Time.utc(2000), DateTime.civil(2000)
end
diff --git a/activesupport/test/core_ext/uri_ext_test.rb b/activesupport/test/core_ext/uri_ext_test.rb
index d988837603..03e388dd7a 100644
--- a/activesupport/test/core_ext/uri_ext_test.rb
+++ b/activesupport/test/core_ext/uri_ext_test.rb
@@ -3,15 +3,11 @@ require 'abstract_unit'
require 'uri'
require 'active_support/core_ext/uri'
-class URIExtTest < Test::Unit::TestCase
+class URIExtTest < ActiveSupport::TestCase
def test_uri_decode_handle_multibyte
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
- if URI.const_defined?(:Parser)
- parser = URI::Parser.new
- assert_equal str, parser.unescape(parser.escape(str))
- else
- assert_equal str, URI.unescape(URI.escape(str))
- end
+ parser = URI::Parser.new
+ assert_equal str, parser.unescape(parser.escape(str))
end
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index fe8f51e11a..081e6a16fd 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -14,7 +14,7 @@ module ModuleWithConstant
InheritedConstant = "Hello"
end
-class DependenciesTest < Test::Unit::TestCase
+class DependenciesTest < ActiveSupport::TestCase
def teardown
ActiveSupport::Dependencies.clear
end
@@ -258,6 +258,85 @@ class DependenciesTest < Test::Unit::TestCase
$:.replace(original_path)
end
+ def test_require_returns_true_when_file_not_yet_required
+ path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ original_path = $:.dup
+ original_features = $".dup
+ $:.push(path)
+
+ with_loading do
+ assert_equal true, require('loaded_constant')
+ end
+ ensure
+ remove_constants(:LoadedConstant)
+ $".replace(original_features)
+ $:.replace(original_path)
+ end
+
+ def test_require_returns_true_when_file_not_yet_required_even_when_no_new_constants_added
+ path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ original_path = $:.dup
+ original_features = $".dup
+ $:.push(path)
+
+ with_loading do
+ Object.module_eval "module LoadedConstant; end"
+ assert_equal true, require('loaded_constant')
+ end
+ ensure
+ remove_constants(:LoadedConstant)
+ $".replace(original_features)
+ $:.replace(original_path)
+ end
+
+ def test_require_returns_false_when_file_already_required
+ path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ original_path = $:.dup
+ original_features = $".dup
+ $:.push(path)
+
+ with_loading do
+ require 'loaded_constant'
+ assert_equal false, require('loaded_constant')
+ end
+ ensure
+ remove_constants(:LoadedConstant)
+ $".replace(original_features)
+ $:.replace(original_path)
+ end
+
+ def test_require_raises_load_error_when_file_not_found
+ with_loading do
+ assert_raise(LoadError) { require 'this_file_dont_exist_dude' }
+ end
+ ensure
+ remove_constants(:LoadedConstant)
+ end
+
+ def test_load_returns_true_when_file_found
+ path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ original_path = $:.dup
+ original_features = $".dup
+ $:.push(path)
+
+ with_loading do
+ assert_equal true, load('loaded_constant.rb')
+ assert_equal true, load('loaded_constant.rb')
+ end
+ ensure
+ remove_constants(:LoadedConstant)
+ $".replace(original_features)
+ $:.replace(original_path)
+ end
+
+ def test_load_raises_load_error_when_file_not_found
+ with_loading do
+ assert_raise(LoadError) { load 'this_file_dont_exist_dude.rb' }
+ end
+ ensure
+ remove_constants(:LoadedConstant)
+ end
+
def failing_test_access_thru_and_upwards_fails
with_autoloading_fixtures do
assert ! defined?(ModuleFolder)
@@ -334,7 +413,7 @@ class DependenciesTest < Test::Unit::TestCase
assert ActiveSupport::Dependencies.qualified_const_defined?("Object")
assert ActiveSupport::Dependencies.qualified_const_defined?("::Object")
assert ActiveSupport::Dependencies.qualified_const_defined?("::Object::Kernel")
- assert ActiveSupport::Dependencies.qualified_const_defined?("::Test::Unit::TestCase")
+ assert ActiveSupport::Dependencies.qualified_const_defined?("::ActiveSupport::TestCase")
end
def test_qualified_const_defined_should_not_call_const_missing
diff --git a/activesupport/test/deprecation/proxy_wrappers_test.rb b/activesupport/test/deprecation/proxy_wrappers_test.rb
index aa887f274d..e4f0f0f7c2 100644
--- a/activesupport/test/deprecation/proxy_wrappers_test.rb
+++ b/activesupport/test/deprecation/proxy_wrappers_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/deprecation'
-class ProxyWrappersTest < Test::Unit::TestCase
+class ProxyWrappersTest < ActiveSupport::TestCase
Waffles = false
NewWaffles = :hamburgers
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index d77a62f108..e21f3efe36 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -93,6 +93,26 @@ class DeprecationTest < ActiveSupport::TestCase
assert_match(/foo=nil/, @b)
end
+ def test_default_stderr_behavior
+ ActiveSupport::Deprecation.behavior = :stderr
+ behavior = ActiveSupport::Deprecation.behavior.first
+
+ content = capture(:stderr) {
+ assert_nil behavior.call('Some error!', ['call stack!'])
+ }
+ assert_match(/Some error!/, content)
+ assert_match(/call stack!/, content)
+ end
+
+ def test_default_silence_behavior
+ ActiveSupport::Deprecation.behavior = :silence
+ behavior = ActiveSupport::Deprecation.behavior.first
+
+ assert_blank capture(:stderr) {
+ assert_nil behavior.call('Some error!', ['call stack!'])
+ }
+ end
+
def test_deprecated_instance_variable_proxy
assert_not_deprecated { @dtc.request.size }
@@ -120,7 +140,7 @@ class DeprecationTest < ActiveSupport::TestCase
ActiveSupport::Deprecation.warn 'abc'
ActiveSupport::Deprecation.warn 'def'
end
- rescue Test::Unit::AssertionFailedError
+ rescue MiniTest::Assertion
flunk 'assert_deprecated should match any warning in block, not just the last one'
end
@@ -166,22 +186,4 @@ class DeprecationTest < ActiveSupport::TestCase
def test_deprecation_with_explicit_message
assert_deprecated(/you now need to do something extra for this one/) { @dtc.d }
end
-
- unless defined?(::MiniTest)
- def test_assertion_failed_error_doesnt_spout_deprecation_warnings
- error_class = Class.new(StandardError) do
- def message
- ActiveSupport::Deprecation.warn 'warning in error message'
- super
- end
- end
-
- raise error_class.new('hmm')
-
- rescue => e
- error = Test::Unit::Error.new('testing ur doodz', e)
- assert_not_deprecated { error.message }
- assert_nil @last_message
- end
- end
end
diff --git a/activesupport/test/descendants_tracker_with_autoloading_test.rb b/activesupport/test/descendants_tracker_with_autoloading_test.rb
index ae18a56f44..9180f1f977 100644
--- a/activesupport/test/descendants_tracker_with_autoloading_test.rb
+++ b/activesupport/test/descendants_tracker_with_autoloading_test.rb
@@ -1,10 +1,9 @@
require 'abstract_unit'
-require 'test/unit'
require 'active_support/descendants_tracker'
require 'active_support/dependencies'
require 'descendants_tracker_test_cases'
-class DescendantsTrackerWithAutoloadingTest < Test::Unit::TestCase
+class DescendantsTrackerWithAutoloadingTest < ActiveSupport::TestCase
include DescendantsTrackerTestCases
def test_clear_with_autoloaded_parent_children_and_granchildren
@@ -32,4 +31,4 @@ class DescendantsTrackerWithAutoloadingTest < Test::Unit::TestCase
assert_equal [], Child2.descendants
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/descendants_tracker_without_autoloading_test.rb b/activesupport/test/descendants_tracker_without_autoloading_test.rb
index 1f0c32dc3f..74669aaca1 100644
--- a/activesupport/test/descendants_tracker_without_autoloading_test.rb
+++ b/activesupport/test/descendants_tracker_without_autoloading_test.rb
@@ -1,8 +1,7 @@
require 'abstract_unit'
-require 'test/unit'
require 'active_support/descendants_tracker'
require 'descendants_tracker_test_cases'
-class DescendantsTrackerWithoutAutoloadingTest < Test::Unit::TestCase
+class DescendantsTrackerWithoutAutoloadingTest < ActiveSupport::TestCase
include DescendantsTrackerTestCases
-end \ No newline at end of file
+end
diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb
index b65bb1d024..8adff5de8d 100644
--- a/activesupport/test/file_update_checker_test.rb
+++ b/activesupport/test/file_update_checker_test.rb
@@ -1,18 +1,20 @@
require 'abstract_unit'
-require 'test/unit'
require 'fileutils'
+require 'thread'
MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
-class FileUpdateCheckerTest < Test::Unit::TestCase
+class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase
FILES = %w(1.txt 2.txt 3.txt)
def setup
+ FileUtils.mkdir_p("tmp_watcher")
FileUtils.touch(FILES)
end
def teardown
- FileUtils.rm(FILES)
+ FileUtils.rm_rf("tmp_watcher")
+ FileUtils.rm_rf(FILES)
end
def test_should_not_execute_the_block_if_no_paths_are_given
@@ -22,34 +24,74 @@ class FileUpdateCheckerTest < Test::Unit::TestCase
assert_equal 0, i
end
- def test_should_invoke_the_block_on_first_call_if_it_does_not_calculate_last_updated_at_on_load
+ def test_should_not_invoke_the_block_if_no_file_has_changed
i = 0
checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- checker.execute_if_updated
- assert_equal 1, i
+ 5.times { assert !checker.execute_if_updated }
+ assert_equal 0, i
end
- def test_should_not_invoke_the_block_on_first_call_if_it_calculates_last_updated_at_on_load
+ def test_should_invoke_the_block_if_a_file_has_changed
i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES, true){ i += 1 }
- checker.execute_if_updated
- assert_equal 0, i
+ checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
+ sleep(1)
+ FileUtils.touch(FILES)
+ assert checker.execute_if_updated
+ assert_equal 1, i
end
- def test_should_not_invoke_the_block_if_no_file_has_changed
+ def test_should_be_robust_enough_to_handle_deleted_files
i = 0
checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- 5.times { checker.execute_if_updated }
+ FileUtils.rm(FILES)
+ assert checker.execute_if_updated
assert_equal 1, i
end
- def test_should_invoke_the_block_if_a_file_has_changed
+ def test_should_cache_updated_result_until_execute
i = 0
checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- checker.execute_if_updated
+ assert !checker.updated?
+
sleep(1)
FileUtils.touch(FILES)
- checker.execute_if_updated
- assert_equal 2, i
+
+ assert checker.updated?
+ checker.execute
+ assert !checker.updated?
+ end
+
+ def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => [:txt]){ i += 1 }
+ FileUtils.cd "tmp_watcher" do
+ FileUtils.touch(FILES)
+ end
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => :rb){ i += 1 }
+ FileUtils.cd "tmp_watcher" do
+ FileUtils.touch(FILES)
+ end
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ def test_should_not_block_if_a_strange_filename_used
+ FileUtils.mkdir_p("tmp_watcher/valid,yetstrange,path,")
+ FileUtils.touch(FILES.map { |file_name| "tmp_watcher/valid,yetstrange,path,/#{file_name}" })
+
+ test = Thread.new do
+ ActiveSupport::FileUpdateChecker.new([],"tmp_watcher/valid,yetstrange,path," => :txt) { i += 1 }
+ Thread.exit
+ end
+ test.priority = -1
+ test.join(5)
+
+ assert !test.alive?
end
end
diff --git a/activesupport/test/flush_cache_on_private_memoization_test.rb b/activesupport/test/flush_cache_on_private_memoization_test.rb
deleted file mode 100644
index 20768b777a..0000000000
--- a/activesupport/test/flush_cache_on_private_memoization_test.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'abstract_unit'
-require 'test/unit'
-
-class FlashCacheOnPrivateMemoizationTest < Test::Unit::TestCase
- ActiveSupport::Deprecation.silence do
- extend ActiveSupport::Memoizable
- end
-
- def test_public
- assert_method_unmemoizable :pub
- end
-
- def test_protected
- assert_method_unmemoizable :prot
- end
-
- def test_private
- assert_method_unmemoizable :priv
- end
-
- def pub; rand end
- memoize :pub
-
- protected
-
- def prot; rand end
- memoize :prot
-
- private
-
- def priv; rand end
- memoize :priv
-
- def assert_method_unmemoizable(meth, message=nil)
- full_message = build_message(message, "<?> not unmemoizable.\n", meth)
- assert_block(full_message) do
- a = send meth
- b = send meth
- unmemoize_all
- c = send meth
- a == b && a != c
- end
- end
-
-end
diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb
index f564e63f29..75a0505899 100644
--- a/activesupport/test/gzip_test.rb
+++ b/activesupport/test/gzip_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/object/blank'
-class GzipTest < Test::Unit::TestCase
+class GzipTest < ActiveSupport::TestCase
def test_compress_should_decompress_to_the_same_value
assert_equal "Hello World", ActiveSupport::Gzip.decompress(ActiveSupport::Gzip.compress("Hello World"))
end
@@ -9,10 +9,7 @@ class GzipTest < Test::Unit::TestCase
def test_compress_should_return_a_binary_string
compressed = ActiveSupport::Gzip.compress('')
- if "".encoding_aware?
- assert_equal Encoding.find('binary'), compressed.encoding
- end
-
+ assert_equal Encoding.find('binary'), compressed.encoding
assert !compressed.blank?, "a compressed blank string should not be blank"
end
end
diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb
index 34825c9b8f..4f2027f4eb 100644
--- a/activesupport/test/i18n_test.rb
+++ b/activesupport/test/i18n_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
require 'active_support/time'
require 'active_support/core_ext/array/conversions'
-class I18nTest < Test::Unit::TestCase
+class I18nTest < ActiveSupport::TestCase
def setup
@date = Date.parse("2008-7-2")
@time = Time.utc(2008, 7, 2, 16, 47, 1)
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 5c956e0075..91ae6bc189 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -4,7 +4,7 @@ require 'active_support/inflector'
require 'inflector_test_cases'
require 'constantize_test_cases'
-class InflectorTest < Test::Unit::TestCase
+class InflectorTest < ActiveSupport::TestCase
include InflectorTestCases
include ConstantizeTestCases
@@ -26,23 +26,20 @@ class InflectorTest < Test::Unit::TestCase
end
def test_uncountable_word_is_not_greedy
- uncountable_word = "ors"
- countable_word = "sponsor"
+ with_dup do
+ uncountable_word = "ors"
+ countable_word = "sponsor"
- cached_uncountables = ActiveSupport::Inflector.inflections.uncountables
+ ActiveSupport::Inflector.inflections.uncountable << uncountable_word
- ActiveSupport::Inflector.inflections.uncountable << uncountable_word
+ assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word)
+ assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word)
+ assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word)
- assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word)
- assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word)
- assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word)
-
- assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word)
- assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word)
- assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word))
-
- ensure
- ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_uncountables
+ assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word)
+ assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word)
+ assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word))
+ end
end
SingularToPlural.each do |singular, plural|
@@ -66,6 +63,14 @@ class InflectorTest < Test::Unit::TestCase
end
end
+ SingularToPlural.each do |singular, plural|
+ define_method "test_singularize_singular_#{singular}" do
+ assert_equal(singular, ActiveSupport::Inflector.singularize(singular))
+ assert_equal(singular.capitalize, ActiveSupport::Inflector.singularize(singular.capitalize))
+ end
+ end
+
+
def test_overwrite_previous_inflectors
assert_equal("series", ActiveSupport::Inflector.singularize("series"))
ActiveSupport::Inflector.inflections.singular "series", "serie"
@@ -194,6 +199,20 @@ class InflectorTest < Test::Unit::TestCase
def test_demodulize
assert_equal "Account", ActiveSupport::Inflector.demodulize("MyApplication::Billing::Account")
+ assert_equal "Account", ActiveSupport::Inflector.demodulize("Account")
+ assert_equal "", ActiveSupport::Inflector.demodulize("")
+ end
+
+ def test_deconstantize
+ assert_equal "MyApplication::Billing", ActiveSupport::Inflector.deconstantize("MyApplication::Billing::Account")
+ assert_equal "::MyApplication::Billing", ActiveSupport::Inflector.deconstantize("::MyApplication::Billing::Account")
+
+ assert_equal "MyApplication", ActiveSupport::Inflector.deconstantize("MyApplication::Billing")
+ assert_equal "::MyApplication", ActiveSupport::Inflector.deconstantize("::MyApplication::Billing")
+
+ assert_equal "", ActiveSupport::Inflector.deconstantize("Account")
+ assert_equal "", ActiveSupport::Inflector.deconstantize("::Account")
+ assert_equal "", ActiveSupport::Inflector.deconstantize("")
end
def test_foreign_key
@@ -281,7 +300,7 @@ class InflectorTest < Test::Unit::TestCase
ActiveSupport::Inflector.constantize(string)
end
end
-
+
def test_safe_constantize
run_safe_constantize_tests_on do |string|
ActiveSupport::Inflector.safe_constantize(string)
@@ -290,6 +309,12 @@ class InflectorTest < Test::Unit::TestCase
def test_ordinal
OrdinalNumbers.each do |number, ordinalized|
+ assert_equal(ordinalized, number + ActiveSupport::Inflector.ordinal(number))
+ end
+ end
+
+ def test_ordinalize
+ OrdinalNumbers.each do |number, ordinalized|
assert_equal(ordinalized, ActiveSupport::Inflector.ordinalize(number))
end
end
@@ -321,56 +346,50 @@ class InflectorTest < Test::Unit::TestCase
%w{plurals singulars uncountables humans}.each do |inflection_type|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def test_clear_#{inflection_type}
- cached_values = ActiveSupport::Inflector.inflections.#{inflection_type}
- ActiveSupport::Inflector.inflections.clear :#{inflection_type}
- assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\"
- ActiveSupport::Inflector.inflections.instance_variable_set :@#{inflection_type}, cached_values
+ with_dup do
+ ActiveSupport::Inflector.inflections.clear :#{inflection_type}
+ assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\"
+ end
end
RUBY
end
def test_clear_all
- cached_values = ActiveSupport::Inflector.inflections.plurals.dup, ActiveSupport::Inflector.inflections.singulars.dup, ActiveSupport::Inflector.inflections.uncountables.dup, ActiveSupport::Inflector.inflections.humans.dup
- ActiveSupport::Inflector.inflections do |inflect|
- # ensure any data is present
- inflect.plural(/(quiz)$/i, '\1zes')
- inflect.singular(/(database)s$/i, '\1')
- inflect.uncountable('series')
- inflect.human("col_rpted_bugs", "Reported bugs")
-
- inflect.clear :all
-
- assert inflect.plurals.empty?
- assert inflect.singulars.empty?
- assert inflect.uncountables.empty?
- assert inflect.humans.empty?
+ with_dup do
+ ActiveSupport::Inflector.inflections do |inflect|
+ # ensure any data is present
+ inflect.plural(/(quiz)$/i, '\1zes')
+ inflect.singular(/(database)s$/i, '\1')
+ inflect.uncountable('series')
+ inflect.human("col_rpted_bugs", "Reported bugs")
+
+ inflect.clear :all
+
+ assert inflect.plurals.empty?
+ assert inflect.singulars.empty?
+ assert inflect.uncountables.empty?
+ assert inflect.humans.empty?
+ end
end
- ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0]
- ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1]
- ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2]
- ActiveSupport::Inflector.inflections.instance_variable_set :@humans, cached_values[3]
end
def test_clear_with_default
- cached_values = ActiveSupport::Inflector.inflections.plurals.dup, ActiveSupport::Inflector.inflections.singulars.dup, ActiveSupport::Inflector.inflections.uncountables.dup, ActiveSupport::Inflector.inflections.humans.dup
- ActiveSupport::Inflector.inflections do |inflect|
- # ensure any data is present
- inflect.plural(/(quiz)$/i, '\1zes')
- inflect.singular(/(database)s$/i, '\1')
- inflect.uncountable('series')
- inflect.human("col_rpted_bugs", "Reported bugs")
-
- inflect.clear
-
- assert inflect.plurals.empty?
- assert inflect.singulars.empty?
- assert inflect.uncountables.empty?
- assert inflect.humans.empty?
+ with_dup do
+ ActiveSupport::Inflector.inflections do |inflect|
+ # ensure any data is present
+ inflect.plural(/(quiz)$/i, '\1zes')
+ inflect.singular(/(database)s$/i, '\1')
+ inflect.uncountable('series')
+ inflect.human("col_rpted_bugs", "Reported bugs")
+
+ inflect.clear
+
+ assert inflect.plurals.empty?
+ assert inflect.singulars.empty?
+ assert inflect.uncountables.empty?
+ assert inflect.humans.empty?
+ end
end
- ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0]
- ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1]
- ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2]
- ActiveSupport::Inflector.inflections.instance_variable_set :@humans, cached_values[3]
end
Irregularities.each do |irregularity|
@@ -419,26 +438,28 @@ class InflectorTest < Test::Unit::TestCase
end
end
- { :singulars => :singular, :plurals => :plural, :uncountables => :uncountable, :humans => :human }.each do |scope, method|
+ %w(plurals singulars uncountables humans acronyms).each do |scope|
ActiveSupport::Inflector.inflections do |inflect|
define_method("test_clear_inflections_with_#{scope}") do
- # save the inflections
- values = inflect.send(scope)
-
- # clear the inflections
- inflect.clear(scope)
-
- assert_equal [], inflect.send(scope)
-
- # restore the inflections
- if scope == :uncountables
- inflect.send(method, values)
- else
- values.reverse.each { |value| inflect.send(method, *value) }
+ with_dup do
+ # clear the inflections
+ inflect.clear(scope)
+ assert_equal [], inflect.send(scope)
end
-
- assert_equal values, inflect.send(scope)
end
end
end
+
+ # Dups the singleton and yields, restoring the original inflections later.
+ # Use this in tests what modify the state of the singleton.
+ #
+ # This helper is implemented by setting @__instance__ because in some tests
+ # there are module functions that access ActiveSupport::Inflector.inflections,
+ # so we need to replace the singleton itself.
+ def with_dup
+ original = ActiveSupport::Inflector.inflections
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original.dup)
+ ensure
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original)
+ end
end
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 0cb1f70657..9fa1f417e4 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -14,6 +14,7 @@ module InflectorTestCases
"fish" => "fish",
"jeans" => "jeans",
"funky jeans" => "funky jeans",
+ "my money" => "my money",
"category" => "categories",
"query" => "queries",
@@ -46,6 +47,7 @@ module InflectorTestCases
"medium" => "media",
"stadium" => "stadia",
"analysis" => "analyses",
+ "my_analysis" => "my_analyses",
"node_child" => "node_children",
"child" => "children",
@@ -92,6 +94,7 @@ module InflectorTestCases
"matrix_fu" => "matrix_fus",
"axis" => "axes",
+ "taxi" => "taxis", # prevents regression
"testis" => "testes",
"crisis" => "crises",
@@ -103,7 +106,13 @@ module InflectorTestCases
"edge" => "edges",
"cow" => "kine",
- "database" => "databases"
+ "database" => "databases",
+
+ # regression tests against improper inflection regexes
+ "|ice" => "|ices",
+ "|ouse" => "|ouses",
+ "slice" => "slices",
+ "police" => "police"
}
CamelToUnderscore = {
@@ -205,16 +214,22 @@ module InflectorTestCases
}
MixtureToTitleCase = {
- 'active_record' => 'Active Record',
- 'ActiveRecord' => 'Active Record',
- 'action web service' => 'Action Web Service',
- 'Action Web Service' => 'Action Web Service',
- 'Action web service' => 'Action Web Service',
- 'actionwebservice' => 'Actionwebservice',
- 'Actionwebservice' => 'Actionwebservice',
- "david's code" => "David's Code",
- "David's code" => "David's Code",
- "david's Code" => "David's Code"
+ 'active_record' => 'Active Record',
+ 'ActiveRecord' => 'Active Record',
+ 'action web service' => 'Action Web Service',
+ 'Action Web Service' => 'Action Web Service',
+ 'Action web service' => 'Action Web Service',
+ 'actionwebservice' => 'Actionwebservice',
+ 'Actionwebservice' => 'Actionwebservice',
+ "david's code" => "David's Code",
+ "David's code" => "David's Code",
+ "david's Code" => "David's Code",
+ "sgt. pepper's" => "Sgt. Pepper's",
+ "i've just seen a face" => "I've Just Seen A Face",
+ "maybe you'll be there" => "Maybe You'll Be There",
+ "¿por qué?" => '¿Por Qué?',
+ "Fred’s" => "Fred’s",
+ "Fred`s" => "Fred`s"
}
OrdinalNumbers = {
diff --git a/activesupport/test/isolation_test.rb b/activesupport/test/isolation_test.rb
deleted file mode 100644
index 2c2986ea28..0000000000
--- a/activesupport/test/isolation_test.rb
+++ /dev/null
@@ -1,182 +0,0 @@
-require 'abstract_unit'
-require 'rbconfig'
-
-if defined?(MiniTest) || defined?(Test::Unit::TestResultFailureSupport)
- $stderr.puts "Isolation tests can test test-unit 1 only"
-
-elsif ENV['CHILD']
- class ChildIsolationTest < ActiveSupport::TestCase
- include ActiveSupport::Testing::Isolation
-
- def self.setup
- File.open(File.join(File.dirname(__FILE__), "fixtures", "isolation_test"), "a") do |f|
- f.puts "hello"
- end
- end
-
- def setup
- @instance = "HELLO"
- end
-
- def teardown
- raise if @boom
- end
-
- test "runs the test" do
- assert true
- end
-
- test "captures errors" do
- raise
- end
-
- test "captures failures" do
- assert false
- end
-
- test "first runs in isolation" do
- assert_nil $x
- $x = 1
- end
-
- test "second runs in isolation" do
- assert_nil $x
- $x = 2
- end
-
- test "runs with slow tests" do
- sleep 0.3
- assert true
- sleep 0.2
- end
-
- test "runs setup" do
- assert "HELLO", @instance
- end
-
- test "runs teardown" do
- @boom = true
- end
-
- test "resets requires one" do
- assert !defined?(Custom)
- assert_equal 0, $LOADED_FEATURES.grep(/fixtures\/custom/).size
- require File.expand_path(File.join(File.dirname(__FILE__), "fixtures", "custom"))
- end
-
- test "resets requires two" do
- assert !defined?(Custom)
- assert_equal 0, $LOADED_FEATURES.grep(/fixtures\/custom/).size
- require File.expand_path(File.join(File.dirname(__FILE__), "fixtures", "custom"))
- end
- end
-else
- class ParentIsolationTest < ActiveSupport::TestCase
-
- File.open(File.join(File.dirname(__FILE__), "fixtures", "isolation_test"), "w") {}
-
- ENV["CHILD"] = "1"
- OUTPUT = `#{RbConfig::CONFIG["bindir"]}/#{RbConfig::CONFIG["ruby_install_name"]} -I#{File.dirname(__FILE__)} "#{File.expand_path(__FILE__)}" -v`
- ENV.delete("CHILD")
-
- def setup
- defined?(::MiniTest) ? parse_minitest : parse_testunit
- end
-
- def parse_testunit
- @results = {}
- OUTPUT[/Started\n\s*(.*)\s*\nFinished/mi, 1].to_s.split(/\s*\n\s*/).each do |result|
- result =~ %r'^(\w+)\(\w+\):\s*(\.|E|F)$'
- @results[$1] = { 'E' => :error, '.' => :success, 'F' => :failure }[$2]
- end
-
- # Extract the backtraces
- @backtraces = {}
- OUTPUT.scan(/^\s*\d+\).*?\n\n/m).each do |backtrace|
- # \n 1) Error:\ntest_captures_errors(ChildIsolationTest):
- backtrace =~ %r'\s*\d+\)\s*(Error|Failure):\n(\w+)'i
- @backtraces[$2] = { :type => $1, :output => backtrace }
- end
- end
-
- def parse_minitest
- @results = {}
- OUTPUT[/Started\n\s*(.*)\s*\nFinished/mi, 1].to_s.split(/\s*\n\s*/).each do |result|
- result =~ %r'^\w+#(\w+):.*:\s*(.*Assertion.*|.*RuntimeError.*|\.\s*)$'
- val = :success
- val = :error if $2.include?('RuntimeError')
- val = :failure if $2.include?('Assertion')
-
- @results[$1] = val
- end
-
- # Extract the backtraces
- @backtraces = {}
- OUTPUT.scan(/^\s*\d+\).*?\n\n/m).each do |backtrace|
- # \n 1) Error:\ntest_captures_errors(ChildIsolationTest):
- backtrace =~ %r'\s*\d+\)\s*(Error|Failure):\n(\w+)'i
- @backtraces[$2] = { :type => $1, :output => backtrace }
- end
- end
-
- def assert_failing(name)
- assert_equal :failure, @results[name.to_s], "Test #{name} failed"
- end
-
- def assert_passing(name)
- assert_equal :success, @results[name.to_s], "Test #{name} passed"
- end
-
- def assert_erroring(name)
- assert_equal :error, @results[name.to_s], "Test #{name} errored"
- end
-
- test "has all tests" do
- assert_equal 10, @results.length
- end
-
- test "passing tests are still reported" do
- assert_passing :test_runs_the_test
- assert_passing :test_runs_with_slow_tests
- end
-
- test "resets global variables" do
- assert_passing :test_first_runs_in_isolation
- assert_passing :test_second_runs_in_isolation
- end
-
- test "resets requires" do
- assert_passing :test_resets_requires_one
- assert_passing :test_resets_requires_two
- end
-
- test "erroring tests are still reported" do
- assert_erroring :test_captures_errors
- end
-
- test "runs setup and teardown methods" do
- assert_passing :test_runs_setup
- assert_erroring :test_runs_teardown
- end
-
- test "correct tests fail" do
- assert_failing :test_captures_failures
- end
-
- test "backtrace is printed for errors" do
- assert_equal 'Error', @backtraces["test_captures_errors"][:type]
- assert_match %r{isolation_test.rb:\d+}, @backtraces["test_captures_errors"][:output]
- end
-
- test "backtrace is printed for failures" do
- assert_equal 'Failure', @backtraces["test_captures_failures"][:type]
- assert_match %r{isolation_test.rb:\d+}, @backtraces["test_captures_failures"][:output]
- end
-
- test "self.setup is run only once" do
- text = File.read(File.join(File.dirname(__FILE__), "fixtures", "isolation_test"))
- assert_equal "hello\n", text
- end
-
- end
-end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 8cf1a54a99..0566ebf291 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
require 'active_support/core_ext/string/inflections'
require 'active_support/json'
-class TestJSONEncoding < Test::Unit::TestCase
+class TestJSONEncoding < ActiveSupport::TestCase
class Foo
def initialize(a, b)
@a, @b = a, b
@@ -27,6 +27,10 @@ class TestJSONEncoding < Test::Unit::TestCase
NilTests = [[ nil, %(null) ]]
NumericTests = [[ 1, %(1) ],
[ 2.5, %(2.5) ],
+ [ 0.0/0.0, %(null) ],
+ [ 1.0/0.0, %(null) ],
+ [ -1.0/0.0, %(null) ],
+ [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
[ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]]
StringTests = [[ 'this is the <string>', %("this is the \\u003Cstring\\u003E")],
@@ -38,6 +42,10 @@ class TestJSONEncoding < Test::Unit::TestCase
ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
[ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
+ RangeTests = [[ 1..2, %("1..2")],
+ [ 1...2, %("1...2")],
+ [ 1.5..2.5, %("1.5..2.5")]]
+
SymbolTests = [[ :a, %("a") ],
[ :this, %("this") ],
[ :"a b", %("a b") ]]
@@ -88,25 +96,21 @@ class TestJSONEncoding < Test::Unit::TestCase
assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d))
end
- def test_utf8_string_encoded_properly_when_kcode_is_utf8
- with_kcode 'UTF8' do
- result = ActiveSupport::JSON.encode('€2.99')
- assert_equal '"\\u20ac2.99"', result
- assert_equal(Encoding::UTF_8, result.encoding) if result.respond_to?(:encoding)
+ def test_utf8_string_encoded_properly
+ result = ActiveSupport::JSON.encode('€2.99')
+ assert_equal '"\\u20ac2.99"', result
+ assert_equal(Encoding::UTF_8, result.encoding)
- result = ActiveSupport::JSON.encode('✎☺')
- assert_equal '"\\u270e\\u263a"', result
- assert_equal(Encoding::UTF_8, result.encoding) if result.respond_to?(:encoding)
- end
+ result = ActiveSupport::JSON.encode('✎☺')
+ assert_equal '"\\u270e\\u263a"', result
+ assert_equal(Encoding::UTF_8, result.encoding)
end
- if '1.9'.respond_to?(:force_encoding)
- def test_non_utf8_string_transcodes
- s = '二'.encode('Shift_JIS')
- result = ActiveSupport::JSON.encode(s)
- assert_equal '"\\u4e8c"', result
- assert_equal Encoding::UTF_8, result.encoding
- end
+ def test_non_utf8_string_transcodes
+ s = '二'.encode('Shift_JIS')
+ result = ActiveSupport::JSON.encode(s)
+ assert_equal '"\\u4e8c"', result
+ assert_equal Encoding::UTF_8, result.encoding
end
def test_exception_raised_when_encoding_circular_reference_in_array
@@ -270,6 +274,17 @@ class TestJSONEncoding < Test::Unit::TestCase
JSON.parse(json_string_and_date))
end
+ def test_opt_out_big_decimal_string_serialization
+ big_decimal = BigDecimal('2.5')
+
+ begin
+ ActiveSupport.encode_big_decimal_as_string = false
+ assert_equal big_decimal.to_s, big_decimal.to_json
+ ensure
+ ActiveSupport.encode_big_decimal_as_string = true
+ end
+ end
+
protected
def object_keys(json_object)
diff --git a/activesupport/test/lazy_load_hooks_test.rb b/activesupport/test/lazy_load_hooks_test.rb
index 58ccc14324..7851634dbf 100644
--- a/activesupport/test/lazy_load_hooks_test.rb
+++ b/activesupport/test/lazy_load_hooks_test.rb
@@ -8,6 +8,16 @@ class LazyLoadHooksTest < ActiveSupport::TestCase
assert_equal 1, i
end
+ def test_basic_hook_with_two_registrations
+ i = 0
+ ActiveSupport.on_load(:basic_hook_with_two) { i += incr }
+ assert_equal 0, i
+ ActiveSupport.run_load_hooks(:basic_hook_with_two, FakeContext.new(2))
+ assert_equal 2, i
+ ActiveSupport.run_load_hooks(:basic_hook_with_two, FakeContext.new(5))
+ assert_equal 7, i
+ end
+
def test_hook_registered_after_run
i = 0
ActiveSupport.run_load_hooks(:registered_after)
@@ -16,6 +26,25 @@ class LazyLoadHooksTest < ActiveSupport::TestCase
assert_equal 1, i
end
+ def test_hook_registered_after_run_with_two_registrations
+ i = 0
+ ActiveSupport.run_load_hooks(:registered_after_with_two, FakeContext.new(2))
+ ActiveSupport.run_load_hooks(:registered_after_with_two, FakeContext.new(5))
+ assert_equal 0, i
+ ActiveSupport.on_load(:registered_after_with_two) { i += incr }
+ assert_equal 7, i
+ end
+
+ def test_hook_registered_interleaved_run_with_two_registrations
+ i = 0
+ ActiveSupport.run_load_hooks(:registered_interleaved_with_two, FakeContext.new(2))
+ assert_equal 0, i
+ ActiveSupport.on_load(:registered_interleaved_with_two) { i += incr }
+ assert_equal 2, i
+ ActiveSupport.run_load_hooks(:registered_interleaved_with_two, FakeContext.new(5))
+ assert_equal 7, i
+ end
+
def test_hook_receives_a_context
i = 0
ActiveSupport.on_load(:contextual) { i += incr }
diff --git a/activesupport/test/load_paths_test.rb b/activesupport/test/load_paths_test.rb
index a2d8da726a..979e25bdf3 100644
--- a/activesupport/test/load_paths_test.rb
+++ b/activesupport/test/load_paths_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class LoadPathsTest < Test::Unit::TestCase
+class LoadPathsTest < ActiveSupport::TestCase
def test_uniq_load_paths
load_paths_count = $LOAD_PATH.inject({}) { |paths, path|
expanded_path = File.expand_path(path)
diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb
index 0c1f3c51ed..2a0e8d20ed 100644
--- a/activesupport/test/log_subscriber_test.rb
+++ b/activesupport/test/log_subscriber_test.rb
@@ -11,7 +11,7 @@ class MyLogSubscriber < ActiveSupport::LogSubscriber
def foo(event)
debug "debug"
- info "info"
+ info { "info" }
warn "warn"
end
@@ -118,6 +118,6 @@ class SyncLogSubscriberTest < ActiveSupport::TestCase
assert_equal 'some_event.my_log_subscriber', @logger.logged(:info).last
assert_equal 1, @logger.logged(:error).size
- assert_equal 'Could not log "puke.my_log_subscriber" event. RuntimeError: puke', @logger.logged(:error).last
+ assert_match 'Could not log "puke.my_log_subscriber" event. RuntimeError: puke', @logger.logged(:error).last
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb
new file mode 100644
index 0000000000..eedeca30a8
--- /dev/null
+++ b/activesupport/test/logger_test.rb
@@ -0,0 +1,123 @@
+require 'abstract_unit'
+require 'multibyte_test_helpers'
+require 'stringio'
+require 'fileutils'
+require 'tempfile'
+
+class LoggerTest < ActiveSupport::TestCase
+ include MultibyteTestHelpers
+
+ Logger = ActiveSupport::Logger
+
+ def setup
+ @message = "A debug message"
+ @integer_message = 12345
+ @output = StringIO.new
+ @logger = Logger.new(@output)
+ end
+
+ def test_write_binary_data_to_existing_file
+ t = Tempfile.new ['development', 'log']
+ t.binmode
+ t.write 'hi mom!'
+ t.close
+
+ f = File.open(t.path, 'w')
+ f.binmode
+
+ logger = Logger.new f
+ logger.level = Logger::DEBUG
+
+ str = "\x80"
+ str.force_encoding("ASCII-8BIT")
+
+ logger.add Logger::DEBUG, str
+ ensure
+ logger.close
+ t.close true
+ end
+
+ def test_write_binary_data_create_file
+ fname = File.join Dir.tmpdir, 'lol', 'rofl.log'
+ FileUtils.mkdir_p File.dirname(fname)
+ f = File.open(fname, 'w')
+ f.binmode
+
+ logger = Logger.new f
+ logger.level = Logger::DEBUG
+
+ str = "\x80"
+ str.force_encoding("ASCII-8BIT")
+
+ logger.add Logger::DEBUG, str
+ ensure
+ logger.close
+ File.unlink fname
+ end
+
+ def test_should_log_debugging_message_when_debugging
+ @logger.level = Logger::DEBUG
+ @logger.add(Logger::DEBUG, @message)
+ assert @output.string.include?(@message)
+ end
+
+ def test_should_not_log_debug_messages_when_log_level_is_info
+ @logger.level = Logger::INFO
+ @logger.add(Logger::DEBUG, @message)
+ assert ! @output.string.include?(@message)
+ end
+
+ def test_should_add_message_passed_as_block_when_using_add
+ @logger.level = Logger::INFO
+ @logger.add(Logger::INFO) {@message}
+ assert @output.string.include?(@message)
+ end
+
+ def test_should_add_message_passed_as_block_when_using_shortcut
+ @logger.level = Logger::INFO
+ @logger.info {@message}
+ assert @output.string.include?(@message)
+ end
+
+ def test_should_convert_message_to_string
+ @logger.level = Logger::INFO
+ @logger.info @integer_message
+ assert @output.string.include?(@integer_message.to_s)
+ end
+
+ def test_should_convert_message_to_string_when_passed_in_block
+ @logger.level = Logger::INFO
+ @logger.info {@integer_message}
+ assert @output.string.include?(@integer_message.to_s)
+ end
+
+ def test_should_not_evaluate_block_if_message_wont_be_logged
+ @logger.level = Logger::INFO
+ evaluated = false
+ @logger.add(Logger::DEBUG) {evaluated = true}
+ assert evaluated == false
+ end
+
+ def test_should_not_mutate_message
+ message_copy = @message.dup
+ @logger.info @message
+ assert_equal message_copy, @message
+ end
+
+ def test_should_know_if_its_loglevel_is_below_a_given_level
+ Logger::Severity.constants.each do |level|
+ next if level.to_s == 'UNKNOWN'
+ @logger.level = Logger::Severity.const_get(level) - 1
+ assert @logger.send("#{level.downcase}?"), "didn't know if it was #{level.downcase}? or below"
+ end
+ end
+
+ def test_buffer_multibyte
+ @logger.info(UNICODE_STRING)
+ @logger.info(BYTE_STRING)
+ assert @output.string.include?(UNICODE_STRING)
+ byte_string = @output.string.dup
+ byte_string.force_encoding("ASCII-8BIT")
+ assert byte_string.include?(BYTE_STRING)
+ end
+end
diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb
deleted file mode 100644
index e333b9a78c..0000000000
--- a/activesupport/test/memoizable_test.rb
+++ /dev/null
@@ -1,290 +0,0 @@
-require 'abstract_unit'
-
-class MemoizableTest < ActiveSupport::TestCase
- class Person
- ActiveSupport::Deprecation.silence do
- extend ActiveSupport::Memoizable
- end
-
- attr_reader :name_calls, :age_calls, :is_developer_calls, :name_query_calls
-
- def initialize
- @name_calls = 0
- @age_calls = 0
- @is_developer_calls = 0
- @name_query_calls = 0
- end
-
- def name
- @name_calls += 1
- "Josh"
- end
-
- def name?
- @name_query_calls += 1
- true
- end
- memoize :name?
-
- def update(name)
- "Joshua"
- end
- memoize :update
-
- def age
- @age_calls += 1
- nil
- end
-
- memoize :name, :age
-
- protected
-
- def memoize_protected_test
- 'protected'
- end
- memoize :memoize_protected_test
-
- private
-
- def is_developer?
- @is_developer_calls += 1
- "Yes"
- end
- memoize :is_developer?
- end
-
- class Company
- attr_reader :name_calls
- def initialize
- @name_calls = 0
- end
-
- def name
- @name_calls += 1
- "37signals"
- end
- end
-
- module Rates
- ActiveSupport::Deprecation.silence do
- extend ActiveSupport::Memoizable
- end
-
- attr_reader :sales_tax_calls
- def sales_tax(price)
- @sales_tax_calls ||= 0
- @sales_tax_calls += 1
- price * 0.1025
- end
- memoize :sales_tax
- end
-
- class Calculator
- ActiveSupport::Deprecation.silence do
- extend ActiveSupport::Memoizable
- end
- include Rates
-
- attr_reader :fib_calls
- def initialize
- @fib_calls = 0
- end
-
- def fib(n)
- @fib_calls += 1
-
- if n == 0 || n == 1
- n
- else
- fib(n - 1) + fib(n - 2)
- end
- end
- memoize :fib
-
- def add_or_subtract(i, j, add)
- if add
- i + j
- else
- i - j
- end
- end
- memoize :add_or_subtract
-
- def counter
- @count ||= 0
- @count += 1
- end
- memoize :counter
- end
-
- def setup
- @person = Person.new
- @calculator = Calculator.new
- end
-
- def test_memoization
- assert_equal "Josh", @person.name
- assert_equal 1, @person.name_calls
-
- 3.times { assert_equal "Josh", @person.name }
- assert_equal 1, @person.name_calls
- end
-
- def test_memoization_with_punctuation
- assert_equal true, @person.name?
-
- assert_nothing_raised(NameError) do
- @person.memoize_all
- @person.unmemoize_all
- end
- end
-
- def test_memoization_flush_with_punctuation
- assert_equal true, @person.name?
- @person.flush_cache(:name?)
- 3.times { assert_equal true, @person.name? }
- assert_equal 2, @person.name_query_calls
- end
-
- def test_memoization_with_nil_value
- assert_equal nil, @person.age
- assert_equal 1, @person.age_calls
-
- 3.times { assert_equal nil, @person.age }
- assert_equal 1, @person.age_calls
- end
-
- def test_reloadable
- assert_equal 1, @calculator.counter
- assert_equal 2, @calculator.counter(:reload)
- assert_equal 2, @calculator.counter
- assert_equal 3, @calculator.counter(true)
- assert_equal 3, @calculator.counter
- end
-
- def test_flush_cache
- assert_equal 1, @calculator.counter
-
- assert @calculator.instance_variable_get(:@_memoized_counter).any?
- @calculator.flush_cache(:counter)
- assert @calculator.instance_variable_get(:@_memoized_counter).empty?
-
- assert_equal 2, @calculator.counter
- end
-
- def test_unmemoize_all
- assert_equal 1, @calculator.counter
-
- assert @calculator.instance_variable_get(:@_memoized_counter).any?
- @calculator.unmemoize_all
- assert @calculator.instance_variable_get(:@_memoized_counter).empty?
-
- assert_equal 2, @calculator.counter
- end
-
- def test_memoize_all
- @calculator.memoize_all
- assert @calculator.instance_variable_defined?(:@_memoized_counter)
- end
-
- def test_memoization_cache_is_different_for_each_instance
- assert_equal 1, @calculator.counter
- assert_equal 2, @calculator.counter(:reload)
- assert_equal 1, Calculator.new.counter
- end
-
- def test_memoized_is_not_affected_by_freeze
- @person.freeze
- assert_equal "Josh", @person.name
- assert_equal "Joshua", @person.update("Joshua")
- end
-
- def test_memoization_with_args
- assert_equal 55, @calculator.fib(10)
- assert_equal 11, @calculator.fib_calls
- end
-
- def test_reloadable_with_args
- assert_equal 55, @calculator.fib(10)
- assert_equal 11, @calculator.fib_calls
- assert_equal 55, @calculator.fib(10, :reload)
- assert_equal 12, @calculator.fib_calls
- assert_equal 55, @calculator.fib(10, true)
- assert_equal 13, @calculator.fib_calls
- end
-
- def test_memoization_with_boolean_arg
- assert_equal 4, @calculator.add_or_subtract(2, 2, true)
- assert_equal 2, @calculator.add_or_subtract(4, 2, false)
- end
-
- def test_object_memoization
- [Company.new, Company.new, Company.new].each do |company|
- ActiveSupport::Deprecation.silence do
- company.extend ActiveSupport::Memoizable
- end
- company.memoize :name
-
- assert_equal "37signals", company.name
- assert_equal 1, company.name_calls
- assert_equal "37signals", company.name
- assert_equal 1, company.name_calls
- end
- end
-
- def test_memoized_module_methods
- assert_equal 1.025, @calculator.sales_tax(10)
- assert_equal 1, @calculator.sales_tax_calls
- assert_equal 1.025, @calculator.sales_tax(10)
- assert_equal 1, @calculator.sales_tax_calls
- assert_equal 2.5625, @calculator.sales_tax(25)
- assert_equal 2, @calculator.sales_tax_calls
- end
-
- def test_object_memoized_module_methods
- company = Company.new
- company.extend(Rates)
-
- assert_equal 1.025, company.sales_tax(10)
- assert_equal 1, company.sales_tax_calls
- assert_equal 1.025, company.sales_tax(10)
- assert_equal 1, company.sales_tax_calls
- assert_equal 2.5625, company.sales_tax(25)
- assert_equal 2, company.sales_tax_calls
- end
-
- def test_double_memoization
- assert_raise(RuntimeError) { Person.memoize :name }
- person = Person.new
- ActiveSupport::Deprecation.silence do
- person.extend ActiveSupport::Memoizable
- end
- assert_raise(RuntimeError) { person.memoize :name }
-
- company = Company.new
- ActiveSupport::Deprecation.silence do
- company.extend ActiveSupport::Memoizable
- end
- company.memoize :name
- assert_raise(RuntimeError) { company.memoize :name }
- end
-
- def test_protected_method_memoization
- person = Person.new
-
- assert_raise(NoMethodError) { person.memoize_protected_test }
- assert_equal "protected", person.send(:memoize_protected_test)
- end
-
- def test_private_method_memoization
- person = Person.new
-
- assert_raise(NoMethodError) { person.is_developer? }
- assert_equal "Yes", person.send(:is_developer?)
- assert_equal 1, person.is_developer_calls
- assert_equal "Yes", person.send(:is_developer?)
- assert_equal 1, person.is_developer_calls
- end
-
-end
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 83a19f8106..b544742300 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -11,70 +11,75 @@ require 'active_support/time'
require 'active_support/json'
class MessageEncryptorTest < ActiveSupport::TestCase
-
class JSONSerializer
def dump(value)
ActiveSupport::JSON.encode(value)
end
-
+
def load(value)
ActiveSupport::JSON.decode(value)
end
end
-
+
def setup
- @encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64))
+ @secret = SecureRandom.hex(64)
+ @verifier = ActiveSupport::MessageVerifier.new(@secret, :serializer => ActiveSupport::MessageEncryptor::NullSerializer)
+ @encryptor = ActiveSupport::MessageEncryptor.new(@secret)
@data = { :some => "data", :now => Time.local(2010) }
end
- def test_simple_round_tripping
- message = @encryptor.encrypt(@data)
- assert_equal @data, @encryptor.decrypt(message)
- end
-
def test_encrypting_twice_yields_differing_cipher_text
- first_messqage = @encryptor.encrypt(@data)
- second_message = @encryptor.encrypt(@data)
+ first_messqage = @encryptor.encrypt_and_sign(@data).split("--").first
+ second_message = @encryptor.encrypt_and_sign(@data).split("--").first
assert_not_equal first_messqage, second_message
end
- def test_messing_with_either_value_causes_failure
- text, iv = @encryptor.encrypt(@data).split("--")
+ def test_messing_with_either_encrypted_values_causes_failure
+ text, iv = @verifier.verify(@encryptor.encrypt_and_sign(@data)).split("--")
assert_not_decrypted([iv, text] * "--")
assert_not_decrypted([text, munge(iv)] * "--")
assert_not_decrypted([munge(text), iv] * "--")
assert_not_decrypted([munge(text), munge(iv)] * "--")
end
+ def test_messing_with_verified_values_causes_failures
+ text, iv = @encryptor.encrypt_and_sign(@data).split("--")
+ assert_not_verified([iv, text] * "--")
+ assert_not_verified([text, munge(iv)] * "--")
+ assert_not_verified([munge(text), iv] * "--")
+ assert_not_verified([munge(text), munge(iv)] * "--")
+ end
+
def test_signed_round_tripping
message = @encryptor.encrypt_and_sign(@data)
assert_equal @data, @encryptor.decrypt_and_verify(message)
end
-
+
def test_alternative_serialization_method
encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), :serializer => JSONSerializer.new)
message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) })
assert_equal encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
end
- def test_digest_algorithm_as_second_parameter_deprecation
- assert_deprecated(/options hash/) do
- ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), 'aes-256-cbc')
- end
- end
-
private
- def assert_not_decrypted(value)
- assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do
- @encryptor.decrypt(value)
- end
+
+ def assert_not_decrypted(value)
+ assert_raise(ActiveSupport::MessageEncryptor::InvalidMessage) do
+ @encryptor.decrypt_and_verify(@verifier.generate(value))
end
+ end
- def munge(base64_string)
- bits = ActiveSupport::Base64.decode64(base64_string)
- bits.reverse!
- ActiveSupport::Base64.encode64s(bits)
+ def assert_not_verified(value)
+ assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
+ @encryptor.decrypt_and_verify(value)
end
+ end
+
+ def munge(base64_string)
+ bits = ::Base64.decode64(base64_string)
+ bits.reverse!
+ ::Base64.strict_encode64(bits)
+ end
end
end
diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index 35747abe5b..5adff41653 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -50,12 +50,6 @@ class MessageVerifierTest < ActiveSupport::TestCase
assert_equal verifier.verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" }
end
- def test_digest_algorithm_as_second_parameter_deprecation
- assert_deprecated(/options hash/) do
- ActiveSupport::MessageVerifier.new("secret", "SHA1")
- end
- end
-
def assert_not_verified(message)
assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do
@verifier.verify(message)
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index bfff10fff2..a8d69d0ec3 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -7,9 +7,10 @@ class String
def __method_for_multibyte_testing_with_integer_result; 1; end
def __method_for_multibyte_testing; 'result'; end
def __method_for_multibyte_testing!; 'result'; end
+ def __method_for_multibyte_testing_that_returns_nil!; end
end
-class MultibyteCharsTest < Test::Unit::TestCase
+class MultibyteCharsTest < ActiveSupport::TestCase
include MultibyteTestHelpers
def setup
@@ -36,11 +37,15 @@ class MultibyteCharsTest < Test::Unit::TestCase
assert_not_equal @chars.object_id, @chars.__method_for_multibyte_testing.object_id
end
- def test_forwarded_bang_method_calls_should_return_the_original_chars_instance
+ def test_forwarded_bang_method_calls_should_return_the_original_chars_instance_when_result_is_not_nil
assert_kind_of @proxy_class, @chars.__method_for_multibyte_testing!
assert_equal @chars.object_id, @chars.__method_for_multibyte_testing!.object_id
end
+ def test_forwarded_bang_method_calls_should_return_nil_when_result_is_nil
+ assert_nil @chars.__method_for_multibyte_testing_that_returns_nil!
+ end
+
def test_methods_are_forwarded_to_wrapped_string_for_byte_strings
assert_equal BYTE_STRING.class, BYTE_STRING.mb_chars.class
end
@@ -67,17 +72,6 @@ class MultibyteCharsTest < Test::Unit::TestCase
assert !@proxy_class.consumes?(BYTE_STRING)
end
- def test_unpack_utf8_strings
- assert_equal 4, ActiveSupport::Multibyte::Unicode.u_unpack(UNICODE_STRING).length
- assert_equal 5, ActiveSupport::Multibyte::Unicode.u_unpack(ASCII_STRING).length
- end
-
- def test_unpack_raises_encoding_error_on_broken_strings
- assert_raise(ActiveSupport::Multibyte::EncodingError) do
- ActiveSupport::Multibyte::Unicode.u_unpack(BYTE_STRING)
- end
- end
-
def test_concatenation_should_return_a_proxy_class_instance
assert_equal ActiveSupport::Multibyte.proxy_class, ('a'.mb_chars + 'b').class
assert_equal ActiveSupport::Multibyte.proxy_class, ('a'.mb_chars << 'b').class
@@ -94,22 +88,18 @@ class MultibyteCharsTest < Test::Unit::TestCase
assert(('a'.mb_chars << 'b'.mb_chars).kind_of?(@proxy_class))
end
+ def test_should_return_string_as_json
+ assert_equal UNICODE_STRING, @chars.as_json
+ end
end
-class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
+class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
include MultibyteTestHelpers
def setup
@chars = UNICODE_STRING.dup.mb_chars
-
- if RUBY_VERSION < '1.9'
- # Multibyte support all kinds of whitespace (ie. NEWLINE, SPACE, EM SPACE)
- @whitespace = "\n\t#{[32, 8195].pack('U*')}"
- else
- # Ruby 1.9 only supports basic whitespace
- @whitespace = "\n\t "
- end
-
+ # Ruby 1.9 only supports basic whitespace
+ @whitespace = "\n\t "
@byte_order_mark = [65279].pack('U')
end
@@ -119,15 +109,11 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
end
end
- def test_indexed_insert_accepts_fixnums
- @chars[2] = 32
- assert_equal 'こに わ', @chars
- end
-
- %w{capitalize downcase lstrip reverse rstrip strip upcase}.each do |method|
+ %w{capitalize downcase lstrip reverse rstrip swapcase upcase}.each do |method|
class_eval(<<-EOTESTS)
- def test_#{method}_bang_should_return_self
- assert_equal @chars.object_id, @chars.send("#{method}!").object_id
+ def test_#{method}_bang_should_return_self_when_modifying_wrapped_string
+ chars = ' él piDió Un bUen café '
+ assert_equal chars.object_id, chars.send("#{method}!").object_id
end
def test_#{method}_bang_should_change_wrapped_string
@@ -150,10 +136,8 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
assert_not_equal original, proxy.to_s
end
- if RUBY_VERSION >= '1.9'
- def test_unicode_string_should_have_utf8_encoding
- assert_equal Encoding::UTF_8, UNICODE_STRING.encoding
- end
+ def test_unicode_string_should_have_utf8_encoding
+ assert_equal Encoding::UTF_8, UNICODE_STRING.encoding
end
def test_identity
@@ -180,6 +164,7 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
assert chars('').decompose.kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars('').compose.kind_of?(ActiveSupport::Multibyte.proxy_class)
assert chars('').tidy_bytes.kind_of?(ActiveSupport::Multibyte.proxy_class)
+ assert chars('').swapcase.kind_of?(ActiveSupport::Multibyte.proxy_class)
end
def test_should_be_equal_to_the_wrapped_string
@@ -428,7 +413,7 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
def test_slice_bang_removes_the_slice_from_the_receiver
chars = 'úüù'.mb_chars
chars.slice!(0,2)
- assert_equal 'úü', chars
+ assert_equal 'ù', chars
end
def test_slice_should_throw_exceptions_on_invalid_arguments
@@ -451,6 +436,11 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
assert_equal 'abc', 'aBc'.mb_chars.downcase
end
+ def test_swapcase_should_swap_ascii_characters
+ assert_equal '', ''.mb_chars.swapcase
+ assert_equal 'AbC', 'aBc'.mb_chars.swapcase
+ end
+
def test_capitalize_should_work_on_ascii_characters
assert_equal '', ''.mb_chars.capitalize
assert_equal 'Abc', 'abc'.mb_chars.capitalize
@@ -468,6 +458,15 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
assert !''.mb_chars.respond_to?(:undefined_method) # Not defined
end
+ def test_method_works_for_proxyed_methods
+ assert_equal 'll', 'hello'.mb_chars.method(:slice).call(2..3) # Defined on Chars
+ chars = 'hello'.mb_chars
+ assert_equal 'Hello', chars.method(:capitalize!).call # Defined on Chars
+ assert_equal 'Hello', chars
+ assert_equal 'jello', 'hello'.mb_chars.method(:gsub).call(/h/, 'j') # Defined on String
+ assert_raise(NameError){ ''.mb_chars.method(:undefined_method) } # Not defined
+ end
+
def test_acts_like_string
assert 'Bambi'.mb_chars.acts_like_string?
end
@@ -476,7 +475,7 @@ end
# The default Multibyte Chars proxy has more features than the normal string implementation. Tests
# for the implementation of these features should run on all Ruby versions and shouldn't be tested
# through the proxy methods.
-class MultibyteCharsExtrasTest < Test::Unit::TestCase
+class MultibyteCharsExtrasTest < ActiveSupport::TestCase
include MultibyteTestHelpers
def test_upcase_should_be_unicode_aware
@@ -485,10 +484,15 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase
end
def test_downcase_should_be_unicode_aware
- assert_equal "абвгд\0f", chars("аБвгд\0f").downcase
+ assert_equal "абвгд\0f", chars("аБвгд\0F").downcase
assert_equal 'こにちわ', chars('こにちわ').downcase
end
+ def test_swapcase_should_be_unicode_aware
+ assert_equal "аaéÜ\0f", chars("АAÉü\0F").swapcase
+ assert_equal 'こにちわ', chars('こにちわ').swapcase
+ end
+
def test_capitalize_should_be_unicode_aware
{ 'аБвг аБвг' => 'Абвг абвг',
'аБвг АБВГ' => 'Абвг абвг',
@@ -515,7 +519,7 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase
def test_limit_should_work_on_a_multibyte_string
example = chars(UNICODE_STRING)
- bytesize = UNICODE_STRING.respond_to?(:bytesize) ? UNICODE_STRING.bytesize : UNICODE_STRING.size
+ bytesize = UNICODE_STRING.bytesize
assert_equal UNICODE_STRING, example.limit(bytesize)
assert_equal '', example.limit(0)
@@ -614,7 +618,7 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase
else
str = input
end
- assert_equal expected_length, chars(str).g_length
+ assert_equal expected_length, chars(str).grapheme_length
end
end
diff --git a/activesupport/test/multibyte_conformance.rb b/activesupport/test/multibyte_conformance.rb
index b3b477bb75..2baf724da4 100644
--- a/activesupport/test/multibyte_conformance.rb
+++ b/activesupport/test/multibyte_conformance.rb
@@ -25,7 +25,7 @@ class Downloader
end
end
-class MultibyteConformanceTest < Test::Unit::TestCase
+class MultibyteConformanceTest < ActiveSupport::TestCase
include MultibyteTestHelpers
UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index 8839b75601..fdbe2f4350 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -3,10 +3,7 @@
module MultibyteTestHelpers
UNICODE_STRING = 'こにちわ'
ASCII_STRING = 'ohayo'
- BYTE_STRING = "\270\236\010\210\245"
- if BYTE_STRING.respond_to?(:force_encoding)
- BYTE_STRING.force_encoding("ASCII-8BIT")
- end
+ BYTE_STRING = "\270\236\010\210\245".force_encoding("ASCII-8BIT")
def chars(str)
ActiveSupport::Multibyte::Chars.new(str)
diff --git a/activesupport/test/multibyte_unicode_database_test.rb b/activesupport/test/multibyte_unicode_database_test.rb
index 26a41579c2..bec65daf50 100644
--- a/activesupport/test/multibyte_unicode_database_test.rb
+++ b/activesupport/test/multibyte_unicode_database_test.rb
@@ -2,7 +2,7 @@
require 'abstract_unit'
-class MultibyteUnicodeDatabaseTest < Test::Unit::TestCase
+class MultibyteUnicodeDatabaseTest < ActiveSupport::TestCase
include ActiveSupport::Multibyte::Unicode
diff --git a/activesupport/test/multibyte_utils_test.rb b/activesupport/test/multibyte_utils_test.rb
deleted file mode 100644
index 0a2f20d282..0000000000
--- a/activesupport/test/multibyte_utils_test.rb
+++ /dev/null
@@ -1,137 +0,0 @@
-# encoding: utf-8
-
-require 'abstract_unit'
-require 'multibyte_test_helpers'
-
-class MultibyteUtilsTest < ActiveSupport::TestCase
- include MultibyteTestHelpers
-
- test "valid_character returns an expression for the current encoding" do
- with_encoding('None') do
- assert_nil ActiveSupport::Multibyte.valid_character
- end
- with_encoding('UTF8') do
- assert_equal ActiveSupport::Multibyte::VALID_CHARACTER['UTF-8'], ActiveSupport::Multibyte.valid_character
- end
- with_encoding('SJIS') do
- assert_equal ActiveSupport::Multibyte::VALID_CHARACTER['Shift_JIS'], ActiveSupport::Multibyte.valid_character
- end
- end
-
- test "verify verifies ASCII strings are properly encoded" do
- with_encoding('None') do
- examples.each do |example|
- assert ActiveSupport::Multibyte.verify(example)
- end
- end
- end
-
- test "verify verifies UTF-8 strings are properly encoded" do
- with_encoding('UTF8') do
- assert ActiveSupport::Multibyte.verify(example('valid UTF-8'))
- assert !ActiveSupport::Multibyte.verify(example('invalid UTF-8'))
- end
- end
-
- test "verify verifies Shift-JIS strings are properly encoded" do
- with_encoding('SJIS') do
- assert ActiveSupport::Multibyte.verify(example('valid Shift-JIS'))
- assert !ActiveSupport::Multibyte.verify(example('invalid Shift-JIS'))
- end
- end
-
- test "verify! raises an exception when it finds an invalid character" do
- with_encoding('UTF8') do
- assert_raises(ActiveSupport::Multibyte::EncodingError) do
- ActiveSupport::Multibyte.verify!(example('invalid UTF-8'))
- end
- end
- end
-
- test "verify! doesn't raise an exception when the encoding is valid" do
- with_encoding('UTF8') do
- assert_nothing_raised do
- ActiveSupport::Multibyte.verify!(example('valid UTF-8'))
- end
- end
- end
-
- if RUBY_VERSION < '1.9'
- test "clean leaves ASCII strings intact" do
- with_encoding('None') do
- [
- 'word', "\270\236\010\210\245"
- ].each do |string|
- assert_equal string, ActiveSupport::Multibyte.clean(string)
- end
- end
- end
-
- test "clean cleans invalid characters from UTF-8 encoded strings" do
- with_encoding('UTF8') do
- cleaned_utf8 = [8].pack('C*')
- assert_equal example('valid UTF-8'), ActiveSupport::Multibyte.clean(example('valid UTF-8'))
- assert_equal cleaned_utf8, ActiveSupport::Multibyte.clean(example('invalid UTF-8'))
- end
- end
-
- test "clean cleans invalid characters from Shift-JIS encoded strings" do
- with_encoding('SJIS') do
- cleaned_sjis = [184, 0, 136, 165].pack('C*')
- assert_equal example('valid Shift-JIS'), ActiveSupport::Multibyte.clean(example('valid Shift-JIS'))
- assert_equal cleaned_sjis, ActiveSupport::Multibyte.clean(example('invalid Shift-JIS'))
- end
- end
- else
- test "clean is a no-op" do
- with_encoding('UTF8') do
- assert_equal example('invalid Shift-JIS'), ActiveSupport::Multibyte.clean(example('invalid Shift-JIS'))
- end
- end
- end
-
- private
-
- STRINGS = {
- 'valid ASCII' => [65, 83, 67, 73, 73].pack('C*'),
- 'invalid ASCII' => [128].pack('C*'),
- 'valid UTF-8' => [227, 129, 147, 227, 129, 171, 227, 129, 161, 227, 130, 143].pack('C*'),
- 'invalid UTF-8' => [184, 158, 8, 136, 165].pack('C*'),
- 'valid Shift-JIS' => [131, 122, 129, 91, 131, 128].pack('C*'),
- 'invalid Shift-JIS' => [184, 158, 8, 0, 255, 136, 165].pack('C*')
- }
-
- if Kernel.const_defined?(:Encoding)
- def example(key)
- STRINGS[key].force_encoding(Encoding.default_external)
- end
-
- def examples
- STRINGS.values.map { |s| s.force_encoding(Encoding.default_external) }
- end
- else
- def example(key)
- STRINGS[key]
- end
-
- def examples
- STRINGS.values
- end
- end
-
- if 'string'.respond_to?(:encoding)
- KCODE_TO_ENCODING = Hash.new(Encoding::BINARY).
- update('UTF8' => Encoding::UTF_8, 'SJIS' => Encoding::Shift_JIS)
-
- def with_encoding(enc)
- before = Encoding.default_external
- silence_warnings { Encoding.default_external = KCODE_TO_ENCODING[enc] }
-
- yield
-
- silence_warnings { Encoding.default_external = before }
- end
- else
- alias with_encoding with_kcode
- end
-end
diff --git a/activesupport/test/notifications/evented_notification_test.rb b/activesupport/test/notifications/evented_notification_test.rb
new file mode 100644
index 0000000000..f77a0eb3fa
--- /dev/null
+++ b/activesupport/test/notifications/evented_notification_test.rb
@@ -0,0 +1,67 @@
+require 'abstract_unit'
+
+module ActiveSupport
+ module Notifications
+ class EventedTest < ActiveSupport::TestCase
+ class Listener
+ attr_reader :events
+
+ def initialize
+ @events = []
+ end
+
+ def start(name, id, payload)
+ @events << [:start, name, id, payload]
+ end
+
+ def finish(name, id, payload)
+ @events << [:finish, name, id, payload]
+ end
+ end
+
+ def test_evented_listener
+ notifier = Fanout.new
+ listener = Listener.new
+ notifier.subscribe 'hi', listener
+ notifier.start 'hi', 1, {}
+ notifier.start 'hi', 2, {}
+ notifier.finish 'hi', 2, {}
+ notifier.finish 'hi', 1, {}
+
+ assert_equal 4, listener.events.length
+ assert_equal [
+ [:start, 'hi', 1, {}],
+ [:start, 'hi', 2, {}],
+ [:finish, 'hi', 2, {}],
+ [:finish, 'hi', 1, {}],
+ ], listener.events
+ end
+
+ def test_evented_listener_no_events
+ notifier = Fanout.new
+ listener = Listener.new
+ notifier.subscribe 'hi', listener
+ notifier.start 'world', 1, {}
+ assert_equal 0, listener.events.length
+ end
+
+ def test_listen_to_everything
+ notifier = Fanout.new
+ listener = Listener.new
+ notifier.subscribe nil, listener
+ notifier.start 'hello', 1, {}
+ notifier.start 'world', 1, {}
+ notifier.finish 'world', 1, {}
+ notifier.finish 'hello', 1, {}
+
+ assert_equal 4, listener.events.length
+ assert_equal [
+ [:start, 'hello', 1, {}],
+ [:start, 'world', 1, {}],
+ [:finish, 'world', 1, {}],
+ [:finish, 'hello', 1, {}],
+ ], listener.events
+ end
+ end
+ end
+end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index 884ee61547..fc9fa90d07 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -24,6 +24,26 @@ module Notifications
end
end
+ class SubscribedTest < TestCase
+ def test_subscribed
+ name = "foo"
+ name2 = name * 2
+ expected = [name, name]
+
+ events = []
+ callback = lambda {|*_| events << _.first}
+ ActiveSupport::Notifications.subscribed(callback, name) do
+ ActiveSupport::Notifications.instrument(name)
+ ActiveSupport::Notifications.instrument(name2)
+ ActiveSupport::Notifications.instrument(name)
+ end
+ assert_equal expected, events
+
+ ActiveSupport::Notifications.instrument(name)
+ assert_equal expected, events
+ end
+ end
+
class UnsubscribeTest < TestCase
def test_unsubscribing_removes_a_subscription
@notifier.publish :foo
diff --git a/activesupport/test/option_merger_test.rb b/activesupport/test/option_merger_test.rb
index 2bdd3034e5..9d139b61b8 100644
--- a/activesupport/test/option_merger_test.rb
+++ b/activesupport/test/option_merger_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/core_ext/object/with_options'
-class OptionMergerTest < Test::Unit::TestCase
+class OptionMergerTest < ActiveSupport::TestCase
def setup
@options = {:hello => 'world'}
end
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index 0b5f912dc4..e8defd396b 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -3,7 +3,7 @@ require 'active_support/json'
require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/hash/indifferent_access'
-class OrderedHashTest < Test::Unit::TestCase
+class OrderedHashTest < ActiveSupport::TestCase
def setup
@keys = %w( blue green red pink orange )
@values = %w( 000099 009900 aa0000 cc0066 cc6633 )
@@ -81,24 +81,21 @@ class OrderedHashTest < Test::Unit::TestCase
keys = []
assert_equal @ordered_hash, @ordered_hash.each_key { |k| keys << k }
assert_equal @keys, keys
- expected_class = RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator
- assert_kind_of expected_class, @ordered_hash.each_key
+ assert_kind_of Enumerator, @ordered_hash.each_key
end
def test_each_value
values = []
assert_equal @ordered_hash, @ordered_hash.each_value { |v| values << v }
assert_equal @values, values
- expected_class = RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator
- assert_kind_of expected_class, @ordered_hash.each_value
+ assert_kind_of Enumerator, @ordered_hash.each_value
end
def test_each
values = []
assert_equal @ordered_hash, @ordered_hash.each {|key, value| values << value}
assert_equal @values, values
- expected_class = RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator
- assert_kind_of expected_class, @ordered_hash.each
+ assert_kind_of Enumerator, @ordered_hash.each
end
def test_each_with_index
@@ -114,9 +111,7 @@ class OrderedHashTest < Test::Unit::TestCase
end
assert_equal @values, values
assert_equal @keys, keys
-
- expected_class = RUBY_VERSION < '1.9' ? Enumerable::Enumerator : Enumerator
- assert_kind_of expected_class, @ordered_hash.each_pair
+ assert_kind_of Enumerator, @ordered_hash.each_pair
end
def test_find_all
@@ -296,21 +291,16 @@ class OrderedHashTest < Test::Unit::TestCase
assert_equal @ordered_hash.values, @deserialized_ordered_hash.values
end
- begin
- require 'psych'
-
- def test_psych_serialize
- @deserialized_ordered_hash = Psych.load(Psych.dump(@ordered_hash))
+ def test_psych_serialize
+ @deserialized_ordered_hash = Psych.load(Psych.dump(@ordered_hash))
- values = @deserialized_ordered_hash.map { |_, value| value }
- assert_equal @values, values
- end
+ values = @deserialized_ordered_hash.map { |_, value| value }
+ assert_equal @values, values
+ end
- def test_psych_serialize_tag
- yaml = Psych.dump(@ordered_hash)
- assert_match '!omap', yaml
- end
- rescue LoadError
+ def test_psych_serialize_tag
+ yaml = Psych.dump(@ordered_hash)
+ assert_match '!omap', yaml
end
def test_has_yaml_tag
diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index b215b60df3..f60f9a58e3 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
+require 'active_support/ordered_options'
-class OrderedOptionsTest < Test::Unit::TestCase
+class OrderedOptionsTest < ActiveSupport::TestCase
def test_usage
a = ActiveSupport::OrderedOptions.new
@@ -76,4 +77,12 @@ class OrderedOptionsTest < Test::Unit::TestCase
assert copy.kind_of?(original.class)
assert_not_equal copy.object_id, original.object_id
end
+
+ def test_introspection
+ a = ActiveSupport::OrderedOptions.new
+ assert a.respond_to?(:blah)
+ assert a.respond_to?(:blah=)
+ assert_equal 42, a.method(:blah=).call(42)
+ assert_equal 42, a.method(:blah).call
+ end
end
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index c28ffa50f2..3f8d09c18e 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -70,7 +70,7 @@ class CoolStargate < Stargate
end
-class RescuableTest < Test::Unit::TestCase
+class RescuableTest < ActiveSupport::TestCase
def setup
@stargate = Stargate.new
@cool_stargate = CoolStargate.new
diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb
index 8f77999d25..047b89be2a 100644
--- a/activesupport/test/safe_buffer_test.rb
+++ b/activesupport/test/safe_buffer_test.rb
@@ -1,9 +1,4 @@
require 'abstract_unit'
-begin
- require 'psych'
-rescue LoadError
-end
-
require 'active_support/core_ext/string/inflections'
require 'yaml'
@@ -12,6 +7,10 @@ class SafeBufferTest < ActiveSupport::TestCase
@buffer = ActiveSupport::SafeBuffer.new
end
+ def test_titleize
+ assert_equal 'Foo', "foo".html_safe.titleize
+ end
+
test "Should look like a string" do
assert @buffer.is_a?(String)
assert_equal "", @buffer
@@ -85,31 +84,60 @@ class SafeBufferTest < ActiveSupport::TestCase
assert_equal "hello&lt;&gt;", clean + @buffer
end
- test "Should concat as a normal string when dirty" do
+ test "Should concat as a normal string when safe" do
clean = "hello".html_safe
@buffer.gsub!('', '<>')
assert_equal "<>hello", @buffer + clean
end
- test "Should preserve dirty? status on copy" do
+ test "Should preserve html_safe? status on copy" do
@buffer.gsub!('', '<>')
assert !@buffer.dup.html_safe?
end
- test "Should raise an error when safe_concat is called on dirty buffers" do
+ test "Should return safe buffer when added with another safe buffer" do
+ clean = "<script>".html_safe
+ result_buffer = @buffer + clean
+ assert result_buffer.html_safe?
+ assert_equal "<script>", result_buffer
+ end
+
+ test "Should raise an error when safe_concat is called on unsafe buffers" do
@buffer.gsub!('', '<>')
assert_raise ActiveSupport::SafeBuffer::SafeConcatError do
@buffer.safe_concat "BUSTED"
end
end
-
- test "should not fail if the returned object is not a string" do
+
+ test "Should not fail if the returned object is not a string" do
assert_kind_of NilClass, @buffer.slice("chipchop")
end
- test "Should initialize @dirty to false for new instance when sliced" do
- dirty = @buffer[0,0].send(:dirty?)
- assert_not_nil dirty
- assert !dirty
+ test "clone_empty returns an empty buffer" do
+ assert_equal '', ActiveSupport::SafeBuffer.new('foo').clone_empty
+ end
+
+ test "clone_empty keeps the original dirtyness" do
+ assert @buffer.clone_empty.html_safe?
+ assert !@buffer.gsub!('', '').clone_empty.html_safe?
+ end
+
+ test "Should be safe when sliced if original value was safe" do
+ new_buffer = @buffer[0,0]
+ assert_not_nil new_buffer
+ assert new_buffer.html_safe?, "should be safe"
+ end
+
+ test "Should continue unsafe on slice" do
+ x = 'foo'.html_safe.gsub!('f', '<script>alert("lolpwnd");</script>')
+
+ # calling gsub! makes the dirty flag true
+ assert !x.html_safe?, "should not be safe"
+
+ # getting a slice of it
+ y = x[0..-1]
+
+ # should still be unsafe
+ assert !y.html_safe?, "should not be safe"
end
end
diff --git a/activesupport/test/string_inquirer_test.rb b/activesupport/test/string_inquirer_test.rb
index 7f11f667df..bb15916e9e 100644
--- a/activesupport/test/string_inquirer_test.rb
+++ b/activesupport/test/string_inquirer_test.rb
@@ -1,6 +1,6 @@
require 'abstract_unit'
-class StringInquirerTest < Test::Unit::TestCase
+class StringInquirerTest < ActiveSupport::TestCase
def test_match
assert ActiveSupport::StringInquirer.new("production").production?
end
diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb
new file mode 100644
index 0000000000..0751c2469e
--- /dev/null
+++ b/activesupport/test/tagged_logging_test.rb
@@ -0,0 +1,72 @@
+require 'abstract_unit'
+require 'active_support/logger'
+require 'active_support/tagged_logging'
+
+class TaggedLoggingTest < ActiveSupport::TestCase
+ class MyLogger < ::ActiveSupport::Logger
+ def flush(*)
+ info "[FLUSHED]"
+ end
+ end
+
+ setup do
+ @output = StringIO.new
+ @logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@output))
+ end
+
+ test "tagged once" do
+ @logger.tagged("BCX") { @logger.info "Funky time" }
+ assert_equal "[BCX] Funky time\n", @output.string
+ end
+
+ test "tagged twice" do
+ @logger.tagged("BCX") { @logger.tagged("Jason") { @logger.info "Funky time" } }
+ assert_equal "[BCX] [Jason] Funky time\n", @output.string
+ end
+
+ test "tagged thrice at once" do
+ @logger.tagged("BCX", "Jason", "New") { @logger.info "Funky time" }
+ assert_equal "[BCX] [Jason] [New] Funky time\n", @output.string
+ end
+
+ test "provides access to the logger instance" do
+ @logger.tagged("BCX") { |logger| logger.info "Funky time" }
+ assert_equal "[BCX] Funky time\n", @output.string
+ end
+
+ test "tagged once with blank and nil" do
+ @logger.tagged(nil, "", "New") { @logger.info "Funky time" }
+ assert_equal "[New] Funky time\n", @output.string
+ end
+
+ test "keeps each tag in their own thread" do
+ @logger.tagged("BCX") do
+ Thread.new do
+ @logger.tagged("OMG") { @logger.info "Cool story bro" }
+ end.join
+ @logger.info "Funky time"
+ end
+ assert_equal "[OMG] Cool story bro\n[BCX] Funky time\n", @output.string
+ end
+
+ test "cleans up the taggings on flush" do
+ @logger.tagged("BCX") do
+ Thread.new do
+ @logger.tagged("OMG") do
+ @logger.flush
+ @logger.info "Cool story bro"
+ end
+ end.join
+ end
+ assert_equal "[FLUSHED]\nCool story bro\n", @output.string
+ end
+
+ test "mixed levels of tagging" do
+ @logger.tagged("BCX") do
+ @logger.tagged("Jason") { @logger.info "Funky time" }
+ @logger.info "Junky time!"
+ end
+
+ assert_equal "[BCX] [Jason] Funky time\n[BCX] Junky time!\n", @output.string
+ end
+end
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 756d21b3e4..e5b5547478 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -18,44 +18,50 @@ module ActiveSupport
end
end
- if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
- def test_callback_with_exception
- tc = Class.new(TestCase) do
- setup :bad_callback
- def bad_callback; raise 'oh noes' end
- def test_true; assert true end
+ def test_callback_with_exception
+ tc = Class.new(TestCase) do
+ def self.name
+ nil
end
- test_name = 'test_true'
- fr = FakeRunner.new
+ setup :bad_callback
+ def bad_callback; raise 'oh noes' end
+ def test_true; assert true end
+ end
- test = tc.new test_name
- test.run fr
- klass, name, exception = *fr.puked.first
+ test_name = 'test_true'
+ fr = FakeRunner.new
- assert_equal tc, klass
- assert_equal test_name, name
- assert_equal 'oh noes', exception.message
- end
+ test = tc.new test_name
+ test.run fr
+ klass, name, exception = *fr.puked.first
+
+ assert_equal tc, klass
+ assert_equal test_name, name
+ assert_equal 'oh noes', exception.message
+ end
- def test_teardown_callback_with_exception
- tc = Class.new(TestCase) do
- teardown :bad_callback
- def bad_callback; raise 'oh noes' end
- def test_true; assert true end
+ def test_teardown_callback_with_exception
+ tc = Class.new(TestCase) do
+ def self.name
+ nil
end
- test_name = 'test_true'
- fr = FakeRunner.new
+ teardown :bad_callback
+ def bad_callback; raise 'oh noes' end
+ def test_true; assert true end
+ end
- test = tc.new test_name
- test.run fr
- klass, name, exception = *fr.puked.first
+ test_name = 'test_true'
+ fr = FakeRunner.new
- assert_equal tc, klass
- assert_equal test_name, name
- assert_equal 'oh noes', exception.message
- end
+ test = tc.new test_name
+ test.run fr
+ klass, name, exception = *fr.puked.first
+
+ assert_equal tc, klass
+ assert_equal test_name, name
+ assert_equal 'oh noes', exception.message
end
end
end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index f880052786..11506554a9 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -61,24 +61,19 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
def test_array_of_expressions_identify_failure
- assert_difference ['@object.num', '1 + 1'] do
- @object.increment
+ assert_raises(MiniTest::Assertion) do
+ assert_difference ['@object.num', '1 + 1'] do
+ @object.increment
+ end
end
- fail 'should not get to here'
- rescue Exception => e
- assert_match(/didn't change by/, e.message)
- assert_match(/expected but was/, e.message)
end
def test_array_of_expressions_identify_failure_when_message_provided
- assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do
- @object.increment
+ assert_raises(MiniTest::Assertion) do
+ assert_difference ['@object.num', '1 + 1'], 1, 'something went wrong' do
+ @object.increment
+ end
end
- fail 'should not get to here'
- rescue Exception => e
- assert_match(/something went wrong/, e.message)
- assert_match(/didn't change by/, e.message)
- assert_match(/expected but was/, e.message)
end
else
def default_test; end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 3575175517..b9434489bb 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
-class TimeZoneTest < Test::Unit::TestCase
+class TimeZoneTest < ActiveSupport::TestCase
def test_utc_to_local
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
assert_equal Time.utc(1999, 12, 31, 19), zone.utc_to_local(Time.utc(2000, 1)) # standard offset -0500
@@ -48,8 +48,8 @@ class TimeZoneTest < Test::Unit::TestCase
def test_now
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns(Time.local(2000))
- zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup
+ def zone.time_now; Time.local(2000); end
assert_instance_of ActiveSupport::TimeWithZone, zone.now
assert_equal Time.utc(2000,1,1,5), zone.now.utc
assert_equal Time.utc(2000), zone.now.time
@@ -59,8 +59,11 @@ class TimeZoneTest < Test::Unit::TestCase
def test_now_enforces_spring_dst_rules
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns(Time.local(2006,4,2,2)) # 2AM springs forward to 3AM
- zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup
+ def zone.time_now
+ Time.local(2006,4,2,2) # 2AM springs forward to 3AM
+ end
+
assert_equal Time.utc(2006,4,2,3), zone.now.time
assert_equal true, zone.now.dst?
end
@@ -68,8 +71,10 @@ class TimeZoneTest < Test::Unit::TestCase
def test_now_enforces_fall_dst_rules
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns(Time.at(1162098000)) # equivalent to 1AM DST
- zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'].dup
+ def zone.time_now
+ Time.at(1162098000) # equivalent to 1AM DST
+ end
assert_equal Time.utc(2006,10,29,1), zone.now.time
assert_equal true, zone.now.dst?
end
@@ -198,6 +203,24 @@ class TimeZoneTest < Test::Unit::TestCase
assert_equal Time.utc(1999,12,31,19), twz.time
end
+ def test_parse_should_not_black_out_system_timezone_dst_jump
+ zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)']
+ zone.stubs(:now).returns(zone.now)
+ Time.stubs(:parse).with('2012-03-25 03:29', zone.now).
+ returns(Time.local(0,29,4,25,3,2012,nil,nil,true,"+03:00"))
+ twz = zone.parse('2012-03-25 03:29')
+ assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0,6]
+ end
+
+ def test_parse_should_black_out_app_timezone_dst_jump
+ zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)']
+ zone.stubs(:now).returns(zone.now)
+ Time.stubs(:parse).with('2012-03-11 02:29', zone.now).
+ returns(Time.local(0,29,2,11,3,2012,nil,nil,false,"+02:00"))
+ twz = zone.parse('2012-03-11 02:29')
+ assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0,6]
+ end
+
def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize
tzinfo = TZInfo::Timezone.get('America/New_York')
zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo)
diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb
index 08e11d4f38..b7076e9e58 100644
--- a/activesupport/test/transliterate_test.rb
+++ b/activesupport/test/transliterate_test.rb
@@ -3,7 +3,7 @@ require 'abstract_unit'
require 'active_support/inflector/transliterate'
require 'active_support/core_ext/object/inclusion'
-class TransliterateTest < Test::Unit::TestCase
+class TransliterateTest < ActiveSupport::TestCase
def test_transliterate_should_not_change_ascii_chars
(0..127).each do |byte|
diff --git a/activesupport/test/ts_isolated.rb b/activesupport/test/ts_isolated.rb
index 58710e0165..938bb4ee99 100644
--- a/activesupport/test/ts_isolated.rb
+++ b/activesupport/test/ts_isolated.rb
@@ -1,10 +1,9 @@
-$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib')
-
-require 'test/unit'
+require 'minitest/autorun'
+require 'active_support/test_case'
require 'rbconfig'
require 'active_support/core_ext/kernel/reporting'
-class TestIsolated < Test::Unit::TestCase
+class TestIsolated < ActiveSupport::TestCase
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
Dir["#{File.dirname(__FILE__)}/**/*_test.rb"].each do |file|
diff --git a/activesupport/test/whiny_nil_test.rb b/activesupport/test/whiny_nil_test.rb
deleted file mode 100644
index 1acaf7228f..0000000000
--- a/activesupport/test/whiny_nil_test.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# Stub to enable testing without Active Record
-module ActiveRecord
- class Base
- def save!
- end
- end
-end
-
-require 'abstract_unit'
-require 'active_support/whiny_nil'
-
-NilClass.add_whiner ::ActiveRecord::Base
-
-class WhinyNilTest < Test::Unit::TestCase
- def test_unchanged
- nil.method_thats_not_in_whiners
- rescue NoMethodError => nme
- assert_match(/nil:NilClass/, nme.message)
- end
-
- def test_active_record
- nil.save!
- rescue NoMethodError => nme
- assert_no_match(/nil:NilClass/, nme.message)
- assert_match(/nil\.save!/, nme.message)
- end
-
- def test_array
- nil.each
- rescue NoMethodError => nme
- assert_no_match(/nil:NilClass/, nme.message)
- assert_match(/nil\.each/, nme.message)
- end
-
- def test_id
- nil.id
- rescue RuntimeError => nme
- assert_no_match(/nil:NilClass/, nme.message)
- assert_match(Regexp.new(nil.object_id.to_s), nme.message)
- end
-
- def test_no_to_ary_coercion
- nil.to_ary
- rescue NoMethodError => nme
- assert_no_match(/nil:NilClass/, nme.message)
- assert_match(/nil\.to_ary/, nme.message)
- end
-
- def test_no_to_str_coercion
- nil.to_str
- rescue NoMethodError => nme
- assert_match(/nil:NilClass/, nme.message)
- end
-end
diff --git a/activesupport/test/xml_mini/jdom_engine_test.rb b/activesupport/test/xml_mini/jdom_engine_test.rb
index 7f809e7898..f77d78d42c 100644
--- a/activesupport/test/xml_mini/jdom_engine_test.rb
+++ b/activesupport/test/xml_mini/jdom_engine_test.rb
@@ -3,7 +3,7 @@ if RUBY_PLATFORM =~ /java/
require 'active_support/xml_mini'
require 'active_support/core_ext/hash/conversions'
- class JDOMEngineTest < Test::Unit::TestCase
+ class JDOMEngineTest < ActiveSupport::TestCase
include ActiveSupport
def setup
diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb
index 83d03bccc6..5debb2fd59 100644
--- a/activesupport/test/xml_mini/libxml_engine_test.rb
+++ b/activesupport/test/xml_mini/libxml_engine_test.rb
@@ -8,7 +8,7 @@ rescue LoadError
# Skip libxml tests
else
-class LibxmlEngineTest < Test::Unit::TestCase
+class LibxmlEngineTest < ActiveSupport::TestCase
include ActiveSupport
def setup
diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
index 864810099e..94250d48ec 100644
--- a/activesupport/test/xml_mini/libxmlsax_engine_test.rb
+++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb
@@ -8,7 +8,7 @@ rescue LoadError
# Skip libxml tests
else
-class LibXMLSAXEngineTest < Test::Unit::TestCase
+class LibXMLSAXEngineTest < ActiveSupport::TestCase
include ActiveSupport
def setup
diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb
index db0d7c5b02..3f37c7cbb6 100644
--- a/activesupport/test/xml_mini/nokogiri_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb
@@ -8,7 +8,7 @@ rescue LoadError
# Skip nokogiri tests
else
-class NokogiriEngineTest < Test::Unit::TestCase
+class NokogiriEngineTest < ActiveSupport::TestCase
include ActiveSupport
def setup
diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
index 1149d0fecc..d6ae7f12ae 100644
--- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb
+++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb
@@ -8,7 +8,7 @@ rescue LoadError
# Skip nokogiri tests
else
-class NokogiriSAXEngineTest < Test::Unit::TestCase
+class NokogiriSAXEngineTest < ActiveSupport::TestCase
include ActiveSupport
def setup
diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb
index 57bb35254a..c4770405f2 100644
--- a/activesupport/test/xml_mini/rexml_engine_test.rb
+++ b/activesupport/test/xml_mini/rexml_engine_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
require 'active_support/xml_mini'
-class REXMLEngineTest < Test::Unit::TestCase
+class REXMLEngineTest < ActiveSupport::TestCase
include ActiveSupport
def test_default_is_rexml
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index dde17ea403..504fc96493 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -3,7 +3,7 @@ require 'active_support/xml_mini'
require 'active_support/builder'
module XmlMiniTest
- class RenameKeyTest < Test::Unit::TestCase
+ class RenameKeyTest < ActiveSupport::TestCase
def test_rename_key_dasherizes_by_default
assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key")
end
diff --git a/ci/travis.rb b/ci/travis.rb
index 8087c72f90..fc120f80ba 100755
--- a/ci/travis.rb
+++ b/ci/travis.rb
@@ -19,7 +19,6 @@ class Build
'ap' => 'actionpack',
'am' => 'actionmailer',
'amo' => 'activemodel',
- 'ares' => 'activeresource',
'as' => 'activesupport',
'ar' => 'activerecord'
}
@@ -35,7 +34,6 @@ class Build
self.options.update(options)
Dir.chdir(dir) do
announce(heading)
- ENV['IM'] = identity_map?.inspect
rake(*tasks)
end
end
@@ -46,7 +44,7 @@ class Build
def heading
heading = [gem]
- heading << "with #{adapter} IM #{identity_map? ? 'enabled' : 'disabled'}" if activerecord?
+ heading << "with #{adapter}" if activerecord?
heading << "in isolation" if isolated?
heading.join(' ')
end
@@ -62,7 +60,6 @@ class Build
def key
key = [gem]
key << adapter if activerecord?
- key << 'IM' if identity_map?
key << 'isolated' if isolated?
key.join(':')
end
@@ -71,10 +68,6 @@ class Build
gem == 'activerecord'
end
- def identity_map?
- options[:identity_map]
- end
-
def isolated?
options[:isolated]
end
@@ -108,7 +101,6 @@ ENV['GEM'].split(',').each do |gem|
results[build.key] = build.run!
if build.activerecord?
- build.options[:identity_map] = true
results[build.key] = build.run!
end
end
@@ -132,7 +124,7 @@ failures = results.select { |key, value| value == false }
if failures.empty?
puts
- puts "Rails build finished sucessfully"
+ puts "Rails build finished successfully"
exit(true)
else
puts
diff --git a/guides/Rakefile b/guides/Rakefile
new file mode 100644
index 0000000000..ad4ff91fe6
--- /dev/null
+++ b/guides/Rakefile
@@ -0,0 +1,11 @@
+desc 'Generate guides (for authors), use ONLY=foo to process just "foo.textile"'
+task :generate_guides do
+ ENV["WARN_BROKEN_LINKS"] = "1" # authors can't disable this
+ ruby "rails_guides.rb"
+end
+
+# Validate guides -------------------------------------------------------------------------
+desc 'Validate guides, use ONLY=foo to process just "foo.html"'
+task :validate_guides do
+ ruby "w3c_validator.rb"
+end
diff --git a/railties/guides/assets/images/belongs_to.png b/guides/assets/images/belongs_to.png
index 44243edbca..44243edbca 100644
--- a/railties/guides/assets/images/belongs_to.png
+++ b/guides/assets/images/belongs_to.png
Binary files differ
diff --git a/railties/guides/assets/images/book_icon.gif b/guides/assets/images/book_icon.gif
index c81d5db520..c81d5db520 100644
--- a/railties/guides/assets/images/book_icon.gif
+++ b/guides/assets/images/book_icon.gif
Binary files differ
diff --git a/railties/guides/assets/images/bullet.gif b/guides/assets/images/bullet.gif
index 95a26364a4..95a26364a4 100644
--- a/railties/guides/assets/images/bullet.gif
+++ b/guides/assets/images/bullet.gif
Binary files differ
diff --git a/railties/guides/assets/images/challenge.png b/guides/assets/images/challenge.png
index d163748640..d163748640 100644
--- a/railties/guides/assets/images/challenge.png
+++ b/guides/assets/images/challenge.png
Binary files differ
diff --git a/railties/guides/assets/images/chapters_icon.gif b/guides/assets/images/chapters_icon.gif
index 06fb415f4a..06fb415f4a 100644
--- a/railties/guides/assets/images/chapters_icon.gif
+++ b/guides/assets/images/chapters_icon.gif
Binary files differ
diff --git a/railties/guides/assets/images/check_bullet.gif b/guides/assets/images/check_bullet.gif
index 1fcfeba250..1fcfeba250 100644
--- a/railties/guides/assets/images/check_bullet.gif
+++ b/guides/assets/images/check_bullet.gif
Binary files differ
diff --git a/railties/guides/assets/images/credits_pic_blank.gif b/guides/assets/images/credits_pic_blank.gif
index f6f654fc65..f6f654fc65 100644
--- a/railties/guides/assets/images/credits_pic_blank.gif
+++ b/guides/assets/images/credits_pic_blank.gif
Binary files differ
diff --git a/railties/guides/assets/images/csrf.png b/guides/assets/images/csrf.png
index ab73baafe8..ab73baafe8 100644
--- a/railties/guides/assets/images/csrf.png
+++ b/guides/assets/images/csrf.png
Binary files differ
diff --git a/railties/guides/assets/images/customized_error_messages.png b/guides/assets/images/customized_error_messages.png
index fa676991e3..fa676991e3 100644
--- a/railties/guides/assets/images/customized_error_messages.png
+++ b/guides/assets/images/customized_error_messages.png
Binary files differ
diff --git a/railties/guides/assets/images/edge_badge.png b/guides/assets/images/edge_badge.png
index cddd46c4b8..cddd46c4b8 100644
--- a/railties/guides/assets/images/edge_badge.png
+++ b/guides/assets/images/edge_badge.png
Binary files differ
diff --git a/railties/guides/assets/images/error_messages.png b/guides/assets/images/error_messages.png
index 428892194a..428892194a 100644
--- a/railties/guides/assets/images/error_messages.png
+++ b/guides/assets/images/error_messages.png
Binary files differ
diff --git a/guides/assets/images/favicon.ico b/guides/assets/images/favicon.ico
new file mode 100644
index 0000000000..e0e80cf8f1
--- /dev/null
+++ b/guides/assets/images/favicon.ico
Binary files differ
diff --git a/railties/guides/assets/images/feature_tile.gif b/guides/assets/images/feature_tile.gif
index 75469361db..75469361db 100644
--- a/railties/guides/assets/images/feature_tile.gif
+++ b/guides/assets/images/feature_tile.gif
Binary files differ
diff --git a/railties/guides/assets/images/footer_tile.gif b/guides/assets/images/footer_tile.gif
index bb33fc1ff0..bb33fc1ff0 100644
--- a/railties/guides/assets/images/footer_tile.gif
+++ b/guides/assets/images/footer_tile.gif
Binary files differ
diff --git a/railties/guides/assets/images/fxn.png b/guides/assets/images/fxn.png
index 9b531ee584..9b531ee584 100644
--- a/railties/guides/assets/images/fxn.png
+++ b/guides/assets/images/fxn.png
Binary files differ
diff --git a/guides/assets/images/getting_started/confirm_dialog.png b/guides/assets/images/getting_started/confirm_dialog.png
new file mode 100644
index 0000000000..a26c09ef2d
--- /dev/null
+++ b/guides/assets/images/getting_started/confirm_dialog.png
Binary files differ
diff --git a/guides/assets/images/getting_started/form_with_errors.png b/guides/assets/images/getting_started/form_with_errors.png
new file mode 100644
index 0000000000..badefe6ea6
--- /dev/null
+++ b/guides/assets/images/getting_started/form_with_errors.png
Binary files differ
diff --git a/guides/assets/images/getting_started/index_action_with_edit_link.png b/guides/assets/images/getting_started/index_action_with_edit_link.png
new file mode 100644
index 0000000000..6e58a13756
--- /dev/null
+++ b/guides/assets/images/getting_started/index_action_with_edit_link.png
Binary files differ
diff --git a/guides/assets/images/getting_started/new_post.png b/guides/assets/images/getting_started/new_post.png
new file mode 100644
index 0000000000..dc9459032a
--- /dev/null
+++ b/guides/assets/images/getting_started/new_post.png
Binary files differ
diff --git a/guides/assets/images/getting_started/post_with_comments.png b/guides/assets/images/getting_started/post_with_comments.png
new file mode 100644
index 0000000000..bd9b2e10f5
--- /dev/null
+++ b/guides/assets/images/getting_started/post_with_comments.png
Binary files differ
diff --git a/guides/assets/images/getting_started/routing_error_no_controller.png b/guides/assets/images/getting_started/routing_error_no_controller.png
new file mode 100644
index 0000000000..92a39efd78
--- /dev/null
+++ b/guides/assets/images/getting_started/routing_error_no_controller.png
Binary files differ
diff --git a/guides/assets/images/getting_started/routing_error_no_route_matches.png b/guides/assets/images/getting_started/routing_error_no_route_matches.png
new file mode 100644
index 0000000000..bc768a94a2
--- /dev/null
+++ b/guides/assets/images/getting_started/routing_error_no_route_matches.png
Binary files differ
diff --git a/guides/assets/images/getting_started/show_action_for_posts.png b/guides/assets/images/getting_started/show_action_for_posts.png
new file mode 100644
index 0000000000..5c8c4d8e5e
--- /dev/null
+++ b/guides/assets/images/getting_started/show_action_for_posts.png
Binary files differ
diff --git a/guides/assets/images/getting_started/template_is_missing_posts_new.png b/guides/assets/images/getting_started/template_is_missing_posts_new.png
new file mode 100644
index 0000000000..9f070d59db
--- /dev/null
+++ b/guides/assets/images/getting_started/template_is_missing_posts_new.png
Binary files differ
diff --git a/guides/assets/images/getting_started/undefined_method_post_path.png b/guides/assets/images/getting_started/undefined_method_post_path.png
new file mode 100644
index 0000000000..f568bf315c
--- /dev/null
+++ b/guides/assets/images/getting_started/undefined_method_post_path.png
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_create_for_posts.png b/guides/assets/images/getting_started/unknown_action_create_for_posts.png
new file mode 100644
index 0000000000..03d92dfb7d
--- /dev/null
+++ b/guides/assets/images/getting_started/unknown_action_create_for_posts.png
Binary files differ
diff --git a/guides/assets/images/getting_started/unknown_action_new_for_posts.png b/guides/assets/images/getting_started/unknown_action_new_for_posts.png
new file mode 100644
index 0000000000..b63883d922
--- /dev/null
+++ b/guides/assets/images/getting_started/unknown_action_new_for_posts.png
Binary files differ
diff --git a/railties/guides/assets/images/grey_bullet.gif b/guides/assets/images/grey_bullet.gif
index e75e8e93a1..e75e8e93a1 100644
--- a/railties/guides/assets/images/grey_bullet.gif
+++ b/guides/assets/images/grey_bullet.gif
Binary files differ
diff --git a/railties/guides/assets/images/habtm.png b/guides/assets/images/habtm.png
index fea78b0b5c..fea78b0b5c 100644
--- a/railties/guides/assets/images/habtm.png
+++ b/guides/assets/images/habtm.png
Binary files differ
diff --git a/railties/guides/assets/images/has_many.png b/guides/assets/images/has_many.png
index 6cff58460d..6cff58460d 100644
--- a/railties/guides/assets/images/has_many.png
+++ b/guides/assets/images/has_many.png
Binary files differ
diff --git a/railties/guides/assets/images/has_many_through.png b/guides/assets/images/has_many_through.png
index 85d7599925..85d7599925 100644
--- a/railties/guides/assets/images/has_many_through.png
+++ b/guides/assets/images/has_many_through.png
Binary files differ
diff --git a/railties/guides/assets/images/has_one.png b/guides/assets/images/has_one.png
index a70ddaaa86..a70ddaaa86 100644
--- a/railties/guides/assets/images/has_one.png
+++ b/guides/assets/images/has_one.png
Binary files differ
diff --git a/railties/guides/assets/images/has_one_through.png b/guides/assets/images/has_one_through.png
index 89a7617a30..89a7617a30 100644
--- a/railties/guides/assets/images/has_one_through.png
+++ b/guides/assets/images/has_one_through.png
Binary files differ
diff --git a/railties/guides/assets/images/header_backdrop.png b/guides/assets/images/header_backdrop.png
index ff2982175e..ff2982175e 100644
--- a/railties/guides/assets/images/header_backdrop.png
+++ b/guides/assets/images/header_backdrop.png
Binary files differ
diff --git a/railties/guides/assets/images/header_tile.gif b/guides/assets/images/header_tile.gif
index e2c878d492..e2c878d492 100644
--- a/railties/guides/assets/images/header_tile.gif
+++ b/guides/assets/images/header_tile.gif
Binary files differ
diff --git a/railties/guides/assets/images/i18n/demo_html_safe.png b/guides/assets/images/i18n/demo_html_safe.png
index f881f60dac..f881f60dac 100644
--- a/railties/guides/assets/images/i18n/demo_html_safe.png
+++ b/guides/assets/images/i18n/demo_html_safe.png
Binary files differ
diff --git a/railties/guides/assets/images/i18n/demo_localized_pirate.png b/guides/assets/images/i18n/demo_localized_pirate.png
index 9134709573..9134709573 100644
--- a/railties/guides/assets/images/i18n/demo_localized_pirate.png
+++ b/guides/assets/images/i18n/demo_localized_pirate.png
Binary files differ
diff --git a/railties/guides/assets/images/i18n/demo_translated_en.png b/guides/assets/images/i18n/demo_translated_en.png
index ecdd878d38..ecdd878d38 100644
--- a/railties/guides/assets/images/i18n/demo_translated_en.png
+++ b/guides/assets/images/i18n/demo_translated_en.png
Binary files differ
diff --git a/railties/guides/assets/images/i18n/demo_translated_pirate.png b/guides/assets/images/i18n/demo_translated_pirate.png
index 41c580923a..41c580923a 100644
--- a/railties/guides/assets/images/i18n/demo_translated_pirate.png
+++ b/guides/assets/images/i18n/demo_translated_pirate.png
Binary files differ
diff --git a/railties/guides/assets/images/i18n/demo_translation_missing.png b/guides/assets/images/i18n/demo_translation_missing.png
index af9e2d0427..af9e2d0427 100644
--- a/railties/guides/assets/images/i18n/demo_translation_missing.png
+++ b/guides/assets/images/i18n/demo_translation_missing.png
Binary files differ
diff --git a/railties/guides/assets/images/i18n/demo_untranslated.png b/guides/assets/images/i18n/demo_untranslated.png
index 3603f43463..3603f43463 100644
--- a/railties/guides/assets/images/i18n/demo_untranslated.png
+++ b/guides/assets/images/i18n/demo_untranslated.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/README b/guides/assets/images/icons/README
index f12b2a730c..f12b2a730c 100644
--- a/railties/guides/assets/images/icons/README
+++ b/guides/assets/images/icons/README
diff --git a/railties/guides/assets/images/icons/callouts/1.png b/guides/assets/images/icons/callouts/1.png
index 7d473430b7..7d473430b7 100644
--- a/railties/guides/assets/images/icons/callouts/1.png
+++ b/guides/assets/images/icons/callouts/1.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/10.png b/guides/assets/images/icons/callouts/10.png
index 997bbc8246..997bbc8246 100644
--- a/railties/guides/assets/images/icons/callouts/10.png
+++ b/guides/assets/images/icons/callouts/10.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/11.png b/guides/assets/images/icons/callouts/11.png
index ce47dac3f5..ce47dac3f5 100644
--- a/railties/guides/assets/images/icons/callouts/11.png
+++ b/guides/assets/images/icons/callouts/11.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/12.png b/guides/assets/images/icons/callouts/12.png
index 31daf4e2f2..31daf4e2f2 100644
--- a/railties/guides/assets/images/icons/callouts/12.png
+++ b/guides/assets/images/icons/callouts/12.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/13.png b/guides/assets/images/icons/callouts/13.png
index 14021a89c2..14021a89c2 100644
--- a/railties/guides/assets/images/icons/callouts/13.png
+++ b/guides/assets/images/icons/callouts/13.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/14.png b/guides/assets/images/icons/callouts/14.png
index 64014b75fe..64014b75fe 100644
--- a/railties/guides/assets/images/icons/callouts/14.png
+++ b/guides/assets/images/icons/callouts/14.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/15.png b/guides/assets/images/icons/callouts/15.png
index 0d65765fcf..0d65765fcf 100644
--- a/railties/guides/assets/images/icons/callouts/15.png
+++ b/guides/assets/images/icons/callouts/15.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/2.png b/guides/assets/images/icons/callouts/2.png
index 5d09341b2f..5d09341b2f 100644
--- a/railties/guides/assets/images/icons/callouts/2.png
+++ b/guides/assets/images/icons/callouts/2.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/3.png b/guides/assets/images/icons/callouts/3.png
index ef7b700471..ef7b700471 100644
--- a/railties/guides/assets/images/icons/callouts/3.png
+++ b/guides/assets/images/icons/callouts/3.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/4.png b/guides/assets/images/icons/callouts/4.png
index adb8364eb5..adb8364eb5 100644
--- a/railties/guides/assets/images/icons/callouts/4.png
+++ b/guides/assets/images/icons/callouts/4.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/5.png b/guides/assets/images/icons/callouts/5.png
index 4d7eb46002..4d7eb46002 100644
--- a/railties/guides/assets/images/icons/callouts/5.png
+++ b/guides/assets/images/icons/callouts/5.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/6.png b/guides/assets/images/icons/callouts/6.png
index 0ba694af6c..0ba694af6c 100644
--- a/railties/guides/assets/images/icons/callouts/6.png
+++ b/guides/assets/images/icons/callouts/6.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/7.png b/guides/assets/images/icons/callouts/7.png
index 472e96f8ac..472e96f8ac 100644
--- a/railties/guides/assets/images/icons/callouts/7.png
+++ b/guides/assets/images/icons/callouts/7.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/8.png b/guides/assets/images/icons/callouts/8.png
index 5e60973c21..5e60973c21 100644
--- a/railties/guides/assets/images/icons/callouts/8.png
+++ b/guides/assets/images/icons/callouts/8.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/callouts/9.png b/guides/assets/images/icons/callouts/9.png
index a0676d26cc..a0676d26cc 100644
--- a/railties/guides/assets/images/icons/callouts/9.png
+++ b/guides/assets/images/icons/callouts/9.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/caution.png b/guides/assets/images/icons/caution.png
index cb9d5ea0df..cb9d5ea0df 100644
--- a/railties/guides/assets/images/icons/caution.png
+++ b/guides/assets/images/icons/caution.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/example.png b/guides/assets/images/icons/example.png
index bba1c0010d..bba1c0010d 100644
--- a/railties/guides/assets/images/icons/example.png
+++ b/guides/assets/images/icons/example.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/home.png b/guides/assets/images/icons/home.png
index 37a5231bac..37a5231bac 100644
--- a/railties/guides/assets/images/icons/home.png
+++ b/guides/assets/images/icons/home.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/important.png b/guides/assets/images/icons/important.png
index 1096c23295..1096c23295 100644
--- a/railties/guides/assets/images/icons/important.png
+++ b/guides/assets/images/icons/important.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/next.png b/guides/assets/images/icons/next.png
index 64e126bdda..64e126bdda 100644
--- a/railties/guides/assets/images/icons/next.png
+++ b/guides/assets/images/icons/next.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/note.png b/guides/assets/images/icons/note.png
index 841820f7c4..841820f7c4 100644
--- a/railties/guides/assets/images/icons/note.png
+++ b/guides/assets/images/icons/note.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/prev.png b/guides/assets/images/icons/prev.png
index 3e8f12fe24..3e8f12fe24 100644
--- a/railties/guides/assets/images/icons/prev.png
+++ b/guides/assets/images/icons/prev.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/tip.png b/guides/assets/images/icons/tip.png
index a3a029d898..a3a029d898 100644
--- a/railties/guides/assets/images/icons/tip.png
+++ b/guides/assets/images/icons/tip.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/up.png b/guides/assets/images/icons/up.png
index 2db1ce62fa..2db1ce62fa 100644
--- a/railties/guides/assets/images/icons/up.png
+++ b/guides/assets/images/icons/up.png
Binary files differ
diff --git a/railties/guides/assets/images/icons/warning.png b/guides/assets/images/icons/warning.png
index 0b0c419df2..0b0c419df2 100644
--- a/railties/guides/assets/images/icons/warning.png
+++ b/guides/assets/images/icons/warning.png
Binary files differ
diff --git a/railties/guides/assets/images/jaimeiniesta.jpg b/guides/assets/images/jaimeiniesta.jpg
index 445f048d92..445f048d92 100644
--- a/railties/guides/assets/images/jaimeiniesta.jpg
+++ b/guides/assets/images/jaimeiniesta.jpg
Binary files differ
diff --git a/railties/guides/assets/images/nav_arrow.gif b/guides/assets/images/nav_arrow.gif
index c4f57658d7..c4f57658d7 100644
--- a/railties/guides/assets/images/nav_arrow.gif
+++ b/guides/assets/images/nav_arrow.gif
Binary files differ
diff --git a/railties/guides/assets/images/polymorphic.png b/guides/assets/images/polymorphic.png
index ff2fd9f76d..ff2fd9f76d 100644
--- a/railties/guides/assets/images/polymorphic.png
+++ b/guides/assets/images/polymorphic.png
Binary files differ
diff --git a/railties/guides/assets/images/radar.png b/guides/assets/images/radar.png
index f61e08763f..f61e08763f 100644
--- a/railties/guides/assets/images/radar.png
+++ b/guides/assets/images/radar.png
Binary files differ
diff --git a/guides/assets/images/rails_guides_kindle_cover.jpg b/guides/assets/images/rails_guides_kindle_cover.jpg
new file mode 100644
index 0000000000..9eb16720a9
--- /dev/null
+++ b/guides/assets/images/rails_guides_kindle_cover.jpg
Binary files differ
diff --git a/railties/guides/assets/images/rails_guides_logo.gif b/guides/assets/images/rails_guides_logo.gif
index a24683a34e..a24683a34e 100644
--- a/railties/guides/assets/images/rails_guides_logo.gif
+++ b/guides/assets/images/rails_guides_logo.gif
Binary files differ
diff --git a/railties/guides/assets/images/rails_logo_remix.gif b/guides/assets/images/rails_logo_remix.gif
index 58960ee4f9..58960ee4f9 100644
--- a/railties/guides/assets/images/rails_logo_remix.gif
+++ b/guides/assets/images/rails_logo_remix.gif
Binary files differ
diff --git a/railties/guides/assets/images/rails_welcome.png b/guides/assets/images/rails_welcome.png
index f2aa210d19..f2aa210d19 100644
--- a/railties/guides/assets/images/rails_welcome.png
+++ b/guides/assets/images/rails_welcome.png
Binary files differ
diff --git a/railties/guides/assets/images/session_fixation.png b/guides/assets/images/session_fixation.png
index 6b084508db..6b084508db 100644
--- a/railties/guides/assets/images/session_fixation.png
+++ b/guides/assets/images/session_fixation.png
Binary files differ
diff --git a/railties/guides/assets/images/tab_grey.gif b/guides/assets/images/tab_grey.gif
index e9680b7136..e9680b7136 100644
--- a/railties/guides/assets/images/tab_grey.gif
+++ b/guides/assets/images/tab_grey.gif
Binary files differ
diff --git a/railties/guides/assets/images/tab_info.gif b/guides/assets/images/tab_info.gif
index 458fea9a61..458fea9a61 100644
--- a/railties/guides/assets/images/tab_info.gif
+++ b/guides/assets/images/tab_info.gif
Binary files differ
diff --git a/railties/guides/assets/images/tab_note.gif b/guides/assets/images/tab_note.gif
index 1d5c171ed6..1d5c171ed6 100644
--- a/railties/guides/assets/images/tab_note.gif
+++ b/guides/assets/images/tab_note.gif
Binary files differ
diff --git a/railties/guides/assets/images/tab_red.gif b/guides/assets/images/tab_red.gif
index daf140b5a8..daf140b5a8 100644
--- a/railties/guides/assets/images/tab_red.gif
+++ b/guides/assets/images/tab_red.gif
Binary files differ
diff --git a/railties/guides/assets/images/tab_yellow.gif b/guides/assets/images/tab_yellow.gif
index dc961c99dd..dc961c99dd 100644
--- a/railties/guides/assets/images/tab_yellow.gif
+++ b/guides/assets/images/tab_yellow.gif
Binary files differ
diff --git a/railties/guides/assets/images/tab_yellow.png b/guides/assets/images/tab_yellow.png
index cceea6581f..cceea6581f 100644
--- a/railties/guides/assets/images/tab_yellow.png
+++ b/guides/assets/images/tab_yellow.png
Binary files differ
diff --git a/railties/guides/assets/images/validation_error_messages.png b/guides/assets/images/validation_error_messages.png
index 622d35da5d..622d35da5d 100644
--- a/railties/guides/assets/images/validation_error_messages.png
+++ b/guides/assets/images/validation_error_messages.png
Binary files differ
diff --git a/railties/guides/assets/images/vijaydev.jpg b/guides/assets/images/vijaydev.jpg
index e21d3cabfc..e21d3cabfc 100644
--- a/railties/guides/assets/images/vijaydev.jpg
+++ b/guides/assets/images/vijaydev.jpg
Binary files differ
diff --git a/railties/guides/assets/javascripts/guides.js b/guides/assets/javascripts/guides.js
index c4e4d459ea..c4e4d459ea 100644
--- a/railties/guides/assets/javascripts/guides.js
+++ b/guides/assets/javascripts/guides.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushAS3.js b/guides/assets/javascripts/syntaxhighlighter/shBrushAS3.js
index 8aa3ed2732..8aa3ed2732 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushAS3.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushAS3.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js b/guides/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js
index d40bbd7dd2..d40bbd7dd2 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushBash.js b/guides/assets/javascripts/syntaxhighlighter/shBrushBash.js
index 8c296969ff..8c296969ff 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushBash.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushBash.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushCSharp.js b/guides/assets/javascripts/syntaxhighlighter/shBrushCSharp.js
index 079214efe1..079214efe1 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushCSharp.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushCSharp.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js b/guides/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js
index 627dbb9b76..627dbb9b76 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushCpp.js b/guides/assets/javascripts/syntaxhighlighter/shBrushCpp.js
index 9f70d3aed6..9f70d3aed6 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushCpp.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushCpp.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushCss.js b/guides/assets/javascripts/syntaxhighlighter/shBrushCss.js
index 4297a9a648..4297a9a648 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushCss.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushCss.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushDelphi.js b/guides/assets/javascripts/syntaxhighlighter/shBrushDelphi.js
index e1060d4468..e1060d4468 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushDelphi.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushDelphi.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushDiff.js b/guides/assets/javascripts/syntaxhighlighter/shBrushDiff.js
index e9b14fc580..e9b14fc580 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushDiff.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushDiff.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushErlang.js b/guides/assets/javascripts/syntaxhighlighter/shBrushErlang.js
index 6ba7d9da87..6ba7d9da87 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushErlang.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushErlang.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushGroovy.js b/guides/assets/javascripts/syntaxhighlighter/shBrushGroovy.js
index 6ec5c18521..6ec5c18521 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushGroovy.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushGroovy.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushJScript.js b/guides/assets/javascripts/syntaxhighlighter/shBrushJScript.js
index ff98daba16..ff98daba16 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushJScript.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushJScript.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushJava.js b/guides/assets/javascripts/syntaxhighlighter/shBrushJava.js
index d692fd6382..d692fd6382 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushJava.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushJava.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js b/guides/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js
index 1a150a6ad3..1a150a6ad3 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushPerl.js b/guides/assets/javascripts/syntaxhighlighter/shBrushPerl.js
index d94a2e0ec5..d94a2e0ec5 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushPerl.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushPerl.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushPhp.js b/guides/assets/javascripts/syntaxhighlighter/shBrushPhp.js
index 95e6e4325b..95e6e4325b 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushPhp.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushPhp.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushPlain.js b/guides/assets/javascripts/syntaxhighlighter/shBrushPlain.js
index 9f7d9e90c3..9f7d9e90c3 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushPlain.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushPlain.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js b/guides/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js
index 0be1752968..0be1752968 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushPython.js b/guides/assets/javascripts/syntaxhighlighter/shBrushPython.js
index ce77462975..ce77462975 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushPython.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushPython.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushRuby.js b/guides/assets/javascripts/syntaxhighlighter/shBrushRuby.js
index ff82130a7a..ff82130a7a 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushRuby.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushRuby.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushSass.js b/guides/assets/javascripts/syntaxhighlighter/shBrushSass.js
index aa04da0996..aa04da0996 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushSass.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushSass.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushScala.js b/guides/assets/javascripts/syntaxhighlighter/shBrushScala.js
index 4b0b6f04d2..4b0b6f04d2 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushScala.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushScala.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushSql.js b/guides/assets/javascripts/syntaxhighlighter/shBrushSql.js
index 5c2cd8806f..5c2cd8806f 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushSql.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushSql.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushVb.js b/guides/assets/javascripts/syntaxhighlighter/shBrushVb.js
index be845dc0b3..be845dc0b3 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushVb.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushVb.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushXml.js b/guides/assets/javascripts/syntaxhighlighter/shBrushXml.js
index 69d9fd0b1f..69d9fd0b1f 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shBrushXml.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shBrushXml.js
diff --git a/railties/guides/assets/javascripts/syntaxhighlighter/shCore.js b/guides/assets/javascripts/syntaxhighlighter/shCore.js
index b47b645472..b47b645472 100644
--- a/railties/guides/assets/javascripts/syntaxhighlighter/shCore.js
+++ b/guides/assets/javascripts/syntaxhighlighter/shCore.js
diff --git a/railties/guides/assets/stylesheets/fixes.css b/guides/assets/stylesheets/fixes.css
index 54efa5b9b7..bf86b29efa 100644
--- a/railties/guides/assets/stylesheets/fixes.css
+++ b/guides/assets/stylesheets/fixes.css
@@ -12,5 +12,5 @@
.syntaxhighlighter table thead,
.syntaxhighlighter table caption,
.syntaxhighlighter textarea {
- line-height: 1.2em !important;
+ line-height: 1.25em !important;
}
diff --git a/guides/assets/stylesheets/kindle.css b/guides/assets/stylesheets/kindle.css
new file mode 100644
index 0000000000..b26cd1786a
--- /dev/null
+++ b/guides/assets/stylesheets/kindle.css
@@ -0,0 +1,11 @@
+p { text-indent: 0; }
+
+p, H1, H2, H3, H4, H5, H6, H7, H8, table { margin-top: 1em;}
+
+.pagebreak { page-break-before: always; }
+#toc H3 {
+ text-indent: 1em;
+}
+#toc .document {
+ text-indent: 2em;
+} \ No newline at end of file
diff --git a/railties/guides/assets/stylesheets/main.css b/guides/assets/stylesheets/main.css
index cf6e9e5cc8..42b85fefa3 100644
--- a/railties/guides/assets/stylesheets/main.css
+++ b/guides/assets/stylesheets/main.css
@@ -80,7 +80,8 @@ body {
font-family: Helvetica, Arial, sans-serif;
font-size: 87.5%;
line-height: 1.5em;
- background: #222;
+ background: #fff;
+ min-width: 69em;
color: #999;
}
@@ -93,6 +94,7 @@ body {
#topNav {
padding: 1em 0;
color: #565656;
+ background: #222;
}
#header {
@@ -110,7 +112,6 @@ body {
}
#container {
- background: #FFF;
color: #333;
padding: 0.5em 0 1.5em 0;
}
@@ -136,7 +137,7 @@ body {
#footer {
padding: 2em 0;
- background: url(../images/footer_tile.gif) repeat-x;
+ background: #222 url(../images/footer_tile.gif) repeat-x;
}
#footer .wrapper {
padding-left: 2em;
@@ -341,6 +342,14 @@ h6 {
margin-top: 0.25em;
}
+#mainCol dd.kindle, #subCol dd.kindle {
+ background: #d5e9f6 url(../images/tab_info.gif) no-repeat left top;
+ border: none;
+ padding: 1.25em 1em 1.25em 48px;
+ margin-left: 0;
+ margin-top: 0.25em;
+}
+
#mainCol div.warning, #subCol dd.warning {
background: #f9d9d8 url(../images/tab_red.gif) no-repeat left top;
border: none;
diff --git a/railties/guides/assets/stylesheets/print.css b/guides/assets/stylesheets/print.css
index 628da105d4..628da105d4 100644
--- a/railties/guides/assets/stylesheets/print.css
+++ b/guides/assets/stylesheets/print.css
diff --git a/railties/guides/assets/stylesheets/reset.css b/guides/assets/stylesheets/reset.css
index cb14fbcc55..cb14fbcc55 100644
--- a/railties/guides/assets/stylesheets/reset.css
+++ b/guides/assets/stylesheets/reset.css
diff --git a/railties/guides/assets/stylesheets/style.css b/guides/assets/stylesheets/style.css
index 89b2ab885a..89b2ab885a 100644
--- a/railties/guides/assets/stylesheets/style.css
+++ b/guides/assets/stylesheets/style.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shCore.css b/guides/assets/stylesheets/syntaxhighlighter/shCore.css
index 34f6864a15..34f6864a15 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shCore.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shCore.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreDefault.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreDefault.css
index 08f9e10e4e..08f9e10e4e 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreDefault.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shCoreDefault.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreDjango.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreDjango.css
index 1db1f70cb0..1db1f70cb0 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreDjango.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shCoreDjango.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css
index a45de9fd8e..a45de9fd8e 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css
index 706c77a0a8..706c77a0a8 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css
index 6101eba51f..6101eba51f 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css
index 2923ce7367..2923ce7367 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css
index e3733eed56..e3733eed56 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreRDark.css b/guides/assets/stylesheets/syntaxhighlighter/shCoreRDark.css
index d09368384d..d09368384d 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shCoreRDark.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shCoreRDark.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeDefault.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeDefault.css
index 136541172d..136541172d 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeDefault.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeDefault.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeDjango.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeDjango.css
index d8b4313433..d8b4313433 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeDjango.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeDjango.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css
index 77377d9533..77377d9533 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css
index dae5053fea..dae5053fea 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css
index 8fbd871fb5..8fbd871fb5 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css
index f4db39cd83..f4db39cd83 100755
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css
index c49563cc9d..c49563cc9d 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeRDark.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeRDark.css
index 6305a10e4e..6305a10e4e 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeRDark.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeRDark.css
diff --git a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css b/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css
index 6d2edb2eb8..6d2edb2eb8 100644
--- a/railties/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css
+++ b/guides/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css
diff --git a/guides/code/getting_started/Gemfile b/guides/code/getting_started/Gemfile
new file mode 100644
index 0000000000..670a8523b0
--- /dev/null
+++ b/guides/code/getting_started/Gemfile
@@ -0,0 +1,38 @@
+source 'https://rubygems.org'
+
+gem 'rails', '3.2.3'
+
+# Bundle edge Rails instead:
+# gem 'rails', :git => 'git://github.com/rails/rails.git'
+
+gem 'sqlite3'
+
+
+# Gems used only for assets and not required
+# in production environments by default.
+group :assets do
+ gem 'sass-rails', '~> 3.2.3'
+ gem 'coffee-rails', '~> 3.2.1'
+
+ # See https://github.com/sstephenson/execjs#readme for more supported runtimes
+ # gem 'therubyracer', :platform => :ruby
+
+ gem 'uglifier', '>= 1.0.3'
+end
+
+gem 'jquery-rails'
+
+# To use ActiveModel has_secure_password
+# gem 'bcrypt-ruby', '~> 3.0.0'
+
+# To use Jbuilder templates for JSON
+# gem 'jbuilder'
+
+# Use unicorn as the app server
+# gem 'unicorn'
+
+# Deploy with Capistrano
+# gem 'capistrano'
+
+# To use debugger
+# gem 'debugger'
diff --git a/railties/guides/code/getting_started/README b/guides/code/getting_started/README.rdoc
index 7c36f2356e..b5d7b6436b 100644
--- a/railties/guides/code/getting_started/README
+++ b/guides/code/getting_started/README.rdoc
@@ -86,8 +86,8 @@ programming in general.
Debugger support is available through the debugger command when you start your
Mongrel or WEBrick server with --debugger. This means that you can break out of
execution at any point in the code, investigate and change the model, and then,
-resume execution! You need to install ruby-debug to run the server in debugging
-mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
+resume execution! You need to install the 'debugger' gem to run the server in debugging
+mode. Add gem 'debugger' to your Gemfile and run <tt>bundle</tt> to install it. Example:
class WeblogController < ActionController::Base
def index
@@ -191,7 +191,6 @@ The default directory structure of a generated Ruby on Rails application:
`-- vendor
|-- assets
`-- stylesheets
- `-- plugins
app
Holds all the code that's specific to this particular application.
@@ -256,6 +255,5 @@ test
directory.
vendor
- External libraries that the application depends on. Also includes the plugins
- subdirectory. If the app has frozen rails, those gems also go here, under
- vendor/rails/. This directory is in the load path.
+ External libraries that the application depends on. If the app has frozen rails,
+ those gems also go here, under vendor/rails/. This directory is in the load path.
diff --git a/railties/guides/code/getting_started/Rakefile b/guides/code/getting_started/Rakefile
index e1d1ec8615..e1d1ec8615 100644
--- a/railties/guides/code/getting_started/Rakefile
+++ b/guides/code/getting_started/Rakefile
diff --git a/railties/guides/code/getting_started/app/assets/images/rails.png b/guides/code/getting_started/app/assets/images/rails.png
index d5edc04e65..d5edc04e65 100644
--- a/railties/guides/code/getting_started/app/assets/images/rails.png
+++ b/guides/code/getting_started/app/assets/images/rails.png
Binary files differ
diff --git a/guides/code/getting_started/app/assets/javascripts/application.js b/guides/code/getting_started/app/assets/javascripts/application.js
new file mode 100644
index 0000000000..9097d830e2
--- /dev/null
+++ b/guides/code/getting_started/app/assets/javascripts/application.js
@@ -0,0 +1,15 @@
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
+//
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// the compiled file.
+//
+// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
+// GO AFTER THE REQUIRES BELOW.
+//
+//= require jquery
+//= require jquery_ujs
+//= require_tree .
diff --git a/guides/code/getting_started/app/assets/stylesheets/application.css b/guides/code/getting_started/app/assets/stylesheets/application.css
new file mode 100644
index 0000000000..3b5cc6648e
--- /dev/null
+++ b/guides/code/getting_started/app/assets/stylesheets/application.css
@@ -0,0 +1,13 @@
+/*
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
+ * listed below.
+ *
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
+ *
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
+ * compiled file, but it's generally better to create a new file per style scope.
+ *
+ *= require_self
+ *= require_tree .
+*/
diff --git a/railties/guides/code/getting_started/app/controllers/application_controller.rb b/guides/code/getting_started/app/controllers/application_controller.rb
index e8065d9505..e8065d9505 100644
--- a/railties/guides/code/getting_started/app/controllers/application_controller.rb
+++ b/guides/code/getting_started/app/controllers/application_controller.rb
diff --git a/railties/guides/code/getting_started/app/controllers/comments_controller.rb b/guides/code/getting_started/app/controllers/comments_controller.rb
index 7447fd078b..cf3d1be42e 100644
--- a/railties/guides/code/getting_started/app/controllers/comments_controller.rb
+++ b/guides/code/getting_started/app/controllers/comments_controller.rb
@@ -1,16 +1,17 @@
class CommentsController < ApplicationController
http_basic_authenticate_with :name => "dhh", :password => "secret", :only => :destroy
+
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.create(params[:comment])
redirect_to post_path(@post)
end
-
+
def destroy
@post = Post.find(params[:post_id])
@comment = @post.comments.find(params[:id])
@comment.destroy
redirect_to post_path(@post)
end
-
+
end
diff --git a/guides/code/getting_started/app/controllers/home_controller.rb b/guides/code/getting_started/app/controllers/home_controller.rb
new file mode 100644
index 0000000000..309b70441e
--- /dev/null
+++ b/guides/code/getting_started/app/controllers/home_controller.rb
@@ -0,0 +1,5 @@
+class WelcomeController < ApplicationController
+ def index
+ end
+
+end
diff --git a/guides/code/getting_started/app/controllers/posts_controller.rb b/guides/code/getting_started/app/controllers/posts_controller.rb
new file mode 100644
index 0000000000..a8ac9aba5a
--- /dev/null
+++ b/guides/code/getting_started/app/controllers/posts_controller.rb
@@ -0,0 +1,47 @@
+class PostsController < ApplicationController
+
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :except => [:index, :show]
+
+ def index
+ @posts = Post.all
+ end
+
+ def show
+ @post = Post.find(params[:id])
+ end
+
+ def new
+ @post = Post.new
+ end
+
+ def create
+ @post = Post.new(params[:post])
+
+ if @post.save
+ redirect_to :action => :show, :id => @post.id
+ else
+ render 'new'
+ end
+ end
+
+ def edit
+ @post = Post.find(params[:id])
+ end
+
+ def update
+ @post = Post.find(params[:id])
+
+ if @post.update_attributes(params[:post])
+ redirect_to :action => :show, :id => @post.id
+ else
+ render 'edit'
+ end
+ end
+
+ def destroy
+ @post = Post.find(params[:id])
+ @post.destroy
+
+ redirect_to :action => :index
+ end
+end
diff --git a/railties/guides/code/getting_started/app/helpers/application_helper.rb b/guides/code/getting_started/app/helpers/application_helper.rb
index de6be7945c..de6be7945c 100644
--- a/railties/guides/code/getting_started/app/helpers/application_helper.rb
+++ b/guides/code/getting_started/app/helpers/application_helper.rb
diff --git a/railties/guides/code/getting_started/app/helpers/comments_helper.rb b/guides/code/getting_started/app/helpers/comments_helper.rb
index 0ec9ca5f2d..0ec9ca5f2d 100644
--- a/railties/guides/code/getting_started/app/helpers/comments_helper.rb
+++ b/guides/code/getting_started/app/helpers/comments_helper.rb
diff --git a/guides/code/getting_started/app/helpers/posts_helper.rb b/guides/code/getting_started/app/helpers/posts_helper.rb
new file mode 100644
index 0000000000..a7b8cec898
--- /dev/null
+++ b/guides/code/getting_started/app/helpers/posts_helper.rb
@@ -0,0 +1,2 @@
+module PostsHelper
+end
diff --git a/guides/code/getting_started/app/helpers/welcome_helper.rb b/guides/code/getting_started/app/helpers/welcome_helper.rb
new file mode 100644
index 0000000000..eeead45fc9
--- /dev/null
+++ b/guides/code/getting_started/app/helpers/welcome_helper.rb
@@ -0,0 +1,2 @@
+module WelcomeHelper
+end
diff --git a/railties/guides/code/getting_started/app/mailers/.gitkeep b/guides/code/getting_started/app/mailers/.gitkeep
index e69de29bb2..e69de29bb2 100644
--- a/railties/guides/code/getting_started/app/mailers/.gitkeep
+++ b/guides/code/getting_started/app/mailers/.gitkeep
diff --git a/railties/guides/code/getting_started/app/models/.gitkeep b/guides/code/getting_started/app/models/.gitkeep
index e69de29bb2..e69de29bb2 100644
--- a/railties/guides/code/getting_started/app/models/.gitkeep
+++ b/guides/code/getting_started/app/models/.gitkeep
diff --git a/railties/guides/code/getting_started/app/models/comment.rb b/guides/code/getting_started/app/models/comment.rb
index 4e76c5b5b0..4e76c5b5b0 100644
--- a/railties/guides/code/getting_started/app/models/comment.rb
+++ b/guides/code/getting_started/app/models/comment.rb
diff --git a/guides/code/getting_started/app/models/post.rb b/guides/code/getting_started/app/models/post.rb
new file mode 100644
index 0000000000..21387340b0
--- /dev/null
+++ b/guides/code/getting_started/app/models/post.rb
@@ -0,0 +1,6 @@
+class Post < ActiveRecord::Base
+ validates :title, :presence => true,
+ :length => { :minimum => 5 }
+
+ has_many :comments, :dependent => :destroy
+end
diff --git a/railties/guides/code/getting_started/app/views/comments/_comment.html.erb b/guides/code/getting_started/app/views/comments/_comment.html.erb
index 4c3fbf26cd..0cebe0bd96 100644
--- a/railties/guides/code/getting_started/app/views/comments/_comment.html.erb
+++ b/guides/code/getting_started/app/views/comments/_comment.html.erb
@@ -1,13 +1,13 @@
<p>
- <b>Commenter:</b>
+ <strong>Commenter:</strong>
<%= comment.commenter %>
</p>
-
+
<p>
- <b>Comment:</b>
+ <strong>Comment:</strong>
<%= comment.body %>
</p>
-
+
<p>
<%= link_to 'Destroy Comment', [comment.post, comment],
:confirm => 'Are you sure?',
diff --git a/railties/guides/code/getting_started/app/views/comments/_form.html.erb b/guides/code/getting_started/app/views/comments/_form.html.erb
index d15bdd6b59..00cb3a08f0 100644
--- a/railties/guides/code/getting_started/app/views/comments/_form.html.erb
+++ b/guides/code/getting_started/app/views/comments/_form.html.erb
@@ -1,13 +1,13 @@
<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
+ <p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
- </div>
- <div class="field">
+ </p>
+ <p>
<%= f.label :body %><br />
<%= f.text_area :body %>
- </div>
- <div class="actions">
+ </p>
+ <p>
<%= f.submit %>
- </div>
+ </p>
<% end %>
diff --git a/railties/guides/code/getting_started/app/views/layouts/application.html.erb b/guides/code/getting_started/app/views/layouts/application.html.erb
index 1e1e4b9a99..6578a41da2 100644
--- a/railties/guides/code/getting_started/app/views/layouts/application.html.erb
+++ b/guides/code/getting_started/app/views/layouts/application.html.erb
@@ -2,11 +2,11 @@
<html>
<head>
<title>Blog</title>
- <%= stylesheet_link_tag "application" %>
+ <%= stylesheet_link_tag "application", :media => "all" %>
<%= javascript_include_tag "application" %>
<%= csrf_meta_tags %>
</head>
-<body style="background: #EEEEEE;">
+<body>
<%= yield %>
diff --git a/guides/code/getting_started/app/views/posts/_form.html.erb b/guides/code/getting_started/app/views/posts/_form.html.erb
new file mode 100644
index 0000000000..f22139938c
--- /dev/null
+++ b/guides/code/getting_started/app/views/posts/_form.html.erb
@@ -0,0 +1,25 @@
+<%= form_for @post do |f| %>
+ <% if @post.errors.any? %>
+ <div id="errorExplanation">
+ <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
+ <ul>
+ <% @post.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+ <p>
+ <%= f.label :title %><br />
+ <%= f.text_field :title %>
+ </p>
+
+ <p>
+ <%= f.label :text %><br />
+ <%= f.text_area :text %>
+ </p>
+
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
diff --git a/guides/code/getting_started/app/views/posts/edit.html.erb b/guides/code/getting_started/app/views/posts/edit.html.erb
new file mode 100644
index 0000000000..911a48569d
--- /dev/null
+++ b/guides/code/getting_started/app/views/posts/edit.html.erb
@@ -0,0 +1,5 @@
+<h1>Editing post</h1>
+
+<%= render 'form' %>
+
+<%= link_to 'Back', :action => :index %>
diff --git a/guides/code/getting_started/app/views/posts/index.html.erb b/guides/code/getting_started/app/views/posts/index.html.erb
new file mode 100644
index 0000000000..7b72720d50
--- /dev/null
+++ b/guides/code/getting_started/app/views/posts/index.html.erb
@@ -0,0 +1,23 @@
+<h1>Listing posts</h1>
+
+<%= link_to 'New post', :action => :new %>
+
+<table>
+ <tr>
+ <th>Title</th>
+ <th>Text</th>
+ <th></th>
+ <th></th>
+ <th></th>
+ </tr>
+
+<% @posts.each do |post| %>
+ <tr>
+ <td><%= post.title %></td>
+ <td><%= post.text %></td>
+ <td><%= link_to 'Show', :action => :show, :id => post.id %>
+ <td><%= link_to 'Edit', :action => :edit, :id => post.id %>
+ <td><%= link_to 'Destroy', { :action => :destroy, :id => post.id }, :method => :delete, :confirm => 'Are you sure?' %>
+ </tr>
+<% end %>
+</table>
diff --git a/railties/guides/code/getting_started/app/views/posts/new.html.erb b/guides/code/getting_started/app/views/posts/new.html.erb
index 36ad7421f9..ce9523a721 100644
--- a/railties/guides/code/getting_started/app/views/posts/new.html.erb
+++ b/guides/code/getting_started/app/views/posts/new.html.erb
@@ -2,4 +2,4 @@
<%= render 'form' %>
-<%= link_to 'Back', posts_path %>
+<%= link_to 'Back', :action => :index %>
diff --git a/guides/code/getting_started/app/views/posts/show.html.erb b/guides/code/getting_started/app/views/posts/show.html.erb
new file mode 100644
index 0000000000..65809033ed
--- /dev/null
+++ b/guides/code/getting_started/app/views/posts/show.html.erb
@@ -0,0 +1,18 @@
+<p>
+ <strong>Title:</strong>
+ <%= @post.title %>
+</p>
+
+<p>
+ <strong>Text:</strong>
+ <%= @post.text %>
+</p>
+
+<h2>Comments</h2>
+<%= render @post.comments %>
+
+<h2>Add a comment:</h2>
+<%= render "comments/form" %>
+
+<%= link_to 'Edit Post', edit_post_path(@post) %> |
+<%= link_to 'Back to Posts', posts_path %>
diff --git a/guides/code/getting_started/app/views/welcome/index.html.erb b/guides/code/getting_started/app/views/welcome/index.html.erb
new file mode 100644
index 0000000000..e04680ea7e
--- /dev/null
+++ b/guides/code/getting_started/app/views/welcome/index.html.erb
@@ -0,0 +1,2 @@
+<h1>Hello, Rails!</h1>
+<%= link_to "My Blog", :controller => "posts" %>
diff --git a/railties/guides/code/getting_started/config.ru b/guides/code/getting_started/config.ru
index ddf869e921..ddf869e921 100644
--- a/railties/guides/code/getting_started/config.ru
+++ b/guides/code/getting_started/config.ru
diff --git a/railties/guides/code/getting_started/config/application.rb b/guides/code/getting_started/config/application.rb
index e914b5a80e..d2cd5c028b 100644
--- a/railties/guides/code/getting_started/config/application.rb
+++ b/guides/code/getting_started/config/application.rb
@@ -4,7 +4,7 @@ require 'rails/all'
if defined?(Bundler)
# If you precompile assets before deploying to production, use this line
- Bundler.require *Rails.groups(:assets => %w(development test))
+ Bundler.require(*Rails.groups(:assets => %w(development test)))
# If you want your assets lazily compiled in production, use this line
# Bundler.require(:default, :assets, Rails.env)
end
@@ -18,10 +18,6 @@ module Blog
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
- # Only load the plugins named here, in the order given (default is alphabetical).
- # :all can be used as a placeholder for all plugins not explicitly named.
- # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
-
# Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
@@ -39,10 +35,21 @@ module Blog
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
- # Enable the asset pipeline
+ # Use SQL instead of Active Record's schema dumper when creating the database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types.
+ # config.active_record.schema_format = :sql
+
+ # Enforce whitelist mode for mass assignment.
+ # This will create an empty whitelist of attributes available for mass-assignment for all models
+ # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
+ # parameters by using an attr_accessible or attr_protected declaration.
+ # config.active_record.whitelist_attributes = true
+
+ # Enable the asset pipeline.
config.assets.enabled = true
- # Version of your assets, change this if you want to expire all your assets
+ # Version of your assets, change this if you want to expire all your assets.
config.assets.version = '1.0'
end
end
diff --git a/railties/guides/code/getting_started/config/boot.rb b/guides/code/getting_started/config/boot.rb
index 4489e58688..3596736667 100644
--- a/railties/guides/code/getting_started/config/boot.rb
+++ b/guides/code/getting_started/config/boot.rb
@@ -1,5 +1,3 @@
-require 'rubygems'
-
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
diff --git a/railties/guides/code/getting_started/config/database.yml b/guides/code/getting_started/config/database.yml
index 51a4dd459d..51a4dd459d 100644
--- a/railties/guides/code/getting_started/config/database.yml
+++ b/guides/code/getting_started/config/database.yml
diff --git a/railties/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb
index 8f728b7ce7..8f728b7ce7 100644
--- a/railties/guides/code/getting_started/config/environment.rb
+++ b/guides/code/getting_started/config/environment.rb
diff --git a/railties/guides/code/getting_started/config/environments/development.rb b/guides/code/getting_started/config/environments/development.rb
index 89932bf19b..cec2b20c0b 100644
--- a/railties/guides/code/getting_started/config/environments/development.rb
+++ b/guides/code/getting_started/config/environments/development.rb
@@ -1,30 +1,34 @@
Blog::Application.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
- # Log error messages when you accidentally call methods on nil.
- config.whiny_nils = true
-
- # Show full error reports and disable caching
+ # Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
- # Don't care if the mailer can't send
+ # Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
- # Print deprecation notices to the Rails logger
+ # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
- # Only use best-standards-support built into browsers
+ # Only use best-standards-support built into browsers.
config.action_dispatch.best_standards_support = :builtin
- # Do not compress assets
+ # Raise exception on mass assignment protection for ActiveRecord models.
+ config.active_record.mass_assignment_sanitizer = :strict
+
+ # Log the query plan for queries taking more than this (works
+ # with SQLite, MySQL, and PostgreSQL).
+ config.active_record.auto_explain_threshold_in_seconds = 0.5
+
+ # Do not compress assets.
config.assets.compress = false
- # Expands the lines which load the assets
+ # Expands the lines which load the assets.
config.assets.debug = true
end
diff --git a/railties/guides/code/getting_started/config/environments/production.rb b/guides/code/getting_started/config/environments/production.rb
index 6ab63d30a6..ecc35b030b 100644
--- a/railties/guides/code/getting_started/config/environments/production.rb
+++ b/guides/code/getting_started/config/environments/production.rb
@@ -1,60 +1,67 @@
Blog::Application.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
- # Code is not reloaded between requests
+ # Code is not reloaded between requests.
config.cache_classes = true
- # Full error reports are disabled and caching is turned on
+ # Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
- # Disable Rails's static asset server (Apache or nginx will already do this)
+ # Disable Rails's static asset server (Apache or nginx will already do this).
config.serve_static_assets = false
- # Compress JavaScripts and CSS
+ # Compress JavaScripts and CSS.
config.assets.compress = true
- # Don't fallback to assets pipeline if a precompiled asset is missed
+ # Don't fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
- # Generate digests for assets URLs
+ # Generate digests for assets URLs.
config.assets.digest = true
- # Defaults to Rails.root.join("public/assets")
+ # Defaults to nil
# config.assets.manifest = YOUR_PATH
- # Specifies the header that your server uses for sending files
+ # Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
- # See everything in the log (default is :info)
+ # See everything in the log (default is :info).
# config.log_level = :debug
- # Use a different logger for distributed setups
- # config.logger = SyslogLogger.new
+ # Prepend all log lines with the following tags.
+ # config.log_tags = [ :subdomain, :uuid ]
- # Use a different cache store in production
+ # Use a different logger for distributed setups.
+ # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
+
+ # Use a different cache store in production.
# config.cache_store = :mem_cache_store
- # Enable serving of images, stylesheets, and JavaScripts from an asset server
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = "http://assets.example.com"
- # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+ # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added).
# config.assets.precompile += %w( search.js )
- # Disable delivery errors, bad email addresses will be ignored
+ # Disable delivery errors, bad email addresses will be ignored.
# config.action_mailer.raise_delivery_errors = false
- # Enable threaded mode
+ # Enable threaded mode.
# config.threadsafe!
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
- # the I18n.default_locale when a translation can not be found)
+ # the I18n.default_locale when a translation can not be found).
config.i18n.fallbacks = true
- # Send deprecation notices to registered listeners
+ # Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
+
+ # Log the query plan for queries taking more than this (works
+ # with SQLite, MySQL, and PostgreSQL).
+ # config.active_record.auto_explain_threshold_in_seconds = 0.5
end
diff --git a/railties/guides/code/getting_started/config/environments/test.rb b/guides/code/getting_started/config/environments/test.rb
index 833241ace3..f2bc932fb3 100644
--- a/railties/guides/code/getting_started/config/environments/test.rb
+++ b/guides/code/getting_started/config/environments/test.rb
@@ -1,5 +1,5 @@
Blog::Application.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
@@ -7,36 +7,28 @@ Blog::Application.configure do
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
- # Configure static asset server for tests with Cache-Control for performance
+ # Configure static asset server for tests with Cache-Control for performance.
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
- # Log error messages when you accidentally call methods on nil
- config.whiny_nils = true
-
- # Show full error reports and disable caching
+ # Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
- # Raise exceptions instead of rendering exception templates
+ # Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
- # Disable request forgery protection in test environment
- config.action_controller.allow_forgery_protection = false
+ # Disable request forgery protection in test environment.
+ config.action_controller.allow_forgery_protection = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
- # Use SQL instead of Active Record's schema dumper when creating the test database.
- # This is necessary if your schema can't be completely dumped by the schema dumper,
- # like if you have constraints or database-specific column types
- # config.active_record.schema_format = :sql
+ # Raise exception on mass assignment protection for Active Record models.
+ config.active_record.mass_assignment_sanitizer = :strict
- # Print deprecation notices to the stderr
+ # Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
-
- # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets
- config.assets.allow_debugging = true
end
diff --git a/railties/guides/code/getting_started/config/initializers/backtrace_silencers.rb b/guides/code/getting_started/config/initializers/backtrace_silencers.rb
index 59385cdf37..59385cdf37 100644
--- a/railties/guides/code/getting_started/config/initializers/backtrace_silencers.rb
+++ b/guides/code/getting_started/config/initializers/backtrace_silencers.rb
diff --git a/railties/guides/code/getting_started/config/initializers/inflections.rb b/guides/code/getting_started/config/initializers/inflections.rb
index 9e8b0131f8..5d8d9be237 100644
--- a/railties/guides/code/getting_started/config/initializers/inflections.rb
+++ b/guides/code/getting_started/config/initializers/inflections.rb
@@ -8,3 +8,8 @@
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end
+#
+# These inflection rules are supported but not enabled by default:
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.acronym 'RESTful'
+# end
diff --git a/railties/guides/code/getting_started/config/initializers/mime_types.rb b/guides/code/getting_started/config/initializers/mime_types.rb
index 72aca7e441..72aca7e441 100644
--- a/railties/guides/code/getting_started/config/initializers/mime_types.rb
+++ b/guides/code/getting_started/config/initializers/mime_types.rb
diff --git a/railties/guides/code/getting_started/config/initializers/secret_token.rb b/guides/code/getting_started/config/initializers/secret_token.rb
index b0c8ee23c1..f36ebdda18 100644
--- a/railties/guides/code/getting_started/config/initializers/secret_token.rb
+++ b/guides/code/getting_started/config/initializers/secret_token.rb
@@ -4,4 +4,6 @@
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
+# Make sure your secret key is kept private
+# if you're sharing your code publicly.
Blog::Application.config.secret_token = '685a9bf865b728c6549a191c90851c1b5ec41ecb60b9e94ad79dd3f824749798aa7b5e94431901960bee57809db0947b481570f7f13376b7ca190fa28099c459'
diff --git a/railties/guides/code/getting_started/config/initializers/session_store.rb b/guides/code/getting_started/config/initializers/session_store.rb
index 1a67af58b5..1a67af58b5 100644
--- a/railties/guides/code/getting_started/config/initializers/session_store.rb
+++ b/guides/code/getting_started/config/initializers/session_store.rb
diff --git a/railties/guides/code/getting_started/config/initializers/wrap_parameters.rb b/guides/code/getting_started/config/initializers/wrap_parameters.rb
index 999df20181..999df20181 100644
--- a/railties/guides/code/getting_started/config/initializers/wrap_parameters.rb
+++ b/guides/code/getting_started/config/initializers/wrap_parameters.rb
diff --git a/railties/guides/code/getting_started/config/locales/en.yml b/guides/code/getting_started/config/locales/en.yml
index 179c14ca52..179c14ca52 100644
--- a/railties/guides/code/getting_started/config/locales/en.yml
+++ b/guides/code/getting_started/config/locales/en.yml
diff --git a/railties/guides/code/getting_started/config/routes.rb b/guides/code/getting_started/config/routes.rb
index 31f0d210db..04a6bd374e 100644
--- a/railties/guides/code/getting_started/config/routes.rb
+++ b/guides/code/getting_started/config/routes.rb
@@ -1,9 +1,8 @@
Blog::Application.routes.draw do
- resources :posts do
- resources :comments
- end
- get "home/index"
+ resources :posts do
+ resources :comments
+ end
# The priority is based upon order of creation:
# first created -> highest priority.
@@ -54,11 +53,11 @@ Blog::Application.routes.draw do
# You can have the root of your site routed with "root"
# just remember to delete public/index.html.
- root :to => "home#index"
-
+ root :to => "welcome#index"
+
# See how all your routes lay out with "rake routes"
# This is a legacy wild controller route that's not recommended for RESTful applications.
# Note: This route will make all actions in every controller accessible via GET requests.
- # match ':controller(/:action(/:id(.:format)))'
+ # match ':controller(/:action(/:id))(.:format)'
end
diff --git a/railties/guides/code/getting_started/db/migrate/20110901012815_create_comments.rb b/guides/code/getting_started/db/migrate/20110901012815_create_comments.rb
index adda8078c1..adda8078c1 100644
--- a/railties/guides/code/getting_started/db/migrate/20110901012815_create_comments.rb
+++ b/guides/code/getting_started/db/migrate/20110901012815_create_comments.rb
diff --git a/railties/guides/code/getting_started/db/migrate/20110901012504_create_posts.rb b/guides/code/getting_started/db/migrate/20120420083127_create_posts.rb
index d45a961523..602bef31ab 100644
--- a/railties/guides/code/getting_started/db/migrate/20110901012504_create_posts.rb
+++ b/guides/code/getting_started/db/migrate/20120420083127_create_posts.rb
@@ -1,9 +1,8 @@
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
- t.string :name
t.string :title
- t.text :content
+ t.text :text
t.timestamps
end
diff --git a/railties/guides/code/getting_started/db/schema.rb b/guides/code/getting_started/db/schema.rb
index 9db4fbe4b6..cfb56ca9b9 100644
--- a/railties/guides/code/getting_started/db/schema.rb
+++ b/guides/code/getting_started/db/schema.rb
@@ -11,31 +11,30 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20110901013701) do
+ActiveRecord::Schema.define(:version => 20120420083127) do
create_table "comments", :force => true do |t|
t.string "commenter"
t.text "body"
t.integer "post_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
add_index "comments", ["post_id"], :name => "index_comments_on_post_id"
create_table "posts", :force => true do |t|
- t.string "name"
t.string "title"
- t.text "content"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.text "text"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
create_table "tags", :force => true do |t|
t.string "name"
t.integer "post_id"
- t.datetime "created_at"
- t.datetime "updated_at"
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
end
add_index "tags", ["post_id"], :name => "index_tags_on_post_id"
diff --git a/railties/guides/code/getting_started/db/seeds.rb b/guides/code/getting_started/db/seeds.rb
index 4edb1e857e..4edb1e857e 100644
--- a/railties/guides/code/getting_started/db/seeds.rb
+++ b/guides/code/getting_started/db/seeds.rb
diff --git a/railties/guides/code/getting_started/doc/README_FOR_APP b/guides/code/getting_started/doc/README_FOR_APP
index fe41f5cc24..fe41f5cc24 100644
--- a/railties/guides/code/getting_started/doc/README_FOR_APP
+++ b/guides/code/getting_started/doc/README_FOR_APP
diff --git a/railties/guides/code/getting_started/lib/assets/.gitkeep b/guides/code/getting_started/lib/assets/.gitkeep
index e69de29bb2..e69de29bb2 100644
--- a/railties/guides/code/getting_started/lib/assets/.gitkeep
+++ b/guides/code/getting_started/lib/assets/.gitkeep
diff --git a/railties/guides/code/getting_started/lib/tasks/.gitkeep b/guides/code/getting_started/lib/tasks/.gitkeep
index e69de29bb2..e69de29bb2 100644
--- a/railties/guides/code/getting_started/lib/tasks/.gitkeep
+++ b/guides/code/getting_started/lib/tasks/.gitkeep
diff --git a/railties/guides/code/getting_started/public/404.html b/guides/code/getting_started/public/404.html
index 9a48320a5f..9a48320a5f 100644
--- a/railties/guides/code/getting_started/public/404.html
+++ b/guides/code/getting_started/public/404.html
diff --git a/railties/guides/code/getting_started/public/422.html b/guides/code/getting_started/public/422.html
index 83660ab187..83660ab187 100644
--- a/railties/guides/code/getting_started/public/422.html
+++ b/guides/code/getting_started/public/422.html
diff --git a/railties/guides/code/getting_started/public/500.html b/guides/code/getting_started/public/500.html
index b80307fc16..f3648a0dbc 100644
--- a/railties/guides/code/getting_started/public/500.html
+++ b/guides/code/getting_started/public/500.html
@@ -20,7 +20,6 @@
<!-- This file lives in public/500.html -->
<div class="dialog">
<h1>We're sorry, but something went wrong.</h1>
- <p>We've been notified about this issue and we'll take a look at it shortly.</p>
</div>
</body>
</html>
diff --git a/railties/guides/code/getting_started/public/favicon.ico b/guides/code/getting_started/public/favicon.ico
index e69de29bb2..e69de29bb2 100644
--- a/railties/guides/code/getting_started/public/favicon.ico
+++ b/guides/code/getting_started/public/favicon.ico
diff --git a/railties/guides/code/getting_started/public/robots.txt b/guides/code/getting_started/public/robots.txt
index 085187fa58..1a3a5e4dd2 100644
--- a/railties/guides/code/getting_started/public/robots.txt
+++ b/guides/code/getting_started/public/robots.txt
@@ -1,5 +1,5 @@
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
-# User-Agent: *
+# User-agent: *
# Disallow: /
diff --git a/railties/guides/code/getting_started/script/rails b/guides/code/getting_started/script/rails
index f8da2cffd4..f8da2cffd4 100755
--- a/railties/guides/code/getting_started/script/rails
+++ b/guides/code/getting_started/script/rails
diff --git a/railties/guides/code/getting_started/test/fixtures/.gitkeep b/guides/code/getting_started/test/fixtures/.gitkeep
index e69de29bb2..e69de29bb2 100644
--- a/railties/guides/code/getting_started/test/fixtures/.gitkeep
+++ b/guides/code/getting_started/test/fixtures/.gitkeep
diff --git a/railties/guides/code/getting_started/test/fixtures/comments.yml b/guides/code/getting_started/test/fixtures/comments.yml
index d33da386bf..d33da386bf 100644
--- a/railties/guides/code/getting_started/test/fixtures/comments.yml
+++ b/guides/code/getting_started/test/fixtures/comments.yml
diff --git a/railties/guides/code/getting_started/test/fixtures/posts.yml b/guides/code/getting_started/test/fixtures/posts.yml
index 8b0f75a33d..e1edfd385e 100644
--- a/railties/guides/code/getting_started/test/fixtures/posts.yml
+++ b/guides/code/getting_started/test/fixtures/posts.yml
@@ -1,11 +1,9 @@
# Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html
one:
- name: MyString
title: MyString
- content: MyText
+ text: MyText
two:
- name: MyString
title: MyString
- content: MyText
+ text: MyText
diff --git a/railties/guides/code/getting_started/test/functional/.gitkeep b/guides/code/getting_started/test/functional/.gitkeep
index e69de29bb2..e69de29bb2 100644
--- a/railties/guides/code/getting_started/test/functional/.gitkeep
+++ b/guides/code/getting_started/test/functional/.gitkeep
diff --git a/railties/guides/code/getting_started/test/functional/comments_controller_test.rb b/guides/code/getting_started/test/functional/comments_controller_test.rb
index 2ec71b4ec5..2ec71b4ec5 100644
--- a/railties/guides/code/getting_started/test/functional/comments_controller_test.rb
+++ b/guides/code/getting_started/test/functional/comments_controller_test.rb
diff --git a/railties/guides/code/getting_started/test/functional/home_controller_test.rb b/guides/code/getting_started/test/functional/home_controller_test.rb
index 0d9bb47c3e..dff8e9d2c5 100644
--- a/railties/guides/code/getting_started/test/functional/home_controller_test.rb
+++ b/guides/code/getting_started/test/functional/home_controller_test.rb
@@ -1,6 +1,6 @@
require 'test_helper'
-class HomeControllerTest < ActionController::TestCase
+class WelcomeControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
diff --git a/railties/guides/code/getting_started/test/functional/posts_controller_test.rb b/guides/code/getting_started/test/functional/posts_controller_test.rb
index b8f7b07820..b8f7b07820 100644
--- a/railties/guides/code/getting_started/test/functional/posts_controller_test.rb
+++ b/guides/code/getting_started/test/functional/posts_controller_test.rb
diff --git a/railties/guides/code/getting_started/test/integration/.gitkeep b/guides/code/getting_started/test/integration/.gitkeep
index e69de29bb2..e69de29bb2 100644
--- a/railties/guides/code/getting_started/test/integration/.gitkeep
+++ b/guides/code/getting_started/test/integration/.gitkeep
diff --git a/railties/guides/code/getting_started/test/performance/browsing_test.rb b/guides/code/getting_started/test/performance/browsing_test.rb
index 3fea27b916..2a849b7f2b 100644
--- a/railties/guides/code/getting_started/test/performance/browsing_test.rb
+++ b/guides/code/getting_started/test/performance/browsing_test.rb
@@ -3,7 +3,7 @@ require 'rails/performance_test_help'
class BrowsingTest < ActionDispatch::PerformanceTest
# Refer to the documentation for all available options
- # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory]
+ # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory],
# :output => 'tmp/performance', :formats => [:flat] }
def test_homepage
diff --git a/railties/guides/code/getting_started/test/test_helper.rb b/guides/code/getting_started/test/test_helper.rb
index 8bf1192ffe..8bf1192ffe 100644
--- a/railties/guides/code/getting_started/test/test_helper.rb
+++ b/guides/code/getting_started/test/test_helper.rb
diff --git a/railties/guides/code/getting_started/test/unit/.gitkeep b/guides/code/getting_started/test/unit/.gitkeep
index e69de29bb2..e69de29bb2 100644
--- a/railties/guides/code/getting_started/test/unit/.gitkeep
+++ b/guides/code/getting_started/test/unit/.gitkeep
diff --git a/railties/guides/code/getting_started/test/unit/comment_test.rb b/guides/code/getting_started/test/unit/comment_test.rb
index b6d6131a96..b6d6131a96 100644
--- a/railties/guides/code/getting_started/test/unit/comment_test.rb
+++ b/guides/code/getting_started/test/unit/comment_test.rb
diff --git a/railties/guides/code/getting_started/test/unit/helpers/comments_helper_test.rb b/guides/code/getting_started/test/unit/helpers/comments_helper_test.rb
index 2518c16bd5..2518c16bd5 100644
--- a/railties/guides/code/getting_started/test/unit/helpers/comments_helper_test.rb
+++ b/guides/code/getting_started/test/unit/helpers/comments_helper_test.rb
diff --git a/railties/guides/code/getting_started/test/unit/helpers/home_helper_test.rb b/guides/code/getting_started/test/unit/helpers/home_helper_test.rb
index 4740a18dac..4740a18dac 100644
--- a/railties/guides/code/getting_started/test/unit/helpers/home_helper_test.rb
+++ b/guides/code/getting_started/test/unit/helpers/home_helper_test.rb
diff --git a/railties/guides/code/getting_started/test/unit/helpers/posts_helper_test.rb b/guides/code/getting_started/test/unit/helpers/posts_helper_test.rb
index 48549c2ea1..48549c2ea1 100644
--- a/railties/guides/code/getting_started/test/unit/helpers/posts_helper_test.rb
+++ b/guides/code/getting_started/test/unit/helpers/posts_helper_test.rb
diff --git a/railties/guides/code/getting_started/test/unit/post_test.rb b/guides/code/getting_started/test/unit/post_test.rb
index 6d9d463a71..6d9d463a71 100644
--- a/railties/guides/code/getting_started/test/unit/post_test.rb
+++ b/guides/code/getting_started/test/unit/post_test.rb
diff --git a/railties/guides/code/getting_started/test/unit/tag_test.rb b/guides/code/getting_started/test/unit/tag_test.rb
index b8498a117c..b8498a117c 100644
--- a/railties/guides/code/getting_started/test/unit/tag_test.rb
+++ b/guides/code/getting_started/test/unit/tag_test.rb
diff --git a/railties/guides/code/getting_started/vendor/assets/stylesheets/.gitkeep b/guides/code/getting_started/vendor/plugins/.gitkeep
index e69de29bb2..e69de29bb2 100644
--- a/railties/guides/code/getting_started/vendor/assets/stylesheets/.gitkeep
+++ b/guides/code/getting_started/vendor/plugins/.gitkeep
diff --git a/railties/guides/rails_guides.rb b/guides/rails_guides.rb
index feb5fe3937..1955309865 100644
--- a/railties/guides/rails_guides.rb
+++ b/guides/rails_guides.rb
@@ -8,13 +8,10 @@ def bundler?
File.exists?('Gemfile')
end
-# Loading Action Pack requires rack and erubis.
-require 'rubygems'
-
begin
# Guides generation in the Rails repo.
- as_lib = File.join(pwd, "../../activesupport/lib")
- ap_lib = File.join(pwd, "../../actionpack/lib")
+ as_lib = File.join(pwd, "../activesupport/lib")
+ ap_lib = File.join(pwd, "../actionpack/lib")
$:.unshift as_lib if File.directory?(as_lib)
$:.unshift ap_lib if File.directory?(ap_lib)
diff --git a/railties/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb
index 4682ead66e..d6a98f9ac4 100644
--- a/railties/guides/rails_guides/generator.rb
+++ b/guides/rails_guides/generator.rb
@@ -47,6 +47,11 @@
# Set to "1" to indicate generated guides should be marked as edge. This
# inserts a badge and changes the preamble of the home page.
#
+# KINDLE
+# Set to "1" to generate the .mobi with all the guides. The kindlegen
+# executable must be in your PATH. You can get it for free from
+# http://www.amazon.com/kindlepublishing
+#
# ---------------------------------------------------------------------------
require 'set'
@@ -65,37 +70,81 @@ module RailsGuides
class Generator
attr_reader :guides_dir, :source_dir, :output_dir, :edge, :warnings, :all
- GUIDES_RE = /\.(?:textile|html\.erb)$/
+ GUIDES_RE = /\.(?:textile|erb)$/
def initialize(output=nil)
- @lang = ENV['GUIDES_LANGUAGE']
+ set_flags_from_environment
+
+ if kindle?
+ check_for_kindlegen
+ register_kindle_mime_types
+ end
+
initialize_dirs(output)
create_output_dir_if_needed
- set_flags_from_environment
+ end
+
+ def set_flags_from_environment
+ @edge = ENV['EDGE'] == '1'
+ @warnings = ENV['WARNINGS'] == '1'
+ @all = ENV['ALL'] == '1'
+ @kindle = ENV['KINDLE'] == '1'
+ @version = ENV['RAILS_VERSION'] || `git rev-parse --short HEAD`.chomp
+ @lang = ENV['GUIDES_LANGUAGE']
+ end
+
+ def register_kindle_mime_types
+ Mime::Type.register_alias("application/xml", :opf, %w(opf))
+ Mime::Type.register_alias("application/xml", :ncx, %w(ncx))
end
def generate
generate_guides
copy_assets
+ generate_mobi if kindle?
end
private
+
+ def kindle?
+ @kindle
+ end
+
+ def check_for_kindlegen
+ if `which kindlegen`.blank?
+ raise "Can't create a kindle version without `kindlegen`."
+ end
+ end
+
+ def generate_mobi
+ opf = "#{output_dir}/rails_guides.opf"
+ out = "#{output_dir}/kindlegen.out"
+
+ system "kindlegen #{opf} -o #{mobi} > #{out} 2>&1"
+ puts "Guides compiled as Kindle book to #{mobi}"
+ puts "(kindlegen log at #{out})."
+ end
+
+ def mobi
+ "ruby_on_rails_guides_#@version%s.mobi" % (@lang.present? ? ".#@lang" : '')
+ end
+
def initialize_dirs(output)
@guides_dir = File.join(File.dirname(__FILE__), '..')
- @source_dir = File.join(@guides_dir, "source", @lang.to_s)
- @output_dir = output || File.join(@guides_dir, "output", @lang.to_s)
+ @source_dir = "#@guides_dir/source/#@lang"
+ @output_dir = if output
+ output
+ elsif kindle?
+ "#@guides_dir/output/kindle/#@lang"
+ else
+ "#@guides_dir/output/#@lang"
+ end.sub(%r</$>, '')
end
def create_output_dir_if_needed
FileUtils.mkdir_p(output_dir)
end
- def set_flags_from_environment
- @edge = ENV['EDGE'] == '1'
- @warnings = ENV['WARNINGS'] == '1'
- @all = ENV['ALL'] == '1'
- end
-
def generate_guides
guides_to_generate.each do |guide|
output_file = output_file_for(guide)
@@ -105,13 +154,20 @@ module RailsGuides
def guides_to_generate
guides = Dir.entries(source_dir).grep(GUIDES_RE)
+
+ if kindle?
+ Dir.entries("#{source_dir}/kindle").grep(GUIDES_RE).map do |entry|
+ guides << "kindle/#{entry}"
+ end
+ end
+
ENV.key?('ONLY') ? select_only(guides) : guides
end
def select_only(guides)
prefixes = ENV['ONLY'].split(",").map(&:strip)
guides.select do |guide|
- prefixes.any? {|p| guide.start_with?(p)}
+ prefixes.any? { |p| guide.start_with?(p) || guide.start_with?("kindle") }
end
end
@@ -120,35 +176,47 @@ module RailsGuides
end
def output_file_for(guide)
- guide.sub(GUIDES_RE, '.html')
+ if guide =~/\.textile$/
+ guide.sub(/\.textile$/, '.html')
+ else
+ guide.sub(/\.erb$/, '')
+ end
+ end
+
+ def output_path_for(output_file)
+ File.join(output_dir, File.basename(output_file))
end
def generate?(source_file, output_file)
fin = File.join(source_dir, source_file)
- fout = File.join(output_dir, output_file)
+ fout = output_path_for(output_file)
all || !File.exists?(fout) || File.mtime(fout) < File.mtime(fin)
end
def generate_guide(guide, output_file)
- puts "Generating #{output_file}"
- File.open(File.join(output_dir, output_file), 'w') do |f|
- view = ActionView::Base.new(source_dir, :edge => edge)
+ output_path = output_path_for(output_file)
+ puts "Generating #{guide} as #{output_file}"
+ layout = kindle? ? 'kindle/layout' : 'layout'
+
+ File.open(output_path, 'w') do |f|
+ view = ActionView::Base.new(source_dir, :edge => @edge, :version => @version, :mobi => "kindle/#{mobi}")
view.extend(Helpers)
- if guide =~ /\.html\.erb$/
+ if guide =~ /\.(\w+)\.erb$/
# Generate the special pages like the home.
- result = view.render(:layout => 'layout', :file => guide)
+ # Passing a template handler in the template name is deprecated. So pass the file name without the extension.
+ result = view.render(:layout => layout, :formats => [$1], :file => $`)
else
body = File.read(File.join(source_dir, guide))
body = set_header_section(body, view)
body = set_index(body, view)
- result = view.render(:layout => 'layout', :text => textile(body))
+ result = view.render(:layout => layout, :text => textile(body))
warn_about_broken_links(result) if @warnings
end
- f.write result
+ f.write(result)
end
end
@@ -215,7 +283,7 @@ module RailsGuides
anchors = Set.new
html.scan(/<h\d\s+id="([^"]+)/).flatten.each do |anchor|
if anchors.member?(anchor)
- puts "*** DUPLICATE ID: #{anchor}, please put and explicit ID, e.g. h4(#explicit-id), or consider rewording"
+ puts "*** DUPLICATE ID: #{anchor}, please use an explicit ID, e.g. h4(#explicit-id), or consider rewording"
else
anchors << anchor
end
diff --git a/railties/guides/rails_guides/helpers.rb b/guides/rails_guides/helpers.rb
index 463df8a7a8..e6ef656474 100644
--- a/railties/guides/rails_guides/helpers.rb
+++ b/guides/rails_guides/helpers.rb
@@ -12,6 +12,22 @@ module RailsGuides
result
end
+ def documents_by_section
+ @documents_by_section ||= YAML.load_file(File.expand_path('../../source/documents.yaml', __FILE__))
+ end
+
+ def documents_flat
+ documents_by_section.map {|section| section['documents']}.flatten
+ end
+
+ def finished_documents(documents)
+ documents.reject { |document| document['work_in_progress'] }
+ end
+
+ def docs_for_menu(position)
+ position == 'L' ? documents_by_section.to(3) : documents_by_section.from(4)
+ end
+
def author(name, nick, image = 'credits_pic_blank.gif', &block)
image = "images/#{image}"
diff --git a/railties/guides/rails_guides/indexer.rb b/guides/rails_guides/indexer.rb
index fb46491817..89fbccbb1d 100644
--- a/railties/guides/rails_guides/indexer.rb
+++ b/guides/rails_guides/indexer.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/object/blank'
-require 'active_support/ordered_hash'
require 'active_support/core_ext/string/inflections'
module RailsGuides
@@ -21,7 +20,7 @@ module RailsGuides
def process(string, current_level=3, counters=[1])
s = StringScanner.new(string)
- level_hash = ActiveSupport::OrderedHash.new
+ level_hash = {}
while !s.eos?
re = %r{^h(\d)(?:\((#.*?)\))?\s*\.\s*(.*)$}
diff --git a/railties/guides/rails_guides/levenshtein.rb b/guides/rails_guides/levenshtein.rb
index f99c6e6ba8..489aa3ea7a 100644
--- a/railties/guides/rails_guides/levenshtein.rb
+++ b/guides/rails_guides/levenshtein.rb
@@ -1,6 +1,6 @@
module RailsGuides
module Levenshtein
- # Based on the pseudocode in http://en.wikipedia.org/wiki/Levenshtein_distance.
+ # Based on the pseudocode in http://en.wikipedia.org/wiki/Levenshtein_distance
def self.distance(s1, s2)
s = s1.unpack('U*')
t = s2.unpack('U*')
diff --git a/railties/guides/rails_guides/textile_extensions.rb b/guides/rails_guides/textile_extensions.rb
index 4677fae504..0a002a785f 100644
--- a/railties/guides/rails_guides/textile_extensions.rb
+++ b/guides/rails_guides/textile_extensions.rb
@@ -1,5 +1,11 @@
require 'active_support/core_ext/object/inclusion'
+module RedCloth::Formatters::HTML
+ def emdash(opts)
+ "--"
+ end
+end
+
module RailsGuides
module TextileExtensions
def notestuff(body)
diff --git a/railties/guides/source/2_2_release_notes.textile b/guides/source/2_2_release_notes.textile
index 8e2d528eee..3a0f2efbaf 100644
--- a/railties/guides/source/2_2_release_notes.textile
+++ b/guides/source/2_2_release_notes.textile
@@ -229,7 +229,7 @@ This will enable recognition of (among others) these routes:
* Lead Contributor: "S. Brent Faulkner":http://www.unwwwired.net/
* More information:
-** "Rails Routing from the Outside In":http://guides.rubyonrails.org/routing.html#_nested_resources
+** "Rails Routing from the Outside In":http://guides.rubyonrails.org/routing.html#nested-resources
** "What's New in Edge Rails: Shallow Routes":http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-shallow-routes
h4. Method Arrays for Member or Collection Routes
diff --git a/railties/guides/source/2_3_release_notes.textile b/guides/source/2_3_release_notes.textile
index 15abba66ab..15abba66ab 100644
--- a/railties/guides/source/2_3_release_notes.textile
+++ b/guides/source/2_3_release_notes.textile
diff --git a/railties/guides/source/3_0_release_notes.textile b/guides/source/3_0_release_notes.textile
index d22c76dd81..d22c76dd81 100644
--- a/railties/guides/source/3_0_release_notes.textile
+++ b/guides/source/3_0_release_notes.textile
diff --git a/railties/guides/source/3_1_release_notes.textile b/guides/source/3_1_release_notes.textile
index c4da87dc34..f88d8624ba 100644
--- a/railties/guides/source/3_1_release_notes.textile
+++ b/guides/source/3_1_release_notes.textile
@@ -21,6 +21,113 @@ Rails 3.1 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby ve
TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02 though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x jump on 1.9.2 for smooth sailing.
+h4. What to update in your apps
+
+The following changes are meant for upgrading your application to Rails 3.1.3, the latest 3.1.x version of Rails.
+
+h5. Gemfile
+
+Make the following changes to your +Gemfile+.
+
+<ruby>
+gem 'rails', '= 3.1.3'
+gem 'mysql2'
+
+# Needed for the new asset pipeline
+group :assets do
+ gem 'sass-rails', "~> 3.1.5"
+ gem 'coffee-rails', "~> 3.1.1"
+ gem 'uglifier', ">= 1.0.3"
+end
+
+# jQuery is the default JavaScript library in Rails 3.1
+gem 'jquery-rails'
+</ruby>
+
+h5. config/application.rb
+
+* The asset pipeline requires the following additions:
+
+<ruby>
+config.assets.enabled = true
+config.assets.version = '1.0'
+</ruby>
+
+* If your application is using the "/assets" route for a resource you may want change the prefix used for assets to avoid conflicts:
+
+<ruby>
+# Defaults to '/assets'
+config.assets.prefix = '/asset-files'
+</ruby>
+
+h5. config/environments/development.rb
+
+* Remove the RJS setting <tt>config.action_view.debug_rjs = true</tt>.
+
+* Add the following, if you enable the asset pipeline.
+
+<ruby>
+# Do not compress assets
+config.assets.compress = false
+
+# Expands the lines which load the assets
+config.assets.debug = true
+</ruby>
+
+h5. config/environments/production.rb
+
+* Again, most of the changes below are for the asset pipeline. You can read more about these in the "Asset Pipeline":asset_pipeline.html guide.
+
+<ruby>
+# Compress JavaScripts and CSS
+config.assets.compress = true
+
+# Don't fallback to assets pipeline if a precompiled asset is missed
+config.assets.compile = false
+
+# Generate digests for assets URLs
+config.assets.digest = true
+
+# Defaults to Rails.root.join("public/assets")
+# config.assets.manifest = YOUR_PATH
+
+# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+# config.assets.precompile += %w( search.js )
+
+
+# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+# config.force_ssl = true
+
+</ruby>
+
+h5. config/environments/test.rb
+
+<ruby>
+# Configure static asset server for tests with Cache-Control for performance
+config.serve_static_assets = true
+config.static_cache_control = "public, max-age=3600"
+</ruby>
+
+h5. config/initializers/wrap_parameters.rb
+
+* Add this file with the following contents, if you wish to wrap parameters into a nested hash. This is on by default in new applications.
+
+<ruby>
+# Be sure to restart your server when you modify this file.
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters :format => [:json]
+end
+
+# Disable root element in JSON by default.
+ActiveSupport.on_load(:active_record) do
+ self.include_root_in_json = false
+end
+</ruby>
+
h3. Creating a Rails 3.1 application
<shell>
@@ -46,7 +153,7 @@ $ rails new myapp --edge
If you have a local checkout of the Rails repository and want to generate an application using that, you can pass the +--dev+ flag:
<shell>
-$ ruby /path/to/rails/bin/rails new myapp --dev
+$ ruby /path/to/rails/railties/bin/rails new myapp --dev
</shell>
h3. Rails Architectural Changes
diff --git a/guides/source/3_2_release_notes.textile b/guides/source/3_2_release_notes.textile
new file mode 100644
index 0000000000..3524ea6595
--- /dev/null
+++ b/guides/source/3_2_release_notes.textile
@@ -0,0 +1,552 @@
+h2. Ruby on Rails 3.2 Release Notes
+
+Highlights in Rails 3.2:
+
+* Faster Development Mode
+* New Routing Engine
+* Automatic Query Explains
+* Tagged Logging
+
+These release notes cover the major changes, but do not include each bug-fix and changes. If you want to see everything, check out the "list of commits":https://github.com/rails/rails/commits/3-2-stable in the main Rails repository on GitHub.
+
+endprologue.
+
+h3. Upgrading to Rails 3.2
+
+If you're upgrading an existing application, it's a great idea to have good test coverage before going in. You should also first upgrade to Rails 3.1 in case you haven't and make sure your application still runs as expected before attempting an update to Rails 3.2. Then take heed of the following changes:
+
+h4. Rails 3.2 requires at least Ruby 1.8.7
+
+Rails 3.2 requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible. Rails 3.2 is also compatible with Ruby 1.9.2.
+
+TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on to 1.9.2 or 1.9.3 for smooth sailing.
+
+h4. What to update in your apps
+
+* Update your Gemfile to depend on
+** <tt>rails = 3.2.0</tt>
+** <tt>sass-rails ~> 3.2.3</tt>
+** <tt>coffee-rails ~> 3.2.1</tt>
+** <tt>uglifier >= 1.0.3</tt>
+
+* Rails 3.2 deprecates <tt>vendor/plugins</tt> and Rails 4.0 will remove them completely. You can start replacing these plugins by extracting them as gems and adding them in your Gemfile. If you choose not to make them gems, you can move them into, say, <tt>lib/my_plugin/*</tt> and add an appropriate initializer in <tt>config/initializers/my_plugin.rb</tt>.
+
+* There are a couple of new configuration changes you'd want to add in <tt>config/environments/development.rb</tt>:
+
+<ruby>
+# Raise exception on mass assignment protection for Active Record models
+config.active_record.mass_assignment_sanitizer = :strict
+
+# Log the query plan for queries taking more than this (works
+# with SQLite, MySQL, and PostgreSQL)
+config.active_record.auto_explain_threshold_in_seconds = 0.5
+</ruby>
+
+The <tt>mass_assignment_sanitizer</tt> config also needs to be added in <tt>config/environments/test.rb</tt>:
+
+<ruby>
+# Raise exception on mass assignment protection for Active Record models
+config.active_record.mass_assignment_sanitizer = :strict
+</ruby>
+
+h4. What to update in your engines
+
+Replace the code beneath the comment in <tt>script/rails</tt> with the following content:
+
+<ruby>
+ENGINE_ROOT = File.expand_path('../..', __FILE__)
+ENGINE_PATH = File.expand_path('../../lib/your_engine_name/engine', __FILE__)
+
+require 'rails/all'
+require 'rails/engine/commands'
+</ruby>
+
+h3. Creating a Rails 3.2 application
+
+<shell>
+# You should have the 'rails' rubygem installed
+$ rails new myapp
+$ cd myapp
+</shell>
+
+h4. Vendoring Gems
+
+Rails now uses a +Gemfile+ in the application root to determine the gems you require for your application to start. This +Gemfile+ is processed by the "Bundler":https://github.com/carlhuda/bundler gem, which then installs all your dependencies. It can even install all the dependencies locally to your application so that it doesn't depend on the system gems.
+
+More information: "Bundler homepage":http://gembundler.com
+
+h4. Living on the Edge
+
++Bundler+ and +Gemfile+ makes freezing your Rails application easy as pie with the new dedicated +bundle+ command. If you want to bundle straight from the Git repository, you can pass the +--edge+ flag:
+
+<shell>
+$ rails new myapp --edge
+</shell>
+
+If you have a local checkout of the Rails repository and want to generate an application using that, you can pass the +--dev+ flag:
+
+<shell>
+$ ruby /path/to/rails/railties/bin/rails new myapp --dev
+</shell>
+
+h3. Major Features
+
+h4. Faster Development Mode & Routing
+
+Rails 3.2 comes with a development mode that's noticeably faster. Inspired by "Active Reload":https://github.com/paneq/active_reload, Rails reloads classes only when files actually change. The performance gains are dramatic on a larger application. Route recognition also got a bunch faster thanks to the new "Journey":https://github.com/rails/journey engine.
+
+h4. Automatic Query Explains
+
+Rails 3.2 comes with a nice feature that explains queries generated by ARel by defining an +explain+ method in <tt>ActiveRecord::Relation</tt>. For example, you can run something like <tt>puts Person.active.limit(5).explain</tt> and the query ARel produces is explained. This allows to check for the proper indexes and further optimizations.
+
+Queries that take more than half a second to run are *automatically* explained in the development mode. This threshold, of course, can be changed.
+
+h4. Tagged Logging
+
+When running a multi-user, multi-account application, it's a great help to be able to filter the log by who did what. TaggedLogging in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications.
+
+h3. Documentation
+
+From Rails 3.2, the Rails guides are available for the Kindle and free Kindle Reading Apps for the iPad, iPhone, Mac, Android, etc.
+
+h3. Railties
+
+* Speed up development by only reloading classes if dependencies files changed. This can be turned off by setting <tt>config.reload_classes_only_on_change</tt> to false.
+
+* New applications get a flag <tt>config.active_record.auto_explain_threshold_in_seconds</tt> in the environments configuration files. With a value of <tt>0.5</tt> in <tt>development.rb</tt> and commented out in <tt>production.rb</tt>. No mention in <tt>test.rb</tt>.
+
+* Added <tt>config.exceptions_app</tt> to set the exceptions application invoked by the +ShowException+ middleware when an exception happens. Defaults to <tt>ActionDispatch::PublicExceptions.new(Rails.public_path)</tt>.
+
+* Added a <tt>DebugExceptions</tt> middleware which contains features extracted from <tt>ShowExceptions</tt> middleware.
+
+* Display mounted engines' routes in <tt>rake routes</tt>.
+
+* Allow to change the loading order of railties with <tt>config.railties_order</tt> like:
+
+<ruby>
+config.railties_order = [Blog::Engine, :main_app, :all]
+</ruby>
+
+* Scaffold returns 204 No Content for API requests without content. This makes scaffold work with jQuery out of the box.
+
+* Update <tt>Rails::Rack::Logger</tt> middleware to apply any tags set in <tt>config.log_tags</tt> to <tt>ActiveSupport::TaggedLogging</tt>. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications.
+
+* Default options to +rails new+ can be set in <tt>~/.railsrc</tt>. You can specify extra command-line arguments to be used every time 'rails new' runs in the <tt>.railsrc</tt> configuration file in your home directory.
+
+* Add an alias +d+ for +destroy+. This works for engines too.
+
+* Attributes on scaffold and model generators default to string. This allows the following: <tt>rails g scaffold Post title body:text author</tt>
+
+* Allow scaffold/model/migration generators to accept "index" and "uniq" modifiers. For example,
+
+<ruby>
+rails g scaffold Post title:string:index author:uniq price:decimal{7,2}
+</ruby>
+
+will create indexes for +title+ and +author+ with the latter being an unique index. Some types such as decimal accept custom options. In the example, +price+ will be a decimal column with precision and scale set to 7 and 2 respectively.
+
+* Turn gem has been removed from default Gemfile.
+
+* Remove old plugin generator +rails generate plugin+ in favor of +rails plugin new+ command.
+
+* Remove old <tt>config.paths.app.controller</tt> API in favor of <tt>config.paths["app/controller"]</tt>.
+
+h4(#railties_deprecations). Deprecations
+
+* +Rails::Plugin+ is deprecated and will be removed in Rails 4.0. Instead of adding plugins to +vendor/plugins+ use gems or bundler with path or git dependencies.
+
+h3. Action Mailer
+
+* Upgraded <tt>mail</tt> version to 2.4.0.
+
+* Removed the old Action Mailer API which was deprecated since Rails 3.0.
+
+h3. Action Pack
+
+h4. Action Controller
+
+* Make <tt>ActiveSupport::Benchmarkable</tt> a default module for <tt>ActionController::Base,</tt> so the <tt>#benchmark</tt> method is once again available in the controller context like it used to be.
+
+* Added +:gzip+ option to +caches_page+. The default option can be configured globally using <tt>page_cache_compression</tt>.
+
+* Rails will now use your default layout (such as "layouts/application") when you specify a layout with <tt>:only</tt> and <tt>:except</tt> condition, and those conditions fail.
+
+<ruby>
+class CarsController
+ layout 'single_car', :only => :show
+end
+</ruby>
+
+Rails will use 'layouts/single_car' when a request comes in :show action, and use 'layouts/application' (or 'layouts/cars', if exists) when a request comes in for any other actions.
+
+* form_for is changed to use "#{action}_#{as}" as the css class and id if +:as+ option is provided. Earlier versions used "#{as}_#{action}".
+
+* <tt>ActionController::ParamsWrapper</tt> on ActiveRecord models now only wrap <tt>attr_accessible</tt> attributes if they were set. If not, only the attributes returned by the class method +attribute_names+ will be wrapped. This fixes the wrapping of nested attributes by adding them to +attr_accessible+.
+
+* Log "Filter chain halted as CALLBACKNAME rendered or redirected" every time a before callback halts.
+
+* <tt>ActionDispatch::ShowExceptions</tt> is refactored. The controller is responsible for choosing to show exceptions. It's possible to override +show_detailed_exceptions?+ in controllers to specify which requests should provide debugging information on errors.
+
+* Responders now return 204 No Content for API requests without a response body (as in the new scaffold).
+
+* <tt>ActionController::TestCase</tt> cookies is refactored. Assigning cookies for test cases should now use <tt>cookies[]</tt>
+
+<ruby>
+cookies[:email] = 'user@example.com'
+get :index
+assert_equal 'user@example.com', cookies[:email]
+</ruby>
+
+To clear the cookies, use +clear+.
+
+<ruby>
+cookies.clear
+get :index
+assert_nil cookies[:email]
+</ruby>
+
+We now no longer write out HTTP_COOKIE and the cookie jar is persistent between requests so if you need to manipulate the environment for your test you need to do it before the cookie jar is created.
+
+* <tt>send_file</tt> now guesses the MIME type from the file extension if +:type+ is not provided.
+
+* MIME type entries for PDF, ZIP and other formats were added.
+
+* Allow fresh_when/stale? to take a record instead of an options hash.
+
+* Changed log level of warning for missing CSRF token from <tt>:debug</tt> to <tt>:warn</tt>.
+
+* Assets should use the request protocol by default or default to relative if no request is available.
+
+h5(#actioncontroller_deprecations). Deprecations
+
+* Deprecated implied layout lookup in controllers whose parent had a explicit layout set:
+
+<ruby>
+class ApplicationController
+ layout "application"
+end
+
+class PostsController < ApplicationController
+end
+</ruby>
+
+In the example above, Posts controller will no longer automatically look up for a posts layout. If you need this functionality you could either remove <tt>layout "application"</tt> from +ApplicationController+ or explicitly set it to +nil+ in +PostsController+.
+
+* Deprecated <tt>ActionController::UnknownAction</tt> in favour of <tt>AbstractController::ActionNotFound</tt>.
+
+* Deprecated <tt>ActionController::DoubleRenderError</tt> in favour of <tt>AbstractController::DoubleRenderError</tt>.
+
+* Deprecated <tt>method_missing</tt> in favour of +action_missing+ for missing actions.
+
+* Deprecated <tt>ActionController#rescue_action</tt>, <tt>ActionController#initialize_template_class</tt> and <tt>ActionController#assign_shortcuts</tt>.
+
+h4. Action Dispatch
+
+* Add <tt>config.action_dispatch.default_charset</tt> to configure default charset for <tt>ActionDispatch::Response</tt>.
+
+* Added <tt>ActionDispatch::RequestId</tt> middleware that'll make a unique X-Request-Id header available to the response and enables the <tt>ActionDispatch::Request#uuid</tt> method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog.
+
+* The <tt>ShowExceptions</tt> middleware now accepts a exceptions application that is responsible to render an exception when the application fails. The application is invoked with a copy of the exception in +env["action_dispatch.exception"]+ and with the <tt>PATH_INFO</tt> rewritten to the status code.
+
+* Allow rescue responses to be configured through a railtie as in <tt>config.action_dispatch.rescue_responses</tt>.
+
+h5(#actiondispatch_deprecations). Deprecations
+
+* Deprecated the ability to set a default charset at the controller level, use the new <tt>config.action_dispatch.default_charset</tt> instead.
+
+h4. Action View
+
+* Add +button_tag+ support to <tt>ActionView::Helpers::FormBuilder</tt>. This support mimics the default behavior of +submit_tag+.
+
+<ruby>
+<%= form_for @post do |f| %>
+ <%= f.button %>
+<% end %>
+</ruby>
+
+* Date helpers accept a new option <tt>:use_two_digit_numbers => true</tt>, that renders select boxes for months and days with a leading zero without changing the respective values. For example, this is useful for displaying ISO 8601-style dates such as '2011-08-01'.
+
+* You can provide a namespace for your form to ensure uniqueness of id attributes on form elements. The namespace attribute will be prefixed with underscore on the generated HTML id.
+
+<ruby>
+<%= form_for(@offer, :namespace => 'namespace') do |f| %>
+ <%= f.label :version, 'Version' %>:
+ <%= f.text_field :version %>
+<% end %>
+</ruby>
+
+* Limit the number of options for +select_year+ to 1000. Pass +:max_years_allowed+ option to set your own limit.
+
+* +content_tag_for+ and +div_for+ can now take a collection of records. It will also yield the record as the first argument if you set a receiving argument in your block. So instead of having to do this:
+
+<ruby>
+@items.each do |item|
+ content_tag_for(:li, item) do
+ Title: <%= item.title %>
+ end
+end
+</ruby>
+
+You can do this:
+
+<ruby>
+content_tag_for(:li, @items) do |item|
+ Title: <%= item.title %>
+end
+</ruby>
+
+* Added +font_path+ helper method that computes the path to a font asset in <tt>public/fonts</tt>.
+
+h5(#actionview_deprecations). Deprecations
+
+* Passing formats or handlers to render :template and friends like <tt>render :template => "foo.html.erb"</tt> is deprecated. Instead, you can provide :handlers and :formats directly as options: <tt> render :template => "foo", :formats => [:html, :js], :handlers => :erb</tt>.
+
+h4. Sprockets
+
+* Adds a configuration option <tt>config.assets.logger</tt> to control Sprockets logging. Set it to +false+ to turn off logging and to +nil+ to default to +Rails.logger+.
+
+h3. Active Record
+
+* Boolean columns with 'on' and 'ON' values are type cast to true.
+
+* When the +timestamps+ method creates the +created_at+ and +updated_at+ columns, it makes them non-nullable by default.
+
+* Implemented <tt>ActiveRecord::Relation#explain</tt>.
+
+* Implements <tt>AR::Base.silence_auto_explain</tt> which allows the user to selectively disable automatic EXPLAINs within a block.
+
+* Implements automatic EXPLAIN logging for slow queries. A new configuration parameter +config.active_record.auto_explain_threshold_in_seconds+ determines what's to be considered a slow query. Setting that to nil disables this feature. Defaults are 0.5 in development mode, and nil in test and production modes. Rails 3.2 supports this feature in SQLite, MySQL (mysql2 adapter), and PostgreSQL.
+
+* Added <tt>ActiveRecord::Base.store</tt> for declaring simple single-column key/value stores.
+
+<ruby>
+class User < ActiveRecord::Base
+ store :settings, accessors: [ :color, :homepage ]
+end
+
+u = User.new(color: 'black', homepage: '37signals.com')
+u.color # Accessor stored attribute
+u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
+</ruby>
+
+* Added ability to run migrations only for a given scope, which allows to run migrations only from one engine (for example to revert changes from an engine that need to be removed).
+
+<ruby>
+rake db:migrate SCOPE=blog
+</ruby>
+
+* Migrations copied from engines are now scoped with engine's name, for example <tt>01_create_posts.blog.rb</tt>.
+
+* Implemented <tt>ActiveRecord::Relation#pluck</tt> method that returns an array of column values directly from the underlying table. This also works with serialized attributes.
+
+<ruby>
+Client.where(:active => true).pluck(:id)
+# SELECT id from clients where active = 1
+</ruby>
+
+* Generated association methods are created within a separate module to allow overriding and composition. For a class named MyModel, the module is named <tt>MyModel::GeneratedFeatureMethods</tt>. It is included into the model class immediately after the +generated_attributes_methods+ module defined in Active Model, so association methods override attribute methods of the same name.
+
+* Add <tt>ActiveRecord::Relation#uniq</tt> for generating unique queries.
+
+<ruby>
+Client.select('DISTINCT name')
+</ruby>
+
+..can be written as:
+
+<ruby>
+Client.select(:name).uniq
+</ruby>
+
+This also allows you to revert the uniqueness in a relation:
+
+<ruby>
+Client.select(:name).uniq.uniq(false)
+</ruby>
+
+* Support index sort order in SQLite, MySQL and PostgreSQL adapters.
+
+* Allow the +:class_name+ option for associations to take a symbol in addition to a string. This is to avoid confusing newbies, and to be consistent with the fact that other options like :foreign_key already allow a symbol or a string.
+
+<ruby>
+has_many :clients, :class_name => :Client # Note that the symbol need to be capitalized
+</ruby>
+
+* In development mode, <tt>db:drop</tt> also drops the test database in order to be symmetric with <tt>db:create</tt>.
+
+* Case-insensitive uniqueness validation avoids calling LOWER in MySQL when the column already uses a case-insensitive collation.
+
+* Transactional fixtures enlist all active database connections. You can test models on different connections without disabling transactional fixtures.
+
+* Add +first_or_create+, +first_or_create!+, +first_or_initialize+ methods to Active Record. This is a better approach over the old +find_or_create_by+ dynamic methods because it's clearer which arguments are used to find the record and which are used to create it.
+
+<ruby>
+User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson")
+</ruby>
+
+* Added a <tt>with_lock</tt> method to Active Record objects, which starts a transaction, locks the object (pessimistically) and yields to the block. The method takes one (optional) parameter and passes it to +lock!+.
+
+This makes it possible to write the following:
+
+<ruby>
+class Order < ActiveRecord::Base
+ def cancel!
+ transaction do
+ lock!
+ # ... cancelling logic
+ end
+ end
+end
+</ruby>
+
+as:
+
+<ruby>
+class Order < ActiveRecord::Base
+ def cancel!
+ with_lock do
+ # ... cancelling logic
+ end
+ end
+end
+</ruby>
+
+h4(#activerecord_deprecations). Deprecations
+
+* Automatic closure of connections in threads is deprecated. For example the following code is deprecated:
+
+<ruby>
+Thread.new { Post.find(1) }.join
+</ruby>
+
+It should be changed to close the database connection at the end of the thread:
+
+<ruby>
+Thread.new {
+ Post.find(1)
+ Post.connection.close
+}.join
+</ruby>
+
+Only people who spawn threads in their application code need to worry about this change.
+
+* The +set_table_name+, +set_inheritance_column+, +set_sequence_name+, +set_primary_key+, +set_locking_column+ methods are deprecated. Use an assignment method instead. For example, instead of +set_table_name+, use <tt>self.table_name=</tt>.
+
+<ruby>
+class Project < ActiveRecord::Base
+ self.table_name = "project"
+end
+</ruby>
+
+Or define your own <tt>self.table_name</tt> method:
+
+<ruby>
+class Post < ActiveRecord::Base
+ def self.table_name
+ "special_" + super
+ end
+end
+
+Post.table_name # => "special_posts"
+
+</ruby>
+
+h3. Active Model
+
+* Add <tt>ActiveModel::Errors#added?</tt> to check if a specific error has been added.
+
+* Add ability to define strict validations with <tt>strict => true</tt> that always raises exception when fails.
+
+* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior.
+
+h4(#activemodel_deprecations). Deprecations
+
+* Deprecated <tt>define_attr_method</tt> in <tt>ActiveModel::AttributeMethods</tt> because this only existed to support methods like +set_table_name+ in Active Record, which are themselves being deprecated.
+
+* Deprecated <tt>Model.model_name.partial_path</tt> in favor of <tt>model.to_partial_path</tt>.
+
+h3. Active Resource
+
+* Redirect responses: 303 See Other and 307 Temporary Redirect now behave like 301 Moved Permanently and 302 Found.
+
+h3. Active Support
+
+* Added <tt>ActiveSupport:TaggedLogging</tt> that can wrap any standard +Logger+ class to provide tagging capabilities.
+
+<ruby>
+Logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
+
+Logger.tagged("BCX") { Logger.info "Stuff" }
+# Logs "[BCX] Stuff"
+
+Logger.tagged("BCX", "Jason") { Logger.info "Stuff" }
+# Logs "[BCX] [Jason] Stuff"
+
+Logger.tagged("BCX") { Logger.tagged("Jason") { Logger.info "Stuff" } }
+# Logs "[BCX] [Jason] Stuff"
+</ruby>
+
+* The +beginning_of_week+ method in +Date+, +Time+ and +DateTime+ accepts an optional argument representing the day in which the week is assumed to start.
+
+* <tt>ActiveSupport::Notifications.subscribed</tt> provides subscriptions to events while a block runs.
+
+* Defined new methods <tt>Module#qualified_const_defined?</tt>, <tt>Module#qualified_const_get</tt> and <tt>Module#qualified_const_set</tt> that are analogous to the corresponding methods in the standard API, but accept qualified constant names.
+
+* Added +#deconstantize+ which complements +#demodulize+ in inflections. This removes the rightmost segment in a qualified constant name.
+
+* Added <tt>safe_constantize</tt> that constantizes a string but returns +nil+ instead of raising an exception if the constant (or part of it) does not exist.
+
+* <tt>ActiveSupport::OrderedHash</tt> is now marked as extractable when using <tt>Array#extract_options!</tt>.
+
+* Added <tt>Array#prepend</tt> as an alias for <tt>Array#unshift</tt> and <tt>Array#append</tt> as an alias for <tt>Array#<<</tt>.
+
+* The definition of a blank string for Ruby 1.9 has been extended to Unicode whitespace. Also, in Ruby 1.8 the ideographic space U+3000 is considered to be whitespace.
+
+* The inflector understands acronyms.
+
+* Added <tt>Time#all_day</tt>, <tt>Time#all_week</tt>, <tt>Time#all_quarter</tt> and <tt>Time#all_year</tt> as a way of generating ranges.
+
+<ruby>
+Event.where(:created_at => Time.now.all_week)
+Event.where(:created_at => Time.now.all_day)
+</ruby>
+
+* Added <tt>instance_accessor: false</tt> as an option to <tt>Class#cattr_accessor</tt> and friends.
+
+* <tt>ActiveSupport::OrderedHash</tt> now has different behavior for <tt>#each</tt> and <tt>#each_pair</tt> when given a block accepting its parameters with a splat.
+
+* Added <tt>ActiveSupport::Cache::NullStore</tt> for use in development and testing.
+
+* Removed <tt>ActiveSupport::SecureRandom</tt> in favor of <tt>SecureRandom</tt> from the standard library.
+
+h4(#activesupport_deprecations). Deprecations
+
+* +ActiveSupport::Base64+ is deprecated in favor of <tt>::Base64</tt>.
+
+* Deprecated <tt>ActiveSupport::Memoizable</tt> in favor of Ruby memoization pattern.
+
+* <tt>Module#synchronize</tt> is deprecated with no replacement. Please use monitor from ruby's standard library.
+
+* Deprecated <tt>ActiveSupport::MessageEncryptor#encrypt</tt> and <tt>ActiveSupport::MessageEncryptor#decrypt</tt>.
+
+* <tt>ActiveSupport::BufferedLogger#silence</tt> is deprecated. If you want to squelch logs for a certain block, change the log level for that block.
+
+* <tt>ActiveSupport::BufferedLogger#open_log</tt> is deprecated. This method should not have been public in the first place.
+
+* <tt>ActiveSupport::BufferedLogger's</tt> behavior of automatically creating the directory for your log file is deprecated. Please make sure to create the directory for your log file before instantiating.
+
+* <tt>ActiveSupport::BufferedLogger#auto_flushing</tt> is deprecated. Either set the sync level on the underlying file handle like this. Or tune your filesystem. The FS cache is now what controls flushing.
+
+<ruby>
+f = File.open('foo.log', 'w')
+f.sync = true
+ActiveSupport::BufferedLogger.new f
+</ruby>
+
+* <tt>ActiveSupport::BufferedLogger#flush</tt> is deprecated. Set sync on your filehandle, or tune your filesystem.
+
+h3. Credits
+
+See the "full list of contributors to Rails":http://contributors.rubyonrails.org/ for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them.
+
+Rails 3.2 Release Notes were compiled by "Vijay Dev":https://github.com/vijaydev.
diff --git a/guides/source/_license.html.erb b/guides/source/_license.html.erb
new file mode 100644
index 0000000000..00b4466f50
--- /dev/null
+++ b/guides/source/_license.html.erb
@@ -0,0 +1,2 @@
+<p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a> License</p>
+<p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p>
diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb
new file mode 100644
index 0000000000..9d2e9c1d68
--- /dev/null
+++ b/guides/source/_welcome.html.erb
@@ -0,0 +1,19 @@
+<h2>Ruby on Rails Guides (<%= @version %>)</h2>
+
+<% if @edge %>
+<p>
+ These are <b>Edge Guides</b>, based on the current <a href="https://github.com/rails/rails/tree/<%= @version %>">master</a> branch.
+</p>
+<p>
+ If you are looking for the ones for the stable version, please check
+ <a href="http://guides.rubyonrails.org">http://guides.rubyonrails.org</a> instead.
+</p>
+<% else %>
+<p>
+ These are the new guides for Rails 3.2 based on <a href="https://github.com/rails/rails/tree/<%= @version %>"><%= @version %></a>.
+ These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.
+</p>
+<% end %>
+<p>
+ The guides for Rails 2.3.x are available at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>.
+</p>
diff --git a/railties/guides/source/action_controller_overview.textile b/guides/source/action_controller_overview.textile
index 5019d49686..cc3350819b 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/guides/source/action_controller_overview.textile
@@ -16,7 +16,7 @@ h3. What Does a Controller Do?
Action Controller is the C in MVC. After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible.
-For most conventional RESTful applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that's not a problem, this is just the most common way for a controller to work.
+For most conventional "RESTful":http://en.wikipedia.org/wiki/Representational_state_transfer applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that's not a problem, this is just the most common way for a controller to work.
A controller can thus be thought of as a middle man between models and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates data from the user to the model.
@@ -148,28 +148,29 @@ In this case, when a user opens the URL +/clients/active+, +params[:status]+ wil
h4. +default_url_options+
-You can set global default parameters that will be used when generating URLs with +default_url_options+. To do this, define a method with that name in your controller:
+You can set global default parameters for URL generation by defining a method called +default_url_options+ in your controller. Such a method must return a hash with the desired defaults, whose keys must be symbols:
<ruby>
class ApplicationController < ActionController::Base
- # The options parameter is the hash passed in to 'url_for'
- def default_url_options(options)
+ def default_url_options
{:locale => I18n.locale}
end
end
</ruby>
-These options will be used as a starting-point when generating URLs, so it's possible they'll be overridden by +url_for+. Because this method is defined in the controller, you can define it on +ApplicationController+ so it would be used for all URL generation, or you could define it on only one controller for all URLs generated there.
+These options will be used as a starting point when generating URLs, so it's possible they'll be overridden by the options passed in +url_for+ calls.
+
+If you define +default_url_options+ in +ApplicationController+, as in the example above, it would be used for all URL generation. The method can also be defined in one specific controller, in which case it only affects URLs generated there.
h3. Session
Your application has a session for each user in which you can store small amounts of data that will be persisted between requests. The session is only available in the controller and the view and can use one of a number of different storage mechanisms:
-* CookieStore - Stores everything on the client.
-* DRbStore - Stores the data on a DRb server.
-* MemCacheStore - Stores the data in a memcache.
-* ActiveRecordStore - Stores the data in a database using Active Record.
+* ActionDispatch::Session::CookieStore - Stores everything on the client.
+* ActiveRecord::SessionStore - Stores the data in a database using Active Record.
+* ActionDispatch::Session::CacheStore - Stores the data in the Rails cache.
+* ActionDispatch::Session::MemCacheStore - Stores the data in a memcached cluster (this is a legacy implementation; consider using CacheStore instead).
All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure).
@@ -177,6 +178,8 @@ For most stores this ID is used to look up the session data on the server, e.g.
The CookieStore can store around 4kB of data -- much less than the others -- but this is usually enough. Storing large amounts of data in the session is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the most common example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error.
+If your user sessions don't store critical data or don't need to be around for long periods (for instance if you just use the flash for messaging), you can consider using ActionDispatch::Session::CacheStore. This will store sessions using the cache implementation you have configured for your application. The advantage of this is that you can use your existing cache infrastructure for storing sessions without requiring any additional setup or administration. The downside, of course, is that the sessions will be ephemeral and could disappear at any time.
+
Read more about session storage in the "Security Guide":security.html.
If you need a different session storage mechanism, you can change it in the +config/initializers/session_store.rb+ file:
@@ -561,7 +564,7 @@ The request object contains a lot of useful information about the request coming
|domain(n=2)|The hostname's first +n+ segments, starting from the right (the TLD).|
|format|The content type requested by the client.|
|method|The HTTP method used for the request.|
-|get?, post?, put?, delete?, head?|Returns true if the HTTP method is GET/POST/PUT/DELETE/HEAD.|
+|get?, post?, patch?, put?, delete?, head?|Returns true if the HTTP method is GET/POST/PATCH/PUT/DELETE/HEAD.|
|headers|Returns a hash containing the headers associated with the request.|
|port|The port number (integer) used for the request.|
|protocol|Returns a string containing the protocol used plus "://", for example "http://".|
diff --git a/railties/guides/source/action_mailer_basics.textile b/guides/source/action_mailer_basics.textile
index ad5b848d2c..ebe774fbef 100644
--- a/railties/guides/source/action_mailer_basics.textile
+++ b/guides/source/action_mailer_basics.textile
@@ -4,7 +4,7 @@ This guide should provide you with all you need to get started in sending and re
endprologue.
-WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
+WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not work in earlier versions of Rails.
h3. Introduction
@@ -244,7 +244,7 @@ It is possible to send email to one or more recipients in one email (for e.g. in
<ruby>
class AdminMailer < ActionMailer::Base
- default :to => Admin.all.map(&:email),
+ default :to => Proc.new { Admin.pluck(:email) },
:from => "notification@example.com"
def new_registration(user)
@@ -362,21 +362,14 @@ When using named routes you only need to supply the +:host+:
Email clients have no web context and so paths have no base URL to form complete web addresses. Thus, when using named routes only the "_url" variant makes sense.
-It is also possible to set a default host that will be used in all mailers by setting the +:host+ option in the +ActionMailer::Base.default_url_options+ hash as follows:
+It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt> option as a configuration option in <tt>config/application.rb</tt>:
<ruby>
-class UserMailer < ActionMailer::Base
- default_url_options[:host] = "example.com"
-
- def welcome_email(user)
- @user = user
- @url = user_url(@user)
- mail(:to => user.email,
- :subject => "Welcome to My Awesome Site")
- end
-end
+config.action_mailer.default_url_options = { :host => "example.com" }
</ruby>
+If you use this setting, you should pass the <tt>:only_path => false</tt> option when using +url_for+. This will ensure that absolute URLs are generated because the +url_for+ view helper will, by default, generate relative URLs when a <tt>:host</tt> option isn't explicitly provided.
+
h4. Sending Multipart Emails
Action Mailer will automatically send multipart emails if you have different templates for the same action. So, for our UserMailer example, if you have +welcome_email.text.erb+ and +welcome_email.html.erb+ in +app/views/user_mailer+, Action Mailer will automatically send a multipart email with the HTML and text versions setup as different parts.
diff --git a/railties/guides/source/action_view_overview.textile b/guides/source/action_view_overview.textile
index 40cde6ad84..bde30ba21c 100644
--- a/railties/guides/source/action_view_overview.textile
+++ b/guides/source/action_view_overview.textile
@@ -16,7 +16,7 @@ Action View and Action Controller are the two major components of Action Pack. I
Action View templates are written using embedded Ruby in tags mingled with HTML. To avoid cluttering the templates with boilerplate code, a number of helper classes provide common behavior for forms, dates, and strings. It's also easy to add new helpers to your application as it evolves.
-Note: Some features of Action View are tied to Active Record, but that doesn't mean that Action View depends on Active Record. Action View is an independent package that can be used with any sort of backend.
+NOTE. Some features of Action View are tied to Active Record, but that doesn't mean that Action View depends on Active Record. Action View is an independent package that can be used with any sort of backend.
h3. Using Action View with Rails
@@ -59,7 +59,6 @@ Now we'll create a simple "Hello World" application that uses the +titleize+ met
*hello_world.rb:*
<ruby>
-require 'rubygems'
require 'active_support/core_ext/string/inflections'
require 'rack'
@@ -94,7 +93,6 @@ Now we'll create the same "Hello World" application in Sinatra.
*hello_world.rb:*
<ruby>
-require 'rubygems'
require 'action_view'
require 'sinatra'
@@ -431,11 +429,11 @@ form("post")
<form action='/posts/create' method='post'>
<p>
<label for="post_title">Title</label><br />
- <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
+ <input id="post_title" name="post[title]" type="text" value="Hello World" />
</p>
<p>
<label for="post_body">Body</label><br />
- <textarea cols="40" id="post_body" name="post[body]" rows="20"></textarea>
+ <textarea id="post_body" name="post[body]"></textarea>
</p>
<input name="commit" type="submit" value="Create" />
</form>
@@ -451,7 +449,7 @@ For example, if +@post+ has an attribute +title+ mapped to a +String+ column tha
<ruby>
input("post", "title") # =>
- <input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />
+ <input id="post_title" name="post[title]" type="text" value="Hello World" />
</ruby>
h4. RecordTagHelper
@@ -535,10 +533,10 @@ h4. AssetTagHelper
This module provides methods for generating HTML that links views to assets such as images, JavaScript files, stylesheets, and feeds.
-By default, Rails links to these assets on the current host in the public folder, but you can direct Rails to link to assets from a dedicated assets server by setting +ActionController::Base.asset_host+ in the application configuration, typically in +config/environments/production.rb+. For example, let's say your asset host is +assets.example.com+:
+By default, Rails links to these assets on the current host in the public folder, but you can direct Rails to link to assets from a dedicated assets server by setting +config.action_controller.asset_host+ in the application configuration, typically in +config/environments/production.rb+. For example, let's say your asset host is +assets.example.com+:
<ruby>
-ActionController::Base.asset_host = "assets.example.com"
+config.action_controller.asset_host = "assets.example.com"
image_tag("rails.png") # => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
</ruby>
@@ -550,9 +548,9 @@ Register one or more JavaScript files to be included when symbol is passed to ja
ActionView::Helpers::AssetTagHelper.register_javascript_expansion :monkey => ["head", "body", "tail"]
javascript_include_tag :monkey # =>
- <script type="text/javascript" src="/javascripts/head.js"></script>
- <script type="text/javascript" src="/javascripts/body.js"></script>
- <script type="text/javascript" src="/javascripts/tail.js"></script>
+ <script src="/javascripts/head.js"></script>
+ <script src="/javascripts/body.js"></script>
+ <script src="/javascripts/tail.js"></script>
</ruby>
h5. register_stylesheet_expansion
@@ -563,14 +561,14 @@ Register one or more stylesheet files to be included when symbol is passed to +s
ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"]
stylesheet_link_tag :monkey # =>
- <link href="/stylesheets/head.css" media="screen" rel="stylesheet" type="text/css" />
- <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
- <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="/stylesheets/head.css" media="screen" rel="stylesheet" />
+ <link href="/stylesheets/body.css" media="screen" rel="stylesheet" />
+ <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" />
</ruby>
h5. auto_discovery_link_tag
-Returns a link tag that browsers and news readers can use to auto-detect an RSS or ATOM feed.
+Returns a link tag that browsers and news readers can use to auto-detect an RSS or Atom feed.
<ruby>
auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "RSS Feed"}) # =>
@@ -585,6 +583,14 @@ Computes the path to an image asset in the +public/images+ directory. Full paths
image_path("edit.png") # => /images/edit.png
</ruby>
+h5. image_url
+
+Computes the url to an image asset in the +public/images+ directory. This will call +image_path+ internally and merge with your current host or your asset host.
+
+<ruby>
+image_url("edit.png") # => http://www.example.com/images/edit.png
+</ruby>
+
h5. image_tag
Returns an html image tag for the source. The source can be a full path or a file that exists in your +public/images+ directory.
@@ -599,7 +605,7 @@ Returns an html script tag for each of the sources provided. You can pass in the
<ruby>
javascript_include_tag "common" # =>
- <script type="text/javascript" src="/javascripts/common.js"></script>
+ <script src="/javascripts/common.js"></script>
</ruby>
If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well.
@@ -618,7 +624,7 @@ You can also cache multiple JavaScript files into one file, which requires less
<ruby>
javascript_include_tag :all, :cache => true # =>
- <script type="text/javascript" src="/javascripts/all.js"></script>
+ <script src="/javascripts/all.js"></script>
</ruby>
h5. javascript_path
@@ -629,13 +635,21 @@ Computes the path to a JavaScript asset in the +public/javascripts+ directory. I
javascript_path "common" # => /javascripts/common.js
</ruby>
+h5. javascript_url
+
+Computes the url to a JavaScript asset in the +public/javascripts+ directory. This will call +javascript_path+ internally and merge with your current host or your asset host.
+
+<ruby>
+javascript_url "common" # => http://www.example.com/javascripts/common.js
+</ruby>
+
h5. stylesheet_link_tag
Returns a stylesheet link tag for the sources specified as arguments. If you don't specify an extension, +.css+ will be appended automatically.
<ruby>
stylesheet_link_tag "application" # =>
- <link href="/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="/stylesheets/application.css" media="screen" rel="stylesheet" />
</ruby>
You can also include all styles in the stylesheet directory using :all as the source:
@@ -648,7 +662,7 @@ You can also cache multiple stylesheets into one file, which requires less HTTP
<ruby>
stylesheet_link_tag :all, :cache => true
- <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
+ <link href="/stylesheets/all.css" media="screen" rel="stylesheet" />
</ruby>
h5. stylesheet_path
@@ -659,11 +673,19 @@ Computes the path to a stylesheet asset in the +public/stylesheets+ directory. I
stylesheet_path "application" # => /stylesheets/application.css
</ruby>
+h5. stylesheet_url
+
+Computes the url to a stylesheet asset in the +public/stylesheets+ directory. This will call +stylesheet_path+ internally and merge with your current host or your asset host.
+
+<ruby>
+stylesheet_url "application" # => http://www.example.com/stylesheets/application.css
+</ruby>
+
h4. AtomFeedHelper
h5. atom_feed
-This helper makes building an ATOM feed easy. Here's a full usage example:
+This helper makes building an Atom feed easy. Here's a full usage example:
*config/routes.rb*
@@ -781,7 +803,7 @@ For example, let's say we have a standard application layout, but also a special
<p>This is a special page.</p>
<% content_for :special_script do %>
- <script type="text/javascript">alert('Hello!')</script>
+ <script>alert('Hello!')</script>
<% end %>
</ruby>
@@ -809,7 +831,7 @@ Reports the approximate distance in time between two Time or Date objects or int
<ruby>
distance_of_time_in_words(Time.now, Time.now + 15.seconds) # => less than a minute
-distance_of_time_in_words(Time.now, Time.now + 15.seconds, true) # => less than 20 seconds
+distance_of_time_in_words(Time.now, Time.now + 15.seconds, :include_seconds => true) # => less than 20 seconds
</ruby>
h5. select_date
@@ -963,8 +985,8 @@ The HTML generated for this would be:
<html>
<form action="/persons/create" method="post">
- <input id="person_first_name" name="person[first_name]" size="30" type="text" />
- <input id="person_last_name" name="person[last_name]" size="30" type="text" />
+ <input id="person_first_name" name="person[first_name]" type="text" />
+ <input id="person_last_name" name="person[last_name]" type="text" />
<input name="commit" type="submit" value="Create" />
</form>
</html>
@@ -1124,6 +1146,79 @@ If <tt>@post.author_id</tt> is 1, this would return:
</select>
</html>
+h5. collection_radio_buttons
+
+Returns +radio_button+ tags for the collection of existing return values of +method+ for +object+'s class.
+
+Example object structure for use with this method:
+
+<ruby>
+class Post < ActiveRecord::Base
+ belongs_to :author
+end
+
+class Author < ActiveRecord::Base
+ has_many :posts
+ def name_with_initial
+ "#{first_name.first}. #{last_name}"
+ end
+end
+</ruby>
+
+Sample usage (selecting the associated Author for an instance of Post, +@post+):
+
+<ruby>
+collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial)
+</ruby>
+
+If <tt>@post.author_id</tt> is 1, this would return:
+
+<html>
+<input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" />
+<label for="post_author_id_1">D. Heinemeier Hansson</label>
+<input id="post_author_id_2" name="post[author_id]" type="radio" value="2" />
+<label for="post_author_id_2">D. Thomas</label>
+<input id="post_author_id_3" name="post[author_id]" type="radio" value="3" />
+<label for="post_author_id_3">M. Clark</label>
+</html>
+
+h5. collection_check_boxes
+
+Returns +check_box+ tags for the collection of existing return values of +method+ for +object+'s class.
+
+Example object structure for use with this method:
+
+<ruby>
+class Post < ActiveRecord::Base
+ has_and_belongs_to_many :author
+end
+
+class Author < ActiveRecord::Base
+ has_and_belongs_to_many :posts
+ def name_with_initial
+ "#{first_name.first}. #{last_name}"
+ end
+end
+</ruby>
+
+Sample usage (selecting the associated Authors for an instance of Post, +@post+):
+
+<ruby>
+collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial)
+</ruby>
+
+If <tt>@post.author_ids</tt> is [1], this would return:
+
+<html>
+<input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" />
+<label for="post_author_ids_1">D. Heinemeier Hansson</label>
+<input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" />
+<label for="post_author_ids_2">D. Thomas</label>
+<input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" />
+<label for="post_author_ids_3">M. Clark</label>
+<input name="post[author_ids][]" type="hidden" value="" />
+</html>
+
h5. country_options_for_select
Returns a string of option tags for pretty much any country in the world.
@@ -1404,7 +1499,7 @@ javascript_tag "alert('All is good')"
</ruby>
<html>
-<script type="text/javascript">
+<script>
//<![CDATA[
alert('All is good')
//]]>
diff --git a/railties/guides/source/active_model_basics.textile b/guides/source/active_model_basics.textile
index 9c8ad24cee..d373f4ac85 100644
--- a/railties/guides/source/active_model_basics.textile
+++ b/guides/source/active_model_basics.textile
@@ -20,7 +20,7 @@ class Person
attribute_method_prefix 'reset_'
attribute_method_suffix '_highest?'
- define_attribute_methods ['age']
+ define_attribute_methods 'age'
attr_accessor :age
@@ -56,7 +56,7 @@ class Person
before_update :reset_me
def update
- _run_update_callbacks do
+ run_callbacks(:update) do
# This will call when we are trying to call update on object.
end
end
@@ -95,12 +95,11 @@ h4. Dirty
An object becomes dirty when an object is gone through one or more changes to its attributes and not yet saved. This gives the ability to check whether an object has been changed or not. It also has attribute based accessor methods. Lets consider a Person class with attributes first_name and last_name
<ruby>
-require 'rubygems'
require 'active_model'
class Person
include ActiveModel::Dirty
- define_attribute_methods [:first_name, :last_name]
+ define_attribute_methods :first_name, :last_name
def first_name
@first_name
diff --git a/railties/guides/source/active_record_basics.textile b/guides/source/active_record_basics.textile
index 66ad7b0255..487f8b70f9 100644
--- a/railties/guides/source/active_record_basics.textile
+++ b/guides/source/active_record_basics.textile
@@ -101,11 +101,11 @@ h3. Overriding the Naming Conventions
What if you need to follow a different naming convention or need to use your Rails application with a legacy database? No problem, you can easily override the default conventions.
-You can use the +ActiveRecord::Base.set_table_name+ method to specify the table name that should be used:
+You can use the +ActiveRecord::Base.table_name=+ method to specify the table name that should be used:
<ruby>
class Product < ActiveRecord::Base
- set_table_name "PRODUCT"
+ self.table_name = "PRODUCT"
end
</ruby>
diff --git a/railties/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile
index 2e1f89cb78..294ef25b33 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/guides/source/active_record_querying.textile
@@ -8,6 +8,7 @@ This guide covers different ways to retrieve data from the database using Active
* Use dynamic finders methods
* Check for the existence of particular records
* Perform various calculations on Active Record models
+* Run EXPLAIN on relations
endprologue.
@@ -93,14 +94,33 @@ client = Client.find(10)
The SQL equivalent of the above is:
<sql>
-SELECT * FROM clients WHERE (clients.id = 10)
+SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1
</sql>
<tt>Model.find(primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception if no matching record is found.
+h5. +take+
+
+<tt>Model.take</tt> retrieves a record without any implicit ordering. For example:
+
+<ruby>
+client = Client.take
+# => #<Client id: 1, first_name: "Lifo">
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 1
+</sql>
+
+<tt>Model.take</tt> returns +nil+ if no record is found and no exception will be raised.
+
+TIP: The retrieved record may vary depending on the database engine.
+
h5. +first+
-<tt>Model.first</tt> finds the first record matched by the supplied options, if any. For example:
+<tt>Model.first</tt> finds the first record ordered by the primary key. For example:
<ruby>
client = Client.first
@@ -110,14 +130,14 @@ client = Client.first
The SQL equivalent of the above is:
<sql>
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
</sql>
-<tt>Model.first</tt> returns +nil+ if no matching record is found. No exception will be raised.
+<tt>Model.first</tt> returns +nil+ if no matching record is found and no exception will be raised.
h5. +last+
-<tt>Model.last</tt> finds the last record matched by the supplied options. For example:
+<tt>Model.last</tt> finds the last record ordered by the primary key. For example:
<ruby>
client = Client.last
@@ -130,11 +150,46 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
</sql>
-<tt>Model.last</tt> returns +nil+ if no matching record is found. No exception will be raised.
+<tt>Model.last</tt> returns +nil+ if no matching record is found and no exception will be raised.
+
+h5. +find_by+
+
+<tt>Model.find_by</tt> finds the first record matching some conditions. For example:
+
+<ruby>
+Client.find_by first_name: 'Lifo'
+# => #<Client id: 1, first_name: "Lifo">
+
+Client.find_by first_name: 'Jon'
+# => nil
+</ruby>
+
+It is equivalent to writing:
+
+<ruby>
+Client.where(first_name: 'Lifo').take
+</ruby>
+
+h5(#take_1). +take!+
+
+<tt>Model.take!</tt> retrieves a record without any implicit ordering. For example:
+
+<ruby>
+client = Client.take!
+# => #<Client id: 1, first_name: "Lifo">
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 1
+</sql>
+
+<tt>Model.take!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
h5(#first_1). +first!+
-<tt>Model.first!</tt> finds the first record. For example:
+<tt>Model.first!</tt> finds the first record ordered by the primary key. For example:
<ruby>
client = Client.first!
@@ -144,14 +199,14 @@ client = Client.first!
The SQL equivalent of the above is:
<sql>
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
</sql>
-<tt>Model.first!</tt> raises +RecordNotFound+ if no matching record is found.
+<tt>Model.first!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
h5(#last_1). +last!+
-<tt>Model.last!</tt> finds the last record. For example:
+<tt>Model.last!</tt> finds the last record ordered by the primary key. For example:
<ruby>
client = Client.last!
@@ -164,7 +219,25 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
</sql>
-<tt>Model.last!</tt> raises +RecordNotFound+ if no matching record is found.
+<tt>Model.last!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
+
+h5(#find_by_1). +find_by!+
+
+<tt>Model.find_by!</tt> finds the first record matching some conditions. It raises +ActiveRecord::RecordNotFound+ if no matching record is found. For example:
+
+<ruby>
+Client.find_by! first_name: 'Lifo'
+# => #<Client id: 1, first_name: "Lifo">
+
+Client.find_by! first_name: 'Jon'
+# => ActiveRecord::RecordNotFound
+</ruby>
+
+It is equivalent to writing:
+
+<ruby>
+Client.where(first_name: 'Lifo').take!
+</ruby>
h4. Retrieving Multiple Objects
@@ -201,7 +274,7 @@ end
But this approach becomes increasingly impractical as the table size increases, since +User.all.each+ instructs Active Record to fetch _the entire table_ in a single pass, build a model object per row, and then keep the entire array of model objects in memory. Indeed, if we have a large number of records, the entire collection may exceed the amount of memory available.
-Rails provides two methods that address this problem by dividing records into memory-friendly batches for processing. The first method, +find_each+, retrieves a batch of records and then yields _each_ record to the block individually as a model. The second method, +find_in_batches+, retrieves a batch of records and then yields _the entire batch_ to the block as an array of models.
+Rails provides two methods that address this problem by dividing records into memory-friendly batches for processing. The first method, +find_each+, retrieves a batch of records and then yields _each_ record to the block individually as a model. The second method, +find_in_batches+, retrieves a batch of records and then yields _the entire batch_ to the block as an array of models.
TIP: The +find_each+ and +find_in_batches+ methods are intended for use in the batch processing of a large number of records that wouldn't fit in memory all at once. If you just need to loop over a thousand records the regular find methods are the preferred option.
@@ -319,20 +392,6 @@ Client.where("created_at >= :start_date AND created_at <= :end_date",
This makes for clearer readability if you have a large number of variable conditions.
-h5(#array-range_conditions). Range Conditions
-
-If you're looking for a range inside of a table (for example, users created in a certain timeframe) you can use the conditions option coupled with the +IN+ SQL statement for this. If you had two dates coming in from a controller you could do something like this to look for a range:
-
-<ruby>
-Client.where(:created_at => (params[:start_date].to_date)..(params[:end_date].to_date))
-</ruby>
-
-This query will generate something similar to the following SQL:
-
-<sql>
- SELECT "clients".* FROM "clients" WHERE ("clients"."created_at" BETWEEN '2010-09-29' AND '2010-11-30')
-</sql>
-
h4. Hash Conditions
Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them:
@@ -351,9 +410,9 @@ The field name can also be a string:
Client.where('locked' => true)
</ruby>
-h5(#hash-range_conditions). Range Conditions
+NOTE: The values cannot be symbols. For example, you cannot do +Client.where(:status => :active)+.
-The good thing about this is that we can pass in a range for our fields without it generating a large query as shown in the preamble of this section.
+h5(#hash-range_conditions). Range Conditions
<ruby>
Client.where(:created_at => (Time.now.midnight - 1.day)..Time.now.midnight)
@@ -403,6 +462,8 @@ Or ordering by multiple fields:
<ruby>
Client.order("orders_count ASC, created_at DESC")
+# OR
+Client.order("orders_count ASC", "created_at DESC")
</ruby>
h3. Selecting Specific Fields
@@ -435,10 +496,26 @@ ActiveModel::MissingAttributeError: missing attribute: <attribute>
Where +&lt;attribute&gt;+ is the attribute you asked for. The +id+ method will not raise the +ActiveRecord::MissingAttributeError+, so just be careful when working with associations because they need the +id+ method to function properly.
-You can also call SQL functions within the select option. For example, if you would like to only grab a single record per unique value in a certain field by using the +DISTINCT+ function you can do it like this:
+If you would like to only grab a single record per unique value in a certain field, you can use +uniq+:
+
+<ruby>
+Client.select(:name).uniq
+</ruby>
+
+This would generate SQL like:
+
+<sql>
+SELECT DISTINCT name FROM clients
+</sql>
+
+You can also remove the uniqueness constraint:
<ruby>
-Client.select("DISTINCT(name)")
+query = Client.select(:name).uniq
+# => Returns unique names
+
+query.uniq(false)
+# => Returns all names, even if there are duplicates
</ruby>
h3. Limit and Offset
@@ -484,7 +561,9 @@ And this will give you a single +Order+ object for each date where there are ord
The SQL that would be executed would be something like this:
<sql>
-SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at)
+SELECT date(created_at) as ordered_date, sum(price) as total_price
+FROM orders
+GROUP BY date(created_at)
</sql>
h3. Having
@@ -500,7 +579,10 @@ Order.select("date(created_at) as ordered_date, sum(price) as total_price").grou
The SQL that would be executed would be something like this:
<sql>
-SELECT date(created_at) as ordered_date, sum(price) as total_price FROM orders GROUP BY date(created_at) HAVING sum(price) > 100
+SELECT date(created_at) as ordered_date, sum(price) as total_price
+FROM orders
+GROUP BY date(created_at)
+HAVING sum(price) > 100
</sql>
This will return single order objects for each day, but only those that are ordered more than $100 in a day.
@@ -589,9 +671,33 @@ SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC
This method accepts *no* arguments.
+h3. Null Relation
+
+The +none+ method returns a chainable relation with no records. Any subsequent conditions chained to the returned relation will continue generating empty relations. This is useful in scenarios where you need a chainable response to a method or a scope that could return zero results.
+
+<ruby>
+Post.none # returns an empty Relation and fires no queries.
+</ruby>
+
+<ruby>
+# The visible_posts method below is expected to return a Relation.
+@posts = current_user.visible_posts.where(:name => params[:name])
+
+def visible_posts
+ case role
+ when 'Country Manager'
+ Post.where(:country => country)
+ when 'Reviewer'
+ Post.published
+ when 'Bad User'
+ Post.none # => returning [] or nil breaks the caller code in this case
+ end
+end
+</ruby>
+
h3. Readonly Objects
-Active Record provides +readonly+ method on a relation to explicitly disallow modification or deletion of any of the returned object. Any attempt to alter or destroy a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception.
+Active Record provides +readonly+ method on a relation to explicitly disallow modification of any of the returned objects. Any attempt to alter a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception.
<ruby>
client = Client.readonly.first
@@ -616,7 +722,7 @@ Optimistic locking allows multiple users to access the same record for edits, an
<strong>Optimistic locking column</strong>
-In order to use optimistic locking, the table needs to have a column called +lock_version+. Each time the record is updated, Active Record increments the +lock_version+ column. If an update request is made with a lower value in the +lock_version+ field than is currently in the +lock_version+ column in the database, the update request will fail with an +ActiveRecord::StaleObjectError+. Example:
+In order to use optimistic locking, the table needs to have a column called +lock_version+ of type integer. Each time the record is updated, Active Record increments the +lock_version+ column. If an update request is made with a lower value in the +lock_version+ field than is currently in the +lock_version+ column in the database, the update request will fail with an +ActiveRecord::StaleObjectError+. Example:
<ruby>
c1 = Client.find(1)
@@ -631,15 +737,13 @@ c2.save # Raises an ActiveRecord::StaleObjectError
You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, or otherwise apply the business logic needed to resolve the conflict.
-NOTE: You must ensure that your database schema defaults the +lock_version+ column to +0+.
-
This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
-To override the name of the +lock_version+ column, +ActiveRecord::Base+ provides a class method called +set_locking_column+:
+To override the name of the +lock_version+ column, +ActiveRecord::Base+ provides a class attribute called +locking_column+:
<ruby>
class Client < ActiveRecord::Base
- set_locking_column :lock_client_column
+ self.locking_column = :lock_client_column
end
</ruby>
@@ -675,6 +779,17 @@ Item.transaction do
end
</ruby>
+If you already have an instance of your model, you can start a transaction and acquire the lock in one go using the following code:
+
+<ruby>
+item = Item.first
+item.with_lock do
+ # This block is called within a transaction,
+ # item is already locked.
+ item.increment!(:views)
+end
+</ruby>
+
h3. Joining Tables
Active Record provides a finder method called +joins+ for specifying +JOIN+ clauses on the resulting SQL. There are multiple ways to use the +joins+ method.
@@ -741,7 +856,7 @@ SELECT categories.* FROM categories
INNER JOIN posts ON posts.category_id = categories.id
</sql>
-Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use Category.joins(:post).select("distinct(categories.id)").
+Or, in English: "return a Category object for all categories with posts". Note that you will see duplicate categories if more than one post has the same category. If you want unique categories, you can use Category.joins(:posts).select("distinct(categories.id)").
h5. Joining Multiple Associations
@@ -831,7 +946,7 @@ This code looks fine at the first sight. But the problem lies within the total n
Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the +includes+ method of the +Model.find+ call. With +includes+, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries.
-Revisiting the above case, we could rewrite +Client.all+ to use eager load addresses:
+Revisiting the above case, we could rewrite +Client.limit(10)+ to use eager load addresses:
<ruby>
clients = Client.includes(:address).limit(10)
@@ -891,21 +1006,23 @@ If, in the case of this +includes+ query, there were no comments for any posts,
h3. Scopes
-Scoping allows you to specify commonly-used ARel queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as +where+, +joins+ and +includes+. All scope methods will return an +ActiveRecord::Relation+ object which will allow for further methods (such as other scopes) to be called on it.
+Scoping allows you to specify commonly-used queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as +where+, +joins+ and +includes+. All scope methods will return an +ActiveRecord::Relation+ object which will allow for further methods (such as other scopes) to be called on it.
-To define a simple scope, we use the +scope+ method inside the class, passing the ARel query that we'd like run when this scope is called:
+To define a simple scope, we use the +scope+ method inside the class, passing the query that we'd like run when this scope is called:
<ruby>
class Post < ActiveRecord::Base
- scope :published, where(:published => true)
+ scope :published, -> { where(published: true) }
end
</ruby>
-Just like before, these methods are also chainable:
+This is exactly the same as defining a class method, and which you use is a matter of personal preference:
<ruby>
class Post < ActiveRecord::Base
- scope :published, where(:published => true).joins(:category)
+ def self.published
+ where(published: true)
+ end
end
</ruby>
@@ -913,8 +1030,8 @@ Scopes are also chainable within scopes:
<ruby>
class Post < ActiveRecord::Base
- scope :published, where(:published => true)
- scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0))
+ scope :published, -> { where(:published => true) }
+ scope :published_and_commented, -> { published.where("comments_count > 0") }
end
</ruby>
@@ -931,39 +1048,27 @@ category = Category.first
category.posts.published # => [published posts belonging to this category]
</ruby>
-h4. Working with times
-
-If you're working with dates or times within scopes, due to how they are evaluated, you will need to use a lambda so that the scope is evaluated every time.
-
-<ruby>
-class Post < ActiveRecord::Base
- scope :last_week, lambda { where("created_at < ?", Time.zone.now ) }
-end
-</ruby>
-
-Without the +lambda+, this +Time.zone.now+ will only be called once.
-
h4. Passing in arguments
-When a +lambda+ is used for a +scope+, it can take arguments:
+Your scope can take arguments:
<ruby>
class Post < ActiveRecord::Base
- scope :1_week_before, lambda { |time| where("created_at < ?", time)
+ scope :created_before, ->(time) { where("created_at < ?", time) }
end
</ruby>
This may then be called using this:
<ruby>
-Post.1_week_before(Time.zone.now)
+Post.created_before(Time.zone.now)
</ruby>
However, this is just duplicating the functionality that would be provided to you by a class method.
<ruby>
class Post < ActiveRecord::Base
- def self.1_week_before(time)
+ def self.created_before(time)
where("created_at < ?", time)
end
end
@@ -972,7 +1077,7 @@ end
Using a class method is the preferred way to accept arguments for scopes. These methods will still be accessible on the association objects:
<ruby>
-category.posts.1_week_before(time)
+category.posts.created_before(time)
</ruby>
h4. Working with scopes
@@ -996,7 +1101,7 @@ If we wish for a scope to be applied across all queries to the model we can use
<ruby>
class Client < ActiveRecord::Base
- default_scope where("removed_at IS NULL")
+ default_scope { where("removed_at IS NULL") }
end
</ruby>
@@ -1081,6 +1186,8 @@ Client.where(:first_name => 'Andy').first_or_create!(:locked => false)
# => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank
</ruby>
+As with +first_or_create+ there is a +find_or_create_by!+ method but the +first_or_create!+ method is preferred for clarity.
+
h4. +first_or_initialize+
The +first_or_initialize+ method will work just like +first_or_create+ but it will not call +create+ but +new+. This means that a new model instance will be created in memory but won't be saved to the database. Continuing with the +first_or_create+ example, we now want the client named 'Nick':
@@ -1129,6 +1236,48 @@ h3. +select_all+
Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")
</ruby>
+h3. +pluck+
+
+<tt>pluck</tt> can be used to query a single column from the underlying table of a model. It accepts a column name as argument and returns an array of values of the specified column with the corresponding data type.
+
+<ruby>
+Client.where(:active => true).pluck(:id)
+# SELECT id FROM clients WHERE active = 1
+
+Client.uniq.pluck(:role)
+# SELECT DISTINCT role FROM clients
+</ruby>
+
++pluck+ makes it possible to replace code like
+
+<ruby>
+Client.select(:id).map { |c| c.id }
+</ruby>
+
+with
+
+<ruby>
+Client.pluck(:id)
+</ruby>
+
+h3. +ids+
+
++ids+ can be used to pluck all the IDs for the relation using the table's primary key.
+
+<ruby>
+Person.ids
+# SELECT id FROM people
+</ruby>
+
+<ruby>
+class Person < ActiveRecord::Base
+ self.primary_key = "person_id"
+end
+
+Person.ids
+# SELECT person_id FROM people
+</ruby>
+
h3. Existence of Objects
If you simply want to check for the existence of the object there's a method called +exists?+. This method will query the database using the same query as +find+, but instead of returning an object or collection of objects it will return either +true+ or +false+.
@@ -1258,3 +1407,122 @@ Client.sum("orders_count")
</ruby>
For options, please see the parent section, "Calculations":#calculations.
+
+h3. Running EXPLAIN
+
+You can run EXPLAIN on the queries triggered by relations. For example,
+
+<ruby>
+User.where(:id => 1).joins(:posts).explain
+</ruby>
+
+may yield
+
+<plain>
+EXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id` = 1
+<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------------<plus>
+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------------<plus>
+| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+| 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------------<plus>
+2 rows in set (0.00 sec)
+</plain>
+
+under MySQL.
+
+Active Record performs a pretty printing that emulates the one of the database
+shells. So, the same query running with the PostgreSQL adapter would yield instead
+
+<plain>
+EXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1
+ QUERY PLAN
+------------------------------------------------------------------------------
+ Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0)
+ Join Filter: (posts.user_id = users.id)
+ -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4)
+ Index Cond: (id = 1)
+ -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4)
+ Filter: (posts.user_id = 1)
+(6 rows)
+</plain>
+
+Eager loading may trigger more than one query under the hood, and some queries
+may need the results of previous ones. Because of that, +explain+ actually
+executes the query, and then asks for the query plans. For example,
+
+<ruby>
+User.where(:id => 1).includes(:posts).explain
+</ruby>
+
+yields
+
+<plain>
+EXPLAIN for: SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
+<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------<plus>
+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------<plus>
+| 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+<plus>----<plus>-------------<plus>-------<plus>-------<plus>---------------<plus>---------<plus>---------<plus>-------<plus>------<plus>-------<plus>
+1 row in set (0.00 sec)
+
+EXPLAIN for: SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1)
+<plus>----<plus>-------------<plus>-------<plus>------<plus>---------------<plus>------<plus>---------<plus>------<plus>------<plus>-------------<plus>
+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+<plus>----<plus>-------------<plus>-------<plus>------<plus>---------------<plus>------<plus>---------<plus>------<plus>------<plus>-------------<plus>
+| 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
+<plus>----<plus>-------------<plus>-------<plus>------<plus>---------------<plus>------<plus>---------<plus>------<plus>------<plus>-------------<plus>
+1 row in set (0.00 sec)
+</plain>
+
+under MySQL.
+
+h4. Automatic EXPLAIN
+
+Active Record is able to run EXPLAIN automatically on slow queries and log its
+output. This feature is controlled by the configuration parameter
+
+<ruby>
+config.active_record.auto_explain_threshold_in_seconds
+</ruby>
+
+If set to a number, any query exceeding those many seconds will have its EXPLAIN
+automatically triggered and logged. In the case of relations, the threshold is
+compared to the total time needed to fetch records. So, a relation is seen as a
+unit of work, no matter whether the implementation of eager loading involves
+several queries under the hood.
+
+A threshold of +nil+ disables automatic EXPLAINs.
+
+The default threshold in development mode is 0.5 seconds, and +nil+ in test and
+production modes.
+
+INFO. Automatic EXPLAIN gets disabled if Active Record has no logger, regardless
+of the value of the threshold.
+
+h5. Disabling Automatic EXPLAIN
+
+Automatic EXPLAIN can be selectively silenced with +ActiveRecord::Base.silence_auto_explain+:
+
+<ruby>
+ActiveRecord::Base.silence_auto_explain do
+ # no automatic EXPLAIN is triggered here
+end
+</ruby>
+
+That may be useful for queries you know are slow but fine, like a heavyweight
+report of an admin interface.
+
+As its name suggests, +silence_auto_explain+ only silences automatic EXPLAINs.
+Explicit calls to +ActiveRecord::Relation#explain+ run.
+
+h4. Interpreting EXPLAIN
+
+Interpretation of the output of EXPLAIN is beyond the scope of this guide. The
+following pointers may be helpful:
+
+* SQLite3: "EXPLAIN QUERY PLAN":http://www.sqlite.org/eqp.html
+
+* MySQL: "EXPLAIN Output Format":http://dev.mysql.com/doc/refman/5.6/en/explain-output.html
+
+* PostgreSQL: "Using EXPLAIN":http://www.postgresql.org/docs/current/static/using-explain.html
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/guides/source/active_record_validations_callbacks.textile
index 665e7f9ccc..f49d91fd3c 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/guides/source/active_record_validations_callbacks.textile
@@ -517,9 +517,21 @@ class Person < ActiveRecord::Base
end
</ruby>
+h3. Strict Validations
+
+You can also specify validations to be strict and raise +ActiveModel::StrictValidationFailed+ when the object is invalid.
+
+<ruby>
+class Person < ActiveRecord::Base
+ validates :name, :presence => { :strict => true }
+end
+
+Person.new.valid? => ActiveModel::StrictValidationFailed: Name can't be blank
+</ruby>
+
h3. Conditional Validation
-Sometimes it will make sense to validate an object just when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string or a +Proc+. You may use the +:if+ option when you want to specify when the validation *should* happen. If you want to specify when the validation *should not* happen, then you may use the +:unless+ option.
+Sometimes it will make sense to validate an object just when a given predicate is satisfied. You can do that by using the +:if+ and +:unless+ options, which can take a symbol, a string, a +Proc+ or an +Array+. You may use the +:if+ option when you want to specify when the validation *should* happen. If you want to specify when the validation *should not* happen, then you may use the +:unless+ option.
h4. Using a Symbol with +:if+ and +:unless+
@@ -571,6 +583,20 @@ end
All validations inside of +with_options+ block will have automatically passed the condition +:if => :is_admin?+
+h4. Combining validation conditions
+
+On the other hand, when multiple conditions define whether or not a validation should happen, an +Array+ can be used. Moreover, you can apply both +:if:+ and +:unless+ to the same validation.
+
+<ruby>
+class Computer < ActiveRecord::Base
+ validates :mouse, :presence => true,
+ :if => ["market.retail?", :desktop?]
+ :unless => Proc.new { |c| c.trackpad.present? }
+end
+</ruby>
+
+The validation only runs when all the +:if+ conditions and none of the +:unless+ conditions are evaluated to +true+.
+
h3. Performing Custom Validations
When the built-in validation helpers are not enough for your needs, you can write your own validators or validation methods as you prefer.
@@ -614,7 +640,7 @@ As shown in the example, you can also combine standard validations with your own
h4. Custom Methods
-You can also create methods that verify the state of your models and add messages to the +errors+ collection when they are invalid. You must then register these methods by using one or more of the +validate+, +validate_on_create+ or +validate_on_update+ class methods, passing in the symbols for the validation methods' names.
+You can also create methods that verify the state of your models and add messages to the +errors+ collection when they are invalid. You must then register these methods by using the +validate+ class method, passing in the symbols for the validation methods' names.
You can pass more than one symbol for each class method and the respective validations will be run in the same order as they were registered.
@@ -637,12 +663,24 @@ class Invoice < ActiveRecord::Base
end
</ruby>
+By default such validations will run every time you call +valid?+. It is also possible to control when to run these custom validations by giving an +:on+ option to the +validate+ method, with either: +:create+ or +:update+.
+
+<ruby>
+class Invoice < ActiveRecord::Base
+ validate :active_customer, :on => :create
+
+ def active_customer
+ errors.add(:customer_id, "is not active") unless customer.active?
+ end
+end
+</ruby>
+
You can even create your own validation helpers and reuse them in several different models. For example, an application that manages surveys may find it useful to express that a certain field corresponds to a set of choices:
<ruby>
ActiveRecord::Base.class_eval do
def self.validates_as_choice(attr_name, n, options={})
- validates attr_name, :inclusion => { {:in => 1..n}.merge(options) }
+ validates attr_name, :inclusion => { { :in => 1..n }.merge!(options) }
end
end
</ruby>
@@ -659,11 +697,11 @@ h3. Working with Validation Errors
In addition to the +valid?+ and +invalid?+ methods covered earlier, Rails provides a number of methods for working with the +errors+ collection and inquiring about the validity of objects.
-The following is a list of the most commonly used methods. Please refer to the +ActiveRecord::Errors+ documentation for a list of all the available methods.
+The following is a list of the most commonly used methods. Please refer to the +ActiveModel::Errors+ documentation for a list of all the available methods.
h4(#working_with_validation_errors-errors). +errors+
-Returns an instance of the class +ActiveModel::Errors+ (which behaves like an ordered hash) containing all errors. Each key is the attribute name and the value is an array of strings with all errors.
+Returns an instance of the class +ActiveModel::Errors+ containing all errors. Each key is the attribute name and the value is an array of strings with all errors.
<ruby>
class Person < ActiveRecord::Base
@@ -796,17 +834,9 @@ person.errors.size # => 0
h3. Displaying Validation Errors in the View
-Rails maintains an official plugin, DynamicForm, that provides helpers to display the error messages of your models in your view templates. You can install it as a plugin or as a Gem.
-
-h4. Installing as a plugin
+"DynamicForm":https://github.com/joelmoss/dynamic_form provides helpers to display the error messages of your models in your view templates.
-<shell>
-$ rails plugin install git://github.com/joelmoss/dynamic_form.git
-</shell>
-
-h4. Installing as a Gem
-
-Add this line in your Gemfile:
+You can install it as a gem by adding this line to your Gemfile:
<ruby>
gem "dynamic_form"
@@ -897,13 +927,8 @@ Below is a simple example where we change the Rails behavior to always display t
<ruby>
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
- if instance.error_message.kind_of?(Array)
- %(#{html_tag}<span class="validation-error">&nbsp;
- #{instance.error_message.join(',')}</span>).html_safe
- else
- %(#{html_tag}<span class="validation-error">&nbsp;
- #{instance.error_message}</span>).html_safe
- end
+ errors = Array(instance.error_message).join(',')
+ %(#{html_tag}<span class="validation-error">&nbsp;#{errors}</span>).html_safe
end
</ruby>
@@ -957,6 +982,7 @@ h4. Creating an Object
* +before_validation+
* +after_validation+
* +before_save+
+* +around_save+
* +before_create+
* +around_create+
* +after_create+
@@ -967,6 +993,7 @@ h4. Updating an Object
* +before_validation+
* +after_validation+
* +before_save+
+* +around_save+
* +before_update+
* +around_update+
* +after_update+
@@ -975,8 +1002,8 @@ h4. Updating an Object
h4. Destroying an Object
* +before_destroy+
-* +after_destroy+
* +around_destroy+
+* +after_destroy+
WARNING. +after_save+ runs both on create and update, but always _after_ the more specific callbacks +after_create+ and +after_update+, no matter the order in which the macro calls were executed.
@@ -986,15 +1013,15 @@ The +after_initialize+ callback will be called whenever an Active Record object
The +after_find+ callback will be called whenever Active Record loads a record from the database. +after_find+ is called before +after_initialize+ if both are defined.
-The +after_initialize+ and +after_find+ callbacks are a bit different from the others. They have no +before_*+ counterparts, and they are registered simply by defining them as regular methods with predefined names. If you try to register +after_initialize+ or +after_find+ using macro-style class methods, they will just be ignored. This behavior is due to performance reasons, since +after_initialize+ and +after_find+ will both be called for each record found in the database, which would otherwise significantly slow down the queries.
+The +after_initialize+ and +after_find+ callbacks have no +before_*+ counterparts, but they can be registered just like the other Active Record callbacks.
<ruby>
class User < ActiveRecord::Base
- def after_initialize
+ after_initialize do |user|
puts "You have initialized an object!"
end
- def after_find
+ after_find do |user|
puts "You have found an object!"
end
end
@@ -1021,7 +1048,7 @@ The following methods trigger callbacks:
* +increment!+
* +save+
* +save!+
-* +save(false)+
+* +save(:validate => false)+
* +toggle!+
* +update+
* +update_attribute+
@@ -1037,6 +1064,7 @@ Additionally, the +after_find+ callback is triggered by the following finder met
* +find_all_by_<em>attribute</em>+
* +find_by_<em>attribute</em>+
* +find_by_<em>attribute</em>!+
+* +find_by_sql+
* +last+
The +after_initialize+ callback is triggered every time a new object of the class is initialized.
@@ -1049,7 +1077,6 @@ Just as with validations, it is also possible to skip callbacks. These methods s
* +decrement_counter+
* +delete+
* +delete_all+
-* +find_by_sql+
* +increment+
* +increment_counter+
* +toggle+
@@ -1094,7 +1121,7 @@ Post destroyed
h3. Conditional Callbacks
-As with validations, we can also make the calling of a callback method conditional on the satisfaction of a given predicate. We can do this using the +:if+ and +:unless+ options, which can take a symbol, a string or a +Proc+. You may use the +:if+ option when you want to specify under which conditions the callback *should* be called. If you want to specify the conditions under which the callback *should not* be called, then you may use the +:unless+ option.
+As with validations, we can also make the calling of a callback method conditional on the satisfaction of a given predicate. We can do this using the +:if+ and +:unless+ options, which can take a symbol, a string, a +Proc+ or an +Array+. You may use the +:if+ option when you want to specify under which conditions the callback *should* be called. If you want to specify the conditions under which the callback *should not* be called, then you may use the +:unless+ option.
h4. Using +:if+ and +:unless+ with a +Symbol+
diff --git a/railties/guides/source/active_support_core_extensions.textile b/guides/source/active_support_core_extensions.textile
index c04e49281e..80faffa49c 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/guides/source/active_support_core_extensions.textile
@@ -84,7 +84,7 @@ The following values are considered to be blank in a Rails application:
* any other object that responds to +empty?+ and it is empty.
-INFO: In Ruby 1.9 the predicate for strings uses the Unicode-aware character class <tt>[:space:]</tt>, so for example U+2029 (paragraph separator) is considered to be whitespace. In Ruby 1.8 whitespace is considered to be <tt>\s</tt> together with the ideographic space U+3000.
+INFO: The predicate for strings uses the Unicode-aware character class <tt>[:space:]</tt>, so for example U+2029 (paragraph separator) is considered to be whitespace.
WARNING: Note that numbers are not mentioned, in particular 0 and 0.0 are *not* blank.
@@ -154,6 +154,51 @@ WARNING. Any class can disallow duplication removing +dup+ and +clone+ or raisin
NOTE: Defined in +active_support/core_ext/object/duplicable.rb+.
+h4. +deep_dup+
+
+The +deep_dup+ method returns deep copy of given object. Normally, when you +dup+ an object that contains other objects, ruby does not +dup+ them. If you have array with a string, for example, it will look like this:
+
+<ruby>
+array = ['string']
+duplicate = array.dup
+
+duplicate.push 'another-string'
+
+# object was duplicated, element added only to duplicate
+array #=> ['string']
+duplicate #=> ['string', 'another-string']
+
+duplicate.first.gsub!('string', 'foo')
+
+# first element was not duplicated, it will be changed for both arrays
+array #=> ['foo']
+duplicate #=> ['foo', 'another-string']
+</ruby>
+
+As you can see, after duplicating +Array+ instance, we got another object, therefore we can modify it and the original object will stay unchanged. This is not true for array's elements, however. Since +dup+ does not make deep copy, the string inside array is still the same object.
+
+If you need a deep copy of an object, you should use +deep_dup+ in such situation:
+
+<ruby>
+array = ['string']
+duplicate = array.deep_dup
+
+duplicate.first.gsub!('string', 'foo')
+
+array #=> ['string']
+duplicate #=> ['foo']
+</ruby>
+
+If object is not duplicable +deep_dup+ will just return this object:
+
+<ruby>
+number = 1
+dup = number.deep_dup
+number.object_id == dup.object_id # => true
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/object/deep_dup.rb+.
+
h4. +try+
Sometimes you want to call a method provided the receiver object is not +nil+, which is something you usually check first. +try+ is like +Object#send+ except that it returns +nil+ if sent to +nil+.
@@ -177,19 +222,6 @@ end
NOTE: Defined in +active_support/core_ext/object/try.rb+.
-h4. +singleton_class+
-
-The method +singleton_class+ returns the singleton class of the receiver:
-
-<ruby>
-String.singleton_class # => #<Class:String>
-String.new.singleton_class # => #<Class:#<String:0x17a1d1c>>
-</ruby>
-
-WARNING: Fixnums and symbols have no singleton classes, +singleton_class+ raises +TypeError+ on them. Moreover, the singleton classes of +nil+, +true+, and +false+, are +NilClass+, +TrueClass+, and +FalseClass+, respectively.
-
-NOTE: Defined in +active_support/core_ext/kernel/singleton_class.rb+.
-
h4. +class_eval(*args, &block)+
You can evaluate code in the context of any object's singleton class using +class_eval+:
@@ -197,7 +229,7 @@ You can evaluate code in the context of any object's singleton class using +clas
<ruby>
class Proc
def bind(object)
- block, time = self, Time.now
+ block, time = self, Time.current
object.class_eval do
method_name = "__bind_#{time.to_i}_#{time.usec}"
define_method(method_name, &block)
@@ -389,7 +421,7 @@ NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+.
h5. +instance_values+
The method +instance_values+ returns a hash that maps instance variable names without "@" to their
-corresponding values. Keys are strings both in Ruby 1.8 and 1.9:
+corresponding values. Keys are strings:
<ruby>
class C
@@ -440,14 +472,16 @@ NOTE: Defined in +active_support/core_ext/kernel/reporting.rb+.
h4. +in?+
-The predicate +in?+ tests if an object is included in another object. An +ArgumentError+ exception will be raised if the argument passed does not respond to +include?+.
+The predicate +in?+ tests if an object is included in another object or a list of objects. An +ArgumentError+ exception will be raised if a single argument is passed and it does not respond to +include?+.
Examples of +in?+:
<ruby>
+1.in?(1,2) # => true
1.in?([1,2]) # => true
"lo".in?("hello") # => true
25.in?(30..50) # => false
+1.in?(1) # => ArgumentError
</ruby>
NOTE: Defined in +active_support/core_ext/object/inclusion.rb+.
@@ -520,58 +554,9 @@ end
NOTE: Defined in +active_support/core_ext/module/aliasing.rb+.
-h5. +attr_accessor_with_default+
-
-The method +attr_accessor_with_default+ serves the same purpose as the Ruby macro +attr_accessor+ but allows you to set a default value for the attribute:
-
-<ruby>
-class Url
- attr_accessor_with_default :port, 80
-end
-
-Url.new.port # => 80
-</ruby>
-
-The default value can be also specified with a block, which is called in the context of the corresponding object:
-
-<ruby>
-class User
- attr_accessor :name, :surname
- attr_accessor_with_default(:full_name) do
- [name, surname].compact.join(" ")
- end
-end
-
-u = User.new
-u.name = 'Xavier'
-u.surname = 'Noria'
-u.full_name # => "Xavier Noria"
-</ruby>
-
-The result is not cached, the block is invoked in each call to the reader.
-
-You can overwrite the default with the writer:
-
-<ruby>
-url = Url.new
-url.host # => 80
-url.host = 8080
-url.host # => 8080
-</ruby>
-
-The default value is returned as long as the attribute is unset. The reader does not rely on the value of the attribute to know whether it has to return the default. It rather monitors the writer: if there's any assignment the value is no longer considered to be unset.
-
-Active Resource uses this macro to set a default value for the +:primary_key+ attribute:
-
-<ruby>
-attr_accessor_with_default :primary_key, 'id'
-</ruby>
-
-NOTE: Defined in +active_support/core_ext/module/attr_accessor_with_default.rb+.
-
h5. Internal Attributes
-When you are defining an attribute in a class that is meant to be subclassed name collisions are a risk. That's remarkably important for libraries.
+When you are defining an attribute in a class that is meant to be subclassed, name collisions are a risk. That's remarkably important for libraries.
Active Support defines the macros +attr_internal_reader+, +attr_internal_writer+, and +attr_internal_accessor+. They behave like their Ruby built-in +attr_*+ counterparts, except they name the underlying instance variable in a way that makes collisions less likely.
@@ -703,7 +688,8 @@ NOTE: Defined in +active_support/core_ext/module/introspection.rb+.
h4. Constants
-The method +local_constants+ returns the names of the constants that have been defined in the receiver module:
+The method +local_constants+ returns the names of the constants that have been
+defined in the receiver module:
<ruby>
module X
@@ -715,39 +701,71 @@ module X
end
end
-X.local_constants # => ["X2", "X1", "Y"], assumes Ruby 1.8
-X::Y.local_constants # => ["X1", "Y1"], assumes Ruby 1.8
+X.local_constants # => [:X1, :X2, :Y]
+X::Y.local_constants # => [:Y1, :X1]
</ruby>
-The names are returned as strings in Ruby 1.8, and as symbols in Ruby 1.9. The method +local_constant_names+ always returns strings.
-
-WARNING: This method returns precise results in Ruby 1.9. In older versions of Ruby, however, it may miss some constants in case the same constant exists in the receiver module as well as in any of its ancestors and both constants point to the same object (objects are compared using +Object#object_id+).
+The names are returned as symbols. (The deprecated method +local_constant_names+ returns strings.)
NOTE: Defined in +active_support/core_ext/module/introspection.rb+.
-h4. Synchronization
+h5. Qualified Constant Names
+
+The standard methods +const_defined?+, +const_get+ , and +const_set+ accept
+bare constant names. Active Support extends this API to be able to pass
+relative qualified constant names.
-The +synchronize+ macro declares a method to be synchronized:
+The new methods are +qualified_const_defined?+, +qualified_const_get+, and
++qualified_const_set+. Their arguments are assumed to be qualified constant
+names relative to their receiver:
<ruby>
-class Counter
- @@mutex = Mutex.new
- attr_reader :value
+Object.qualified_const_defined?("Math::PI") # => true
+Object.qualified_const_get("Math::PI") # => 3.141592653589793
+Object.qualified_const_set("Math::Phi", 1.618034) # => 1.618034
+</ruby>
- def initialize
- @value = 0
- end
+Arguments may be bare constant names:
- def incr
- @value += 1 # non-atomic
+<ruby>
+Math.qualified_const_get("E") # => 2.718281828459045
+</ruby>
+
+These methods are analogous to their builtin counterparts. In particular,
++qualified_constant_defined?+ accepts an optional second argument to be
+able to say whether you want the predicate to look in the ancestors.
+This flag is taken into account for each constant in the expression while
+walking down the path.
+
+For example, given
+
+<ruby>
+module M
+ X = 1
+end
+
+module N
+ class C
+ include M
end
- synchronize :incr, :with => '@@mutex'
end
</ruby>
-The method receives the name of an action, and a +:with+ option with code. The code is evaluated in the context of the receiver each time the method is invoked, and it should evaluate to a +Mutex+ instance or any other object that responds to +synchronize+ and accepts a block.
++qualified_const_defined?+ behaves this way:
+
+<ruby>
+N.qualified_const_defined?("C::X", false) # => false
+N.qualified_const_defined?("C::X", true) # => true
+N.qualified_const_defined?("C::X") # => true
+</ruby>
-NOTE: Defined in +active_support/core_ext/module/synchronization.rb+.
+As the last example implies, the second argument defaults to true,
+as in +const_defined?+.
+
+For coherence with the builtin methods only relative paths are accepted.
+Absolute qualified constant names like +::Math::PI+ raise +NameError+.
+
+NOTE: Defined in +active_support/core_ext/module/qualified_const.rb+.
h4. Reachable
@@ -799,7 +817,7 @@ M.name # => "M"
N = Module.new
N.name # => "N"
-Module.new.name # => "" in 1.8, nil in 1.9
+Module.new.name # => nil
</ruby>
You can check whether a module has a name with the predicate +anonymous?+:
@@ -912,18 +930,6 @@ In the previous example the macro generates +avatar_size+ rather than +size+.
NOTE: Defined in +active_support/core_ext/module/delegation.rb+
-h4. Method Names
-
-The builtin methods +instance_methods+ and +methods+ return method names as strings or symbols depending on the Ruby version. Active Support defines +instance_method_names+ and +method_names+ to be equivalent to them, respectively, but always getting strings back.
-
-For example, +ActionView::Helpers::FormBuilder+ knows this array difference is going to work no matter the Ruby version:
-
-<ruby>
-self.field_helpers = (FormHelper.instance_method_names - ['form_for'])
-</ruby>
-
-NOTE: Defined in +active_support/core_ext/module/method_names.rb+
-
h4. Redefining Methods
There are cases where you need to define a method with +define_method+, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either.
@@ -1073,49 +1079,6 @@ A model may find it useful to set +:instance_accessor+ to +false+ as a way to pr
NOTE: Defined in +active_support/core_ext/class/attribute_accessors.rb+.
-h4. Class Inheritable Attributes
-
-WARNING: Class Inheritable Attributes are deprecated. It's recommended that you use +Class#class_attribute+ instead.
-
-Class variables are shared down the inheritance tree. Class instance variables are not shared, but they are not inherited either. The macros +class_inheritable_reader+, +class_inheritable_writer+, and +class_inheritable_accessor+ provide accessors for class-level data which is inherited but not shared with children:
-
-<ruby>
-module ActionController
- class Base
- # FIXME: REVISE/SIMPLIFY THIS COMMENT.
- # The value of allow_forgery_protection is inherited,
- # but its value in a particular class does not affect
- # the value in the rest of the controllers hierarchy.
- class_inheritable_accessor :allow_forgery_protection
- end
-end
-</ruby>
-
-They accomplish this with class instance variables and cloning on subclassing, there are no class variables involved. Cloning is performed with +dup+ as long as the value is duplicable.
-
-There are some variants specialised in arrays and hashes:
-
-<ruby>
-class_inheritable_array
-class_inheritable_hash
-</ruby>
-
-Those writers take any inherited array or hash into account and extend them rather than overwrite them.
-
-As with vanilla class attribute accessors these macros create convenience instance methods for reading and writing. The generation of the writer instance method can be prevented setting +:instance_writer+ to +false+ (not any false value, but exactly +false+):
-
-<ruby>
-module ActiveRecord
- class Base
- class_inheritable_accessor :default_scoping, :instance_writer => false
- end
-end
-</ruby>
-
-Since values are copied when a subclass is defined, if the base class changes the attribute after that, the subclass does not see the new value. That's the point.
-
-NOTE: Defined in +active_support/core_ext/class/inheritable_attributes.rb+.
-
h4. Subclasses & Descendants
h5. +subclasses+
@@ -1170,7 +1133,7 @@ h4. Output Safety
h5. Motivation
-Inserting data into HTML templates needs extra care. For example you can't just interpolate +@review.title+ verbatim into an HTML page. On one hand if the review title is "Flanagan & Matz rules!" the output won't be well-formed because an ampersand has to be escaped as "&amp;amp;". On the other hand, depending on the application that may be a big security hole because users can inject malicious HTML setting a hand-crafted review title. Check out the "section about cross-site scripting in the Security guide":security.html#cross-site-scripting-xss for further information about the risks.
+Inserting data into HTML templates needs extra care. For example, you can't just interpolate +@review.title+ verbatim into an HTML page. For one thing, if the review title is "Flanagan & Matz rules!" the output won't be well-formed because an ampersand has to be escaped as "&amp;amp;". What's more, depending on the application, that may be a big security hole because users can inject malicious HTML setting a hand-crafted review title. Check out the "section about cross-site scripting in the Security guide":security.html#cross-site-scripting-xss for further information about the risks.
h5. Safe Strings
@@ -1241,7 +1204,7 @@ NOTE: Defined in +active_support/core_ext/string/output_safety.rb+.
h5. Transformation
-As a rule of thumb, except perhaps for concatenation as explained above, any method that may change a string gives you an unsafe string. These are +donwcase+, +gsub+, +strip+, +chomp+, +underscore+, etc.
+As a rule of thumb, except perhaps for concatenation as explained above, any method that may change a string gives you an unsafe string. These are +downcase+, +gsub+, +strip+, +chomp+, +underscore+, etc.
In the case of in-place transformations like +gsub!+ the receiver itself becomes unsafe.
@@ -1294,9 +1257,14 @@ Pass a +:separator+ to truncate the string at a natural break:
# => "Oh dear! Oh..."
</ruby>
-In the above example "dear" gets cut first, but then +:separator+ prevents it.
+The option +:separator+ can be a regexp:
-WARNING: The option +:separator+ can't be a regexp.
+<ruby>
+"Oh dear! Oh dear! I shall be late!".truncate(18, :separator => /\s/)
+# => "Oh dear! Oh..."
+</ruby>
+
+In above examples "dear" gets cut first, but then +:separator+ prevents it.
NOTE: Defined in +active_support/core_ext/string/filters.rb+.
@@ -1309,20 +1277,6 @@ The <tt>inquiry</tt> method converts a string into a +StringInquirer+ object mak
"active".inquiry.inactive? # => false
</ruby>
-h4. Key-based Interpolation
-
-In Ruby 1.9 the <tt>%</tt> string operator supports key-based interpolation, both formatted and unformatted:
-
-<ruby>
-"Total is %<total>.02f" % {:total => 43.1} # => Total is 43.10
-"I say %{foo}" % {:foo => "wadus"} # => "I say wadus"
-"I say %{woo}" % {:foo => "wadus"} # => KeyError
-</ruby>
-
-Active Support adds that functionality to <tt>%</tt> in previous versions of Ruby.
-
-NOTE: Defined in +active_support/core_ext/string/interpolation.rb+.
-
h4. +starts_with?+ and +ends_with?+
Active Support defines 3rd person aliases of +String#start_with?+ and +String#end_with?+:
@@ -1369,7 +1323,7 @@ Returns the character of the string at position +position+:
"hello".at(0) # => "h"
"hello".at(4) # => "o"
"hello".at(-1) # => "o"
-"hello".at(10) # => ERROR if < 1.9, nil in 1.9
+"hello".at(10) # => nil
</ruby>
NOTE: Defined in +active_support/core_ext/string/access.rb+.
@@ -1597,7 +1551,7 @@ NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
h5. +demodulize+
-Given a string with a qualified constant reference expression, +demodulize+ returns the very constant name, that is, the rightmost part of it:
+Given a string with a qualified constant name, +demodulize+ returns the very constant name, that is, the rightmost part of it:
<ruby>
"Product".demodulize # => "Product"
@@ -1620,6 +1574,31 @@ end
NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
+h5. +deconstantize+
+
+Given a string with a qualified constant reference expression, +deconstantize+ removes the rightmost segment, generally leaving the name of the constant's container:
+
+<ruby>
+"Product".deconstantize # => ""
+"Backoffice::UsersController".deconstantize # => "Backoffice"
+"Admin::Hotel::ReservationUtils".deconstantize # => "Admin::Hotel"
+</ruby>
+
+Active Support for example uses this method in +Module#qualified_const_set+:
+
+<ruby>
+def qualified_const_set(path, value)
+ QualifiedConstUtils.raise_if_absolute(path)
+
+ const_name = path.demodulize
+ mod_name = path.deconstantize
+ mod = mod_name.empty? ? self : qualified_const_get(mod_name)
+ mod.const_set(const_name, value)
+end
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
+
h5. +parameterize+
The method +parameterize+ normalizes its receiver in a way that can be used in pretty URLs.
@@ -1766,43 +1745,6 @@ NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
h4(#string-conversions). Conversions
-h5. +ord+
-
-Ruby 1.9 defines +ord+ to be the codepoint of the first character of the receiver. Active Support backports +ord+ for single-byte encodings like ASCII or ISO-8859-1 in Ruby 1.8:
-
-<ruby>
-"a".ord # => 97
-"à".ord # => 224, in ISO-8859-1
-</ruby>
-
-In Ruby 1.8 +ord+ doesn't work in general in UTF8 strings, use the multibyte support in Active Support for that:
-
-<ruby>
-"a".mb_chars.ord # => 97
-"à".mb_chars.ord # => 224, in UTF8
-</ruby>
-
-Note that the 224 is different in both examples. In ISO-8859-1 "à" is represented as a single byte, 224. Its single-character representation in UTF8 has two bytes, namely 195 and 160, but its Unicode codepoint is 224. If we call +ord+ on the UTF8 string "à" the return value will be 195 in Ruby 1.8. That is not an error, because UTF8 is unsupported, the call itself would be bogus.
-
-INFO: +ord+ is equivalent to +getbyte(0)+.
-
-NOTE: Defined in +active_support/core_ext/string/conversions.rb+.
-
-h5. +getbyte+
-
-Active Support backports +getbyte+ from Ruby 1.9:
-
-<ruby>
-"foo".getbyte(0) # => 102, same as "foo".ord
-"foo".getbyte(1) # => 111
-"foo".getbyte(9) # => nil
-"foo".getbyte(-1) # => 111
-</ruby>
-
-INFO: +getbyte+ is equivalent to +[]+.
-
-NOTE: Defined in +active_support/core_ext/string/conversions.rb+.
-
h5. +to_date+, +to_time+, +to_datetime+
The methods +to_date+, +to_time+, and +to_datetime+ are basically convenience wrappers around +Date._parse+:
@@ -1861,6 +1803,43 @@ Singular forms are aliased so you are able to say:
NOTE: Defined in +active_support/core_ext/numeric/bytes.rb+.
+h4. Time
+
+Enables the use of time calculations and declarations, like @45.minutes <plus> 2.hours <plus> 4.years@.
+
+These methods use Time#advance for precise date calculations when using from_now, ago, etc.
+as well as adding or subtracting their results from a Time object. For example:
+
+<ruby>
+# equivalent to Time.current.advance(:months => 1)
+1.month.from_now
+
+# equivalent to Time.current.advance(:years => 2)
+2.years.from_now
+
+# equivalent to Time.current.advance(:months => 4, :years => 5)
+(4.months + 5.years).from_now
+</ruby>
+
+While these methods provide precise calculation when used as in the examples above, care
+should be taken to note that this is not true if the result of `months', `years', etc is
+converted before use:
+
+<ruby>
+# equivalent to 30.days.to_i.from_now
+1.month.to_i.from_now
+
+# equivalent to 365.25.days.to_f.from_now
+1.year.to_f.from_now
+</ruby>
+
+In such cases, Ruby's core
+Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
+Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
+date and time arithmetic.
+
+NOTE: Defined in +active_support/core_ext/numeric/time.rb+.
+
h3. Extensions to +Integer+
h4. +multiple_of?+
@@ -1874,9 +1853,24 @@ The method +multiple_of?+ tests whether an integer is multiple of the argument:
NOTE: Defined in +active_support/core_ext/integer/multiple.rb+.
+h4. +ordinal+
+
+The method +ordinal+ returns the ordinal suffix string corresponding to the receiver integer:
+
+<ruby>
+1.ordinal # => "st"
+2.ordinal # => "nd"
+53.ordinal # => "rd"
+2009.ordinal # => "th"
+-21.ordinal # => "st"
+-134.ordinal # => "th"
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/integer/inflections.rb+.
+
h4. +ordinalize+
-The method +ordinalize+ returns the ordinal string corresponding to the receiver integer:
+The method +ordinalize+ returns the ordinal string corresponding to the receiver integer. In comparison, note that the +ordinal+ method returns *only* the suffix string.
<ruby>
1.ordinalize # => "1st"
@@ -1889,38 +1883,12 @@ The method +ordinalize+ returns the ordinal string corresponding to the receiver
NOTE: Defined in +active_support/core_ext/integer/inflections.rb+.
-h3. Extensions to +Float+
-
-h4. +round+
-
-The built-in method +Float#round+ rounds a float to the nearest integer. In Ruby 1.9 this method takes an optional argument to let you specify a precision. Active Support adds that functionality to +round+ in previous versions of Ruby:
-
-<ruby>
-Math::E.round(4) # => 2.7183
-</ruby>
-
-NOTE: Defined in +active_support/core_ext/float/rounding.rb+.
-
h3. Extensions to +BigDecimal+
...
h3. Extensions to +Enumerable+
-h4. +group_by+
-
-Active Support redefines +group_by+ in Ruby 1.8.7 so that it returns an ordered hash as in 1.9:
-
-<ruby>
-entries_by_surname_initial = address_book.group_by do |entry|
- entry.surname.at(0).upcase
-end
-</ruby>
-
-Distinct block return values are added to the hash as they come, so that's the resulting order.
-
-NOTE: Defined in +active_support/core_ext/enumerable.rb+.
-
h4. +sum+
The method +sum+ adds the elements of an enumerable:
@@ -1968,32 +1936,6 @@ end
NOTE: Defined in +active_support/core_ext/enumerable.rb+.
-h4. +each_with_object+
-
-The +inject+ method offers iteration with an accumulator:
-
-<ruby>
-[2, 3, 4].inject(1) {|product, i| product*i } # => 24
-</ruby>
-
-The block is expected to return the value for the accumulator in the next iteration, and this makes building mutable objects a bit cumbersome:
-
-<ruby>
-[1, 2].inject({}) {|h, i| h[i] = i**2; h} # => {1 => 1, 2 => 4}
-</ruby>
-
-See that spurious "+; h+"?
-
-Active Support backports +each_with_object+ from Ruby 1.9, which addresses that use case. It iterates over the collection, passes the accumulator, and returns the accumulator when done. You normally modify the accumulator in place. The example above would be written this way:
-
-<ruby>
-[1, 2].each_with_object({}) {|i, h| h[i] = i**2} # => {1 => 1, 2 => 4}
-</ruby>
-
-WARNING. Note that the item of the collection and the accumulator come in different order in +inject+ and +each_with_object+.
-
-NOTE: Defined in +active_support/core_ext/enumerable.rb+.
-
h4. +index_by+
The method +index_by+ generates a hash with the elements of an enumerable indexed by some key.
@@ -2065,20 +2007,6 @@ The methods +second+, +third+, +fourth+, and +fifth+ return the corresponding el
NOTE: Defined in +active_support/core_ext/array/access.rb+.
-h4. Random Access
-
-Active Support backports +sample+ from Ruby 1.9:
-
-<ruby>
-shape_type = [Circle, Square, Triangle].sample
-# => Square, for example
-
-shape_types = [Circle, Square, Triangle].sample(2)
-# => [Triangle, Circle], for example
-</ruby>
-
-NOTE: Defined in +active_support/core_ext/array/random_access.rb+.
-
h4. Adding Elements
h5. +prepend+
@@ -2165,7 +2093,7 @@ h5. +to_formatted_s+
The method +to_formatted_s+ acts like +to_s+ by default.
-If the array contains items that respond to +id+, however, it may be passed the symbol <tt>:db</tt> as argument. That's typically used with collections of ARs, though technically any object in Ruby 1.8 responds to +id+ indeed. Returned strings are:
+If the array contains items that respond to +id+, however, it may be passed the symbol <tt>:db</tt> as argument. That's typically used with collections of ARs. Returned strings are:
<ruby>
[].to_formatted_s(:db) # => "null"
@@ -2205,20 +2133,20 @@ To do so it sends +to_xml+ to every item in turn, and collects the results under
By default, the name of the root element is the underscorized and dasherized plural of the name of the class of the first item, provided the rest of elements belong to that type (checked with <tt>is_a?</tt>) and they are not hashes. In the example above that's "contributors".
-If there's any element that does not belong to the type of the first one the root node becomes "records":
+If there's any element that does not belong to the type of the first one the root node becomes "objects":
<ruby>
[Contributor.first, Commit.first].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
-# <records type="array">
-# <record>
+# <objects type="array">
+# <object>
# <id type="integer">4583</id>
# <name>Aaron Batalion</name>
# <rank type="integer">53</rank>
# <url-id>aaron-batalion</url-id>
-# </record>
-# <record>
+# </object>
+# <object>
# <author>Joshua Peek</author>
# <authored-timestamp type="datetime">2009-09-02T16:44:36Z</authored-timestamp>
# <branch>origin/master</branch>
@@ -2229,30 +2157,30 @@ If there's any element that does not belong to the type of the first one the roo
# <imported-from-svn type="boolean">false</imported-from-svn>
# <message>Kill AMo observing wrap_with_notifications since ARes was only using it</message>
# <sha1>723a47bfb3708f968821bc969a9a3fc873a3ed58</sha1>
-# </record>
-# </records>
+# </object>
+# </objects>
</ruby>
-If the receiver is an array of hashes the root element is by default also "records":
+If the receiver is an array of hashes the root element is by default also "objects":
<ruby>
[{:a => 1, :b => 2}, {:c => 3}].to_xml
# =>
# <?xml version="1.0" encoding="UTF-8"?>
-# <records type="array">
-# <record>
+# <objects type="array">
+# <object>
# <b type="integer">2</b>
# <a type="integer">1</a>
-# </record>
-# <record>
+# </object>
+# <object>
# <c type="integer">3</c>
-# </record>
-# </records>
+# </object>
+# </objects>
</ruby>
WARNING. If the collection is empty the root element is by default "nil-classes". That's a gotcha, for example the root element of the list of contributors above would not be "contributors" if the collection was empty, but "nil-classes". You may use the <tt>:root</tt> option to ensure a consistent root element.
-The name of children nodes is by default the name of the root node singularized. In the examples above we've seen "contributor" and "record". The option <tt>:children</tt> allows you to set these node names.
+The name of children nodes is by default the name of the root node singularized. In the examples above we've seen "contributor" and "object". The option <tt>:children</tt> allows you to set these node names.
The default XML builder is a fresh instance of <tt>Builder::XmlMarkup</tt>. You can configure your own builder via the <tt>:builder</tt> option. The method also accepts options like <tt>:dasherize</tt> and friends, they are forwarded to the builder:
@@ -2305,9 +2233,6 @@ The last point is particularly worth comparing for some enumerables:
<ruby>
Array.wrap(:foo => :bar) # => [{:foo => :bar}]
Array(:foo => :bar) # => [[:foo, :bar]]
-
-Array.wrap("foo\nbar") # => ["foo\nbar"]
-Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
</ruby>
There's also a related idiom that uses the splat operator:
@@ -2322,6 +2247,19 @@ Thus, in this case the behavior is different for +nil+, and the differences with
NOTE: Defined in +active_support/core_ext/array/wrap.rb+.
+h4. Duplicating
+
+The method +Array.deep_dup+ duplicates itself and all objects inside recursively with ActiveSupport method +Object#deep_dup+. It works like +Array#map+ with sending +deep_dup+ method to each object inside.
+
+<ruby>
+array = [1, [2, 3]]
+dup = array.deep_dup
+dup[1][2] = 4
+array[1][2] == nil # => true
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/array/deep_dup.rb+.
+
h4. Grouping
h5. +in_groups_of(number, fill_with = nil)+
@@ -2528,6 +2466,23 @@ The method +deep_merge!+ performs a deep merge in place.
NOTE: Defined in +active_support/core_ext/hash/deep_merge.rb+.
+h4. Deep duplicating
+
+The method +Hash.deep_dup+ duplicates itself and all keys and values inside recursively with ActiveSupport method +Object#deep_dup+. It works like +Enumerator#each_with_object+ with sending +deep_dup+ method to each pair inside.
+
+<ruby>
+hash = { :a => 1, :b => { :c => 2, :d => [3, 4] } }
+
+dup = hash.deep_dup
+dup[:b][:e] = 5
+dup[:b][:d] << 5
+
+hash[:b][:e] == nil # => true
+hash[:b][:d] == [3, 4] # => true
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/hash/deep_dup.rb+.
+
h4. Diffing
The method +diff+ returns a hash that represents a diff of the receiver and the argument with the following logic:
@@ -2798,47 +2753,30 @@ As the example depicts, the +:db+ format generates a +BETWEEN+ SQL clause. That
NOTE: Defined in +active_support/core_ext/range/conversions.rb+.
-h4. +step+
-
-Active Support extends the method +Range#step+ so that it can be invoked without a block:
-
-<ruby>
-(1..10).step(2) # => [1, 3, 5, 7, 9]
-</ruby>
-
-As the example shows, in that case the method returns an array with the corresponding elements.
-
-NOTE: Defined in +active_support/core_ext/range/blockless_step.rb+.
-
h4. +include?+
-The method +Range#include?+ says whether some value falls between the ends of a given instance:
+The methods +Range#include?+ and +Range#===+ say whether some value falls between the ends of a given instance:
<ruby>
(2..3).include?(Math::E) # => true
</ruby>
-Active Support extends this method so that the argument may be another range in turn. In that case we test whether the ends of the argument range belong to the receiver themselves:
+Active Support extends these methods so that the argument may be another range in turn. In that case we test whether the ends of the argument range belong to the receiver themselves:
<ruby>
(1..10).include?(3..7) # => true
(1..10).include?(0..7) # => false
(1..10).include?(3..11) # => false
(1...9).include?(3..9) # => false
-</ruby>
-WARNING: The original +Range#include?+ is still the one aliased to +Range#===+.
+(1..10) === (3..7) # => true
+(1..10) === (0..7) # => false
+(1..10) === (3..11) # => false
+(1...9) === (3..9) # => false
+</ruby>
NOTE: Defined in +active_support/core_ext/range/include_range.rb+.
-h4. +cover?+
-
-Ruby 1.9 provides +cover?+, and Active Support defines it for previous versions as an alias for +include?+.
-
-The method +include?+ in Ruby 1.9 is different from the one in 1.8 for non-numeric ranges: instead of being based on comparisons between the value and the range's endpoints, it walks the range with +succ+ looking for value. This works better for ranges with holes, but it has different complexity and may not finish in some other cases.
-
-In Ruby 1.9 the old behavior is still available in the new +cover?+, which Active Support backports for forward compatibility. For example, Rails uses +cover?+ for ranges in +validates_inclusion_of+.
-
h4. +overlaps?+
The method +Range#overlaps?+ says whether any two given ranges have non-void intersection:
@@ -2931,7 +2869,7 @@ d.prev_year # => Sun, 28 Feb 1999
d.next_year # => Wed, 28 Feb 2001
</ruby>
-Active Support defines these methods as well for Ruby 1.8.
++prev_year+ is aliased to +last_year+.
h6. +prev_month+, +next_month+
@@ -2952,19 +2890,34 @@ Date.new(2000, 5, 31).next_month # => Fri, 30 Jun 2000
Date.new(2000, 1, 31).next_month # => Tue, 29 Feb 2000
</ruby>
-Active Support defines these methods as well for Ruby 1.8.
++prev_month+ is aliased to +last_month+.
h6. +beginning_of_week+, +end_of_week+
-The methods +beginning_of_week+ and +end_of_week+ return the dates for the beginning and end of week, assuming weeks start on Monday:
+The methods +beginning_of_week+ and +end_of_week+ return the dates for the
+beginning and end of the week, respectively. Weeks are assumed to start on
+Monday, but that can be changed passing an argument.
<ruby>
-d = Date.new(2010, 5, 8) # => Sat, 08 May 2010
-d.beginning_of_week # => Mon, 03 May 2010
-d.end_of_week # => Sun, 09 May 2010
+d = Date.new(2010, 5, 8) # => Sat, 08 May 2010
+d.beginning_of_week # => Mon, 03 May 2010
+d.beginning_of_week(:sunday) # => Sun, 02 May 2010
+d.end_of_week # => Sun, 09 May 2010
+d.end_of_week(:sunday) # => Sat, 08 May 2010
</ruby>
-+beginning_of_week+ is aliased to +monday+ and +at_beginning_of_week+. +end_of_week+ is aliased to +sunday+ and +at_end_of_week+.
++beginning_of_week+ is aliased to +at_beginning_of_week+ and +end_of_week+ is aliased to +at_end_of_week+.
+
+h6. +monday+, +sunday+
+
+The methods +monday+ and +sunday+ return the dates for the beginning and
+end of the week, respectively. Weeks are assumed to start on Monday.
+
+<ruby>
+d = Date.new(2010, 5, 8) # => Sat, 08 May 2010
+d.monday # => Mon, 03 May 2010
+d.sunday # => Sun, 09 May 2010
+</ruby>
h6. +prev_week+, +next_week+
@@ -2984,6 +2937,8 @@ d.prev_week(:saturday) # => Sat, 01 May 2010
d.prev_week(:friday) # => Fri, 30 Apr 2010
</ruby>
++prev_week+ is aliased to +last_week+.
+
h6. +beginning_of_month+, +end_of_month+
The methods +beginning_of_month+ and +end_of_month+ return the dates for the beginning and end of the month:
@@ -3144,18 +3099,38 @@ The method +beginning_of_day+ returns a timestamp at the beginning of the day (0
<ruby>
date = Date.new(2010, 6, 7)
-date.beginning_of_day # => Sun Jun 07 00:00:00 +0200 2010
+date.beginning_of_day # => Mon Jun 07 00:00:00 +0200 2010
</ruby>
The method +end_of_day+ returns a timestamp at the end of the day (23:59:59):
<ruby>
date = Date.new(2010, 6, 7)
-date.end_of_day # => Sun Jun 06 23:59:59 +0200 2010
+date.end_of_day # => Mon Jun 07 23:59:59 +0200 2010
</ruby>
+beginning_of_day+ is aliased to +at_beginning_of_day+, +midnight+, +at_midnight+.
+h6. +beginning_of_hour+, +end_of_hour+
+
+The method +beginning_of_hour+ returns a timestamp at the beginning of the hour (hh:00:00):
+
+<ruby>
+date = DateTime.new(2010, 6, 7, 19, 55, 25)
+date.beginning_of_hour # => Mon Jun 07 19:00:00 +0200 2010
+</ruby>
+
+The method +end_of_hour+ returns a timestamp at the end of the hour (hh:59:59):
+
+<ruby>
+date = DateTime.new(2010, 6, 7, 19, 55, 25)
+date.end_of_hour # => Mon Jun 07 19:59:59 +0200 2010
+</ruby>
+
++beginning_of_hour+ is aliased to +at_beginning_of_hour+.
+
+INFO: +beginning_of_hour+ and +end_of_hour+ are implemented for +Time+ and +DateTime+ but *not* +Date+ as it does not make sense to request the beginning or end of an hour on a +Date+ instance.
+
h6. +ago+, +since+
The method +ago+ receives a number of seconds as argument and returns a timestamp those many seconds ago from midnight:
@@ -3189,16 +3164,18 @@ The class +DateTime+ is a subclass of +Date+ so by loading +active_support/core_
<ruby>
yesterday
tomorrow
-beginning_of_week (monday, at_beginning_of_week)
-end_on_week (at_end_of_week)
+beginning_of_week (at_beginning_of_week)
+end_of_week (at_end_of_week)
+monday
+sunday
weeks_ago
-prev_week
+prev_week (last_week)
next_week
months_ago
months_since
beginning_of_month (at_beginning_of_month)
end_of_month (at_end_of_month)
-prev_month
+prev_month (last_month)
next_month
beginning_of_quarter (at_beginning_of_quarter)
end_of_quarter (at_end_of_quarter)
@@ -3206,7 +3183,7 @@ beginning_of_year (at_beginning_of_year)
end_of_year (at_end_of_year)
years_ago
years_since
-prev_year
+prev_year (last_year)
next_year
</ruby>
@@ -3221,6 +3198,13 @@ since (in)
On the other hand, +advance+ and +change+ are also defined and support more options, they are documented below.
+The following methods are only implemented in +active_support/core_ext/date_time/calculations.rb+ as they only make sense when used with a +DateTime+ instance:
+
+<ruby>
+beginning_of_hour (at_beginning_of_hour)
+end_of_hour
+</ruby>
+
h5. Named Datetimes
h6. +DateTime.current+
@@ -3363,16 +3347,20 @@ ago
since (in)
beginning_of_day (midnight, at_midnight, at_beginning_of_day)
end_of_day
-beginning_of_week (monday, at_beginning_of_week)
-end_on_week (at_end_of_week)
+beginning_of_hour (at_beginning_of_hour)
+end_of_hour
+beginning_of_week (at_beginning_of_week)
+end_of_week (at_end_of_week)
+monday
+sunday
weeks_ago
-prev_week
+prev_week (last_week)
next_week
months_ago
months_since
beginning_of_month (at_beginning_of_month)
end_of_month (at_end_of_month)
-prev_month
+prev_month (last_month)
next_month
beginning_of_quarter (at_beginning_of_quarter)
end_of_quarter (at_end_of_quarter)
@@ -3380,7 +3368,7 @@ beginning_of_year (at_beginning_of_year)
end_of_year (at_end_of_year)
years_ago
years_since
-prev_year
+prev_year (last_year)
next_year
</ruby>
@@ -3489,12 +3477,6 @@ Time.utc_time(1582, 10, 3) + 5.days
# => Mon Oct 18 00:00:00 UTC 1582
</ruby>
-h3. Extensions to +Process+
-
-h4. +daemon+
-
-Ruby 1.9 provides +Process.daemon+, and Active Support defines it for previous versions. It accepts the same two arguments, whether it should chdir to the root directory (default, true), and whether it should inherit the standard file descriptors from the parent (default, false).
-
h3. Extensions to +File+
h4. +atomic_write+
diff --git a/guides/source/active_support_instrumentation.textile b/guides/source/active_support_instrumentation.textile
new file mode 100644
index 0000000000..430549fba4
--- /dev/null
+++ b/guides/source/active_support_instrumentation.textile
@@ -0,0 +1,448 @@
+h2. Active Support Instrumentation
+
+Active Support is a part of core Rails that provides Ruby language extensions, utilities and other things. One of the things it includes is an instrumentation API that can be used inside an application to measure certain actions that occur within Ruby code, such as that inside a Rails application or the framework itself. It is not limited to Rails, however. It can be used independently in other Ruby scripts if it is so desired.
+
+In this guide, you will learn how to use the instrumentation API inside of ActiveSupport to measure events inside of Rails and other Ruby code. We cover:
+
+* What instrumentation can provide
+* The hooks inside the Rails framework for instrumentation
+* Adding a subscriber to a hook
+* Building a custom instrumentation implementation
+
+endprologue.
+
+h3. Introduction to instrumentation
+
+The instrumentation API provided by ActiveSupport allows developers to provide hooks which other developers may hook into. There are several of these within the Rails framework, as described below in <TODO: link to section detailing each hook point>. With this API, developers can choose to be notified when certain events occur inside their application or another piece of Ruby code.
+
+For example, there is a hook provided within Active Record that is called every time Active Record uses a SQL query on a database. This hook could be *subscribed* to, and used to track the number of queries during a certain action. There's another hook around the processing of an action of a controller. This could be used, for instance, to track how long a specific action has taken.
+
+You are even able to create your own events inside your application which you can later subscribe to.
+
+h3. Rails framework hooks
+
+Within the Ruby on Rails framework, there are a number of hooks provided for common events. These are detailed below.
+
+h3. ActionController
+
+h4. write_fragment.action_controller
+
+|_.Key |_.Value|
+|+:key+ |The complete key|
+
+<ruby>
+{
+ :key => 'posts/1-dasboard-view'
+}
+</ruby>
+
+h4. read_fragment.action_controller
+
+|_.Key |_.Value|
+|+:key+ |The complete key|
+
+<ruby>
+{
+ :key => 'posts/1-dasboard-view'
+}
+</ruby>
+
+h4. expire_fragment.action_controller
+
+|_.Key |_.Value|
+|+:key+ |The complete key|
+
+<ruby>
+{
+ :key => 'posts/1-dasboard-view'
+}
+</ruby>
+
+h4. exist_fragment?.action_controller
+
+|_.Key |_.Value|
+|+:key+ |The complete key|
+
+<ruby>
+{
+ :key => 'posts/1-dasboard-view'
+}
+</ruby>
+
+h4. write_page.action_controller
+
+|_.Key |_.Value|
+|+:path+ |The complete path|
+
+<ruby>
+{
+ :path => '/users/1'
+}
+</ruby>
+
+h4. expire_page.action_controller
+
+|_.Key |_.Value|
+|+:path+ |The complete path|
+
+<ruby>
+{
+ :path => '/users/1'
+}
+</ruby>
+
+h4. start_processing.action_controller
+
+|_.Key |_.Value |
+|+:controller+ |The controller name|
+|+:action+ |The action|
+|+:params+ |Hash of request parameters without any filtered parameter|
+|+:format+ |html/js/json/xml etc|
+|+:method+ |HTTP request verb|
+|+:path+ |Request path|
+
+<ruby>
+{
+ :controller => "PostsController",
+ :action => "new",
+ :params => { "action" => "new", "controller" => "posts" },
+ :format => :html,
+ :method => "GET",
+ :path => "/posts/new"
+}
+</ruby>
+
+h4. process_action.action_controller
+
+|_.Key |_.Value |
+|+:controller+ |The controller name|
+|+:action+ |The action|
+|+:params+ |Hash of request parameters without any filtered parameter|
+|+:format+ |html/js/json/xml etc|
+|+:method+ |HTTP request verb|
+|+:path+ |Request path|
+|+:view_runtime+ |Amount spent in view in ms|
+
+<ruby>
+{
+ :controller => "PostsController",
+ :action => "index",
+ :params => {"action" => "index", "controller" => "posts"},
+ :format => :html,
+ :method => "GET",
+ :path => "/posts",
+ :status => 200,
+ :view_runtime => 46.848,
+ :db_runtime => 0.157
+}
+</ruby>
+
+h4. send_file.action_controller
+
+|_.Key |_.Value |
+|+:path+ |Complete path to the file|
+
+INFO. Additional keys may be added by the caller.
+
+h4. send_data.action_controller
+
++ActionController+ does not had any specific information to the payload. All options are passed through to the payload.
+
+h4. redirect_to.action_controller
+
+|_.Key |_.Value |
+|+:status+ |HTTP response code|
+|+:location+ |URL to redirect to|
+
+<ruby>
+{
+ :status => 302,
+ :location => "http://localhost:3000/posts/new"
+}
+</ruby>
+
+h4. halted_callback.action_controller
+
+|_.Key |_.Value |
+|+:filter+ |Filter that halted the action|
+
+<ruby>
+{
+ :filter => ":halting_filter"
+}
+</ruby>
+
+h3. ActionView
+
+h4. render_template.action_view
+
+|_.Key |_.Value |
+|+:identifier+ |Full path to template|
+|+:layout+ |Applicable layout|
+
+<ruby>
+{
+ :identifier => "/Users/adam/projects/notifications/app/views/posts/index.html.erb",
+ :layout => "layouts/application"
+}
+</ruby>
+
+h4. render_partial.action_view
+
+|_.Key |_.Value |
+|+:identifier+ |Full path to template|
+
+<ruby>
+{
+ :identifier => "/Users/adam/projects/notifications/app/views/posts/_form.html.erb",
+}
+</ruby>
+
+h3. ActiveRecord
+
+h4. sql.active_record
+
+|_.Key |_.Value |
+|+:sql+ |SQL statement|
+|+:name+ |Name of the operation|
+|+:object_id+ |+self.object_id+|
+
+INFO. The adapters will add their own data as well.
+
+<ruby>
+{
+ :sql => "SELECT \"posts\".* FROM \"posts\" ",
+ :name => "Post Load",
+ :connection_id => 70307250813140,
+ :binds => []
+}
+</ruby>
+
+h4. identity.active_record
+
+|_.Key |_.Value |
+|+:line+ |Primary Key of object in the identity map|
+|+:name+ |Record's class|
+|+:connection_id+ |+self.object_id+|
+
+h3. ActionMailer
+
+h4. receive.action_mailer
+
+|_.Key |_.Value|
+|+:mailer+ |Name of the mailer class|
+|+:message_id+ |ID of the message, generated by the Mail gem|
+|+:subject+ |Subject of the mail|
+|+:to+ |To address(es) of the mail|
+|+:from+ |From address of the mail|
+|+:bcc+ |BCC addresses of the mail|
+|+:cc+ |CC addresses of the mail|
+|+:date+ |Date of the mail|
+|+:mail+ |The encoded form of the mail|
+
+<ruby>
+{
+ :mailer => "Notification",
+ :message_id => "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
+ :subject => "Rails Guides",
+ :to => ["users@rails.com", "ddh@rails.com"],
+ :from => ["me@rails.com"],
+ :date => Sat, 10 Mar 2012 14:18:09 +0100,
+ :mail=> "..." # ommitted for beverity
+}
+</ruby>
+
+h4. deliver.action_mailer
+
+|_.Key |_.Value|
+|+:mailer+ |Name of the mailer class|
+|+:message_id+ |ID of the message, generated by the Mail gem|
+|+:subject+ |Subject of the mail|
+|+:to+ |To address(es) of the mail|
+|+:from+ |From address of the mail|
+|+:bcc+ |BCC addresses of the mail|
+|+:cc+ |CC addresses of the mail|
+|+:date+ |Date of the mail|
+|+:mail+ |The encoded form of the mail|
+
+<ruby>
+{
+ :mailer => "Notification",
+ :message_id => "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail",
+ :subject => "Rails Guides",
+ :to => ["users@rails.com", "ddh@rails.com"],
+ :from => ["me@rails.com"],
+ :date => Sat, 10 Mar 2012 14:18:09 +0100,
+ :mail=> "..." # ommitted for beverity
+}
+</ruby>
+
+h3. ActiveResource
+
+h4. request.active_resource
+
+|_.Key |_.Value|
+|+:method+ |HTTP method|
+|+:request_uri+ |Complete URI|
+|+:result+ |HTTP response object|
+
+h3. ActiveSupport
+
+h4. cache_read.active_support
+
+|_.Key |_.Value|
+|+:key+ |Key used in the store|
+|+:hit+ |If this read is a hit|
+|+:super_operation+ |:fetch is added when a read is used with +#fetch+|
+
+h4. cache_generate.active_support
+
+This event is only used when +#fetch+ is called with a block.
+
+|_.Key |_.Value|
+|+:key+ |Key used in the store|
+
+INFO. Options passed to fetch will be merged with the payload when writing to the store
+
+<ruby>
+{
+ :key => 'name-of-complicated-computation'
+}
+</ruby>
+
+
+h4. cache_fetch_hit.active_support
+
+This event is only used when +#fetch+ is called with a block.
+
+|_.Key |_.Value|
+|+:key+ |Key used in the store|
+
+INFO. Options passed to fetch will be merged with the payload.
+
+<ruby>
+{
+ :key => 'name-of-complicated-computation'
+}
+</ruby>
+
+h4. cache_write.active_support
+
+|_.Key |_.Value|
+|+:key+ |Key used in the store|
+
+INFO. Cache stores my add their own keys
+
+<ruby>
+{
+ :key => 'name-of-complicated-computation'
+}
+</ruby>
+
+h4. cache_delete.active_support
+
+|_.Key |_.Value|
+|+:key+ |Key used in the store|
+
+<ruby>
+{
+ :key => 'name-of-complicated-computation'
+}
+</ruby>
+
+h4. cache_exist?.active_support
+
+|_.Key |_.Value|
+|+:key+ |Key used in the store|
+
+<ruby>
+{
+ :key => 'name-of-complicated-computation'
+}
+</ruby>
+
+h3. Rails
+
+h4. deprecation.rails
+
+|_.Key |_.Value|
+|+:message+ |The deprecation warning|
+|+:callstack+ |Where the deprecation came from|
+
+h3. Subscribing to an event
+
+Subscribing to an event is easy. Use +ActiveSupport::Notifications.subscribe+ with a block to
+listen to any notification.
+
+The block receives the following arguments:
+
+# The name of the event
+# Time when is started
+# Time when it finished
+# An unique ID for this event
+# The payload (described in previous sections)
+
+<ruby>
+ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, data|
+ # your own custom stuff
+ Rails.logger.info "#{name} Received!"
+end
+</ruby>
+
+Defining all those block arguments each time can be tedious. You can easily create an +ActiveSupport::Notifications::Event+
+from block args like this:
+
+<ruby>
+ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
+ event = ActiveSupport::Notification::Event.new args
+
+ event.name # => "process_action.action_controller"
+ event.duration # => 10 (in milliseconds)
+ event.payload # => { :extra => :information }
+
+ Rails.logger.info "#{event} Received!"
+end
+</ruby>
+
+Most times you only care about the data itself. Here is a shortuct to just get the data.
+
+<ruby>
+ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
+ data = args.extract_options!
+ data # { :extra => :information }
+</ruby>
+
+You may also subscribe to events matching a regular expresssion. This enables you to subscribe to
+multiple events at once. Here's you could subscribe to everything from +ActionController+.
+
+<ruby>
+ActiveSupport::Notifications.subscribe /action_controller/ do |*args|
+ # inspect all ActionController events
+end
+</ruby>
+
+h3. Creating custom events
+
+Adding your own events is easy as well. +ActiveSupport::Notifications+ will take care of
+all the heavy lifting for you. Simply call +instrument+ with a +name+, +payload+ and a block.
+The notification will be sent after the block returns. +ActiveSupport+ will generate the start and end times
+as well as the unique ID. All data passed into the +insturment+ call will make it into the payload.
+
+Here's an example:
+
+<ruby>
+ActiveSupport::Notifications.instrument "my.custom.event", :this => :data do
+ # do your custom stuff here
+end
+</ruby>
+
+Now you can listen to this event with:
+
+<ruby>
+ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data|
+ puts data.inspect # { :this => :data }
+end
+</ruby>
+
+You should follow Rails conventions when defining your own events. The format is: +event.library+.
+If you application is sending Tweets, you should create an event named +tweet.twitter+.
diff --git a/railties/guides/source/ajax_on_rails.textile b/guides/source/ajax_on_rails.textile
index 3a0ccfe9b2..bfd007490a 100644
--- a/railties/guides/source/ajax_on_rails.textile
+++ b/guides/source/ajax_on_rails.textile
@@ -78,7 +78,7 @@ will produce
<ruby>
button_to('Destroy', 'http://www.example.com', :confirm => 'Are you sure?',
- :method => "delete", :remote => true, :disable_with => 'loading...')
+ :method => "delete", :remote => true, 'data-disable-with' => 'loading...')
</ruby>
will produce
@@ -87,7 +87,7 @@ will produce
<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
<div>
<input name='_method' value='delete' type='hidden' />
- <input value='Destroy' type='submit' disable_with='loading...' data-confirm='Are you sure?' />
+ <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
</div>
</form>
</html>
@@ -134,7 +134,7 @@ If the server returns 200, the output of the above example is equivalent to our
** *position* By default (i.e. when not specifying this option, like in the examples before) the response is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities:
*** +:before+ Inserts the response text just before the target element. More precisely, it creates a text node from the response and inserts it as the left sibling of the target element.
*** +:after+ Similar behavior to +:before+, but in this case the response is inserted after the target element.
-*** +:top+ Inserts the text into the target element, before it's original content. If the target element was empty, this is equivalent with not specifying +:position+ at all.
+*** +:top+ Inserts the text into the target element, before its original content. If the target element was empty, this is equivalent with not specifying +:position+ at all.
*** +:bottom+ The counterpart of +:top+: the response is inserted after the target element's original content.
A typical example of using +:bottom+ is inserting a new &lt;li&gt; element into an existing list:
@@ -146,7 +146,8 @@ link_to_remote "Add new item",
:position => :bottom
</ruby>
-** *:method* Most typically you want to use a POST request when adding a remote link to your view so this is the default behavior. However, sometimes you'll want to update (PUT) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list:
+** *:method* Most typically you want to use a POST request when adding a remote
+link to your view so this is the default behavior. However, sometimes you'll want to update (PATCH/PUT) or delete/destroy (DELETE) something and you can specify this with the +:method+ option. Let's see an example for a typical AJAX link for deleting an item from a list:
<ruby>
link_to_remote "Delete the item",
@@ -167,13 +168,13 @@ Note that if we wouldn't override the default behavior (POST), the above snippet
<ruby>
link_to_remote "Update record",
:url => record_url(record),
- :method => :put,
+ :method => :patch,
:with => "'status=' <plus> 'encodeURIComponent($('status').value) <plus> '&completed=' <plus> $('completed')"
</ruby>
This generates a remote link which adds 2 parameters to the standard URL generated by Rails, taken from the page (contained in the elements matched by the 'status' and 'completed' DOM id).
-** *Callbacks* Since an AJAX call is typically asynchronous, as it's name suggests (this is not a rule, and you can fire a synchronous request - see the last option, +:type+) your only way of communicating with a request once it is fired is via specifying callbacks. There are six options at your disposal (in fact 508, counting all possible response types, but these six are the most frequent and therefore specified by a constant):
+** *Callbacks* Since an AJAX call is typically asynchronous, as its name suggests (this is not a rule, and you can fire a synchronous request - see the last option, +:type+) your only way of communicating with a request once it is fired is via specifying callbacks. There are six options at your disposal (in fact 508, counting all possible response types, but these six are the most frequent and therefore specified by a constant):
*** +:loading:+ =&gt; +code+ The request is in the process of receiving the data, but the transfer is not completed yet.
*** +:loaded:+ =&gt; +code+ The transfer is completed, but the data is not processed and returned yet
*** +:interactive:+ =&gt; +code+ One step after +:loaded+: The data is fully received and being processed
@@ -202,7 +203,7 @@ link_to_remote "Add new item",
** *:type* If you want to fire a synchronous request for some obscure reason (blocking the browser while the request is processed and doesn't return a status code), you can use the +:type+ option with the value of +:synchronous+.
* Finally, using the +html_options+ parameter you can add HTML attributes to the generated tag. It works like the same parameter of the +link_to+ helper. There are interesting side effects for the +href+ and +onclick+ parameters though:
** If you specify the +href+ parameter, the AJAX link will degrade gracefully, i.e. the link will point to the URL even if JavaScript is disabled in the client browser
-** +link_to_remote+ gains it's AJAX behavior by specifying the remote call in the onclick handler of the link. If you supply +html_options[:onclick]+ you override the default behavior, so use this with care!
+** +link_to_remote+ gains its AJAX behavior by specifying the remote call in the onclick handler of the link. If you supply +html_options[:onclick]+ you override the default behavior, so use this with care!
We are finished with +link_to_remote+. I know this is quite a lot to digest for one helper function, but remember, these options are common for all the rest of the Rails view helpers, so we will take a look at the differences / additional parameters in the next sections.
@@ -210,8 +211,8 @@ h4. AJAX Forms
There are three different ways of adding AJAX forms to your view using Rails Prototype helpers. They are slightly different, but striving for the same goal: instead of submitting the form using the standard HTTP request/response cycle, it is submitted asynchronously, thus not reloading the page. These methods are the following:
-* +remote_form_for+ (and it's alias +form_remote_for+) is tied to Rails most tightly of the three since it takes a resource, model or array of resources (in case of a nested resource) as a parameter.
-* +form_remote_tag+ AJAXifies the form by serializing and sending it's data in the background
+* +remote_form_for+ (and its alias +form_remote_for+) is tied to Rails most tightly of the three since it takes a resource, model or array of resources (in case of a nested resource) as a parameter.
+* +form_remote_tag+ AJAXifies the form by serializing and sending its data in the background
* +submit_to_remote+ and +button_to_remote+ is more rarely used than the previous two. Rather than creating an AJAX form, you add a button/input
Let's see them in action one by one!
diff --git a/railties/guides/source/api_documentation_guidelines.textile b/guides/source/api_documentation_guidelines.textile
index 99eb668513..c6aa1f0a2b 100644
--- a/railties/guides/source/api_documentation_guidelines.textile
+++ b/guides/source/api_documentation_guidelines.textile
@@ -6,7 +6,7 @@ endprologue.
h3. RDoc
-The Rails API documentation is generated with RDoc 2.5. Please consult the documentation for help with the "markup":http://rdoc.rubyforge.org/RDoc/Markup.html, and take into account also these "additional directives":http://rdoc.rubyforge.org/RDoc/Parser/Ruby.html.
+The Rails API documentation is generated with RDoc. Please consult the documentation for help with the "markup":http://rdoc.rubyforge.org/RDoc/Markup.html, and also take into account these "additional directives":http://rdoc.rubyforge.org/RDoc/Parser/Ruby.html.
h3. Wording
@@ -14,7 +14,7 @@ Write simple, declarative sentences. Brevity is a plus: get to the point.
Write in present tense: "Returns a hash that...", rather than "Returned a hash that..." or "Will return a hash that...".
-Start comments in upper case, follow regular punctuation rules:
+Start comments in upper case. Follow regular punctuation rules:
<ruby>
# Declares an attribute reader backed by an internally-named instance variable.
@@ -23,7 +23,7 @@ def attr_internal_reader(*attrs)
end
</ruby>
-Communicate to the reader the current way of doing things, both explicitly and implicitly. Use the recommended idioms in edge, reorder sections to emphasize favored approaches if needed, etc. The documentation should be a model for best practices and canonical, modern Rails usage.
+Communicate to the reader the current way of doing things, both explicitly and implicitly. Use the idioms recommended in edge. Reorder sections to emphasize favored approaches if needed, etc. The documentation should be a model for best practices and canonical, modern Rails usage.
Documentation has to be concise but comprehensive. Explore and document edge cases. What happens if a module is anonymous? What if a collection is empty? What if an argument is nil?
@@ -41,10 +41,9 @@ h3. Example Code
Choose meaningful examples that depict and cover the basics as well as interesting points or gotchas.
-Use two spaces to indent chunks of code--that is two spaces with respect to the left margin; the examples
-themselves should use "Rails coding conventions":contributing_to_ruby_on_rails.html#follow-the-coding-conventions.
+Use two spaces to indent chunks of code--that is, for markup purposes, two spaces with respect to the left margin. The examples themselves should use "Rails coding conventions":contributing_to_ruby_on_rails.html#follow-the-coding-conventions.
-Short docs do not need an explicit "Examples" label to introduce snippets, they just follow paragraphs:
+Short docs do not need an explicit "Examples" label to introduce snippets; they just follow paragraphs:
<ruby>
# Converts a collection of elements into a formatted string by calling
@@ -64,7 +63,7 @@ On the other hand, big chunks of structured documentation may have a separate "E
# Person.exists?(['name LIKE ?', "%#{query}%"])
</ruby>
-The result of expressions follow them and are introduced by "# => ", vertically aligned:
+The results of expressions follow them and are introduced by "# => ", vertically aligned:
<ruby>
# For checking if a fixnum is even or odd.
@@ -98,7 +97,7 @@ On the other hand, regular comments do not use an arrow:
h3. Filenames
-As a rule of thumb use filenames relative to the application root:
+As a rule of thumb, use filenames relative to the application root:
<plain>
config/routes.rb # YES
@@ -111,12 +110,12 @@ h3. Fonts
h4. Fixed-width Font
Use fixed-width fonts for:
-* constants, in particular class and module names
-* method names
-* literals like +nil+, +false+, +true+, +self+
-* symbols
-* method parameters
-* file names
+* Constants, in particular class and module names.
+* Method names.
+* Literals like +nil+, +false+, +true+, +self+.
+* Symbols.
+* Method parameters.
+* File names.
<ruby>
class Array
@@ -135,10 +134,17 @@ h4. Regular Font
When "true" and "false" are English words rather than Ruby keywords use a regular font:
<ruby>
-# If <tt>reload_plugins?</tt> is false, add this to your plugin's <tt>init.rb</tt>
-# to make it reloadable:
+# Runs all the validations within the specified context. Returns true if no errors are found,
+# false otherwise.
#
-# Dependencies.load_once_paths.delete lib_path
+# If the argument is false (default is +nil+), the context is set to <tt>:create</tt> if
+# <tt>new_record?</tt> is true, and to <tt>:update</tt> if it is not.
+#
+# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
+# some <tt>:on</tt> option will only run in the specified context.
+def valid?(context = nil)
+ ...
+end
</ruby>
h3. Description Lists
@@ -153,7 +159,7 @@ The description starts in upper case and ends with a full stop—it's standard E
h3. Dynamically Generated Methods
-Methods created with +(module|class)_eval(STRING)+ have a comment by their side with an instance of the generated code. That comment is 2 spaces apart from the template:
+Methods created with +(module|class)_eval(STRING)+ have a comment by their side with an instance of the generated code. That comment is 2 spaces away from the template:
<ruby>
for severity in Severity.constants
@@ -169,7 +175,7 @@ for severity in Severity.constants
end
</ruby>
-If the resulting lines are too wide, say 200 columns or more, we put the comment above the call:
+If the resulting lines are too wide, say 200 columns or more, put the comment above the call:
<ruby>
# def self.find_by_login_and_activated(*args)
diff --git a/railties/guides/source/asset_pipeline.textile b/guides/source/asset_pipeline.textile
index afaf0f6fe3..105efe229e 100644
--- a/railties/guides/source/asset_pipeline.textile
+++ b/guides/source/asset_pipeline.textile
@@ -6,7 +6,7 @@ By referring to this guide you will be able to:
* Understand what the asset pipeline is and what it does
* Properly organize your application assets
* Understand the benefits of the asset pipeline
-* Adding a pre-processor to the pipeline
+* Add a pre-processor to the pipeline
* Package assets with a gem
endprologue.
@@ -17,7 +17,7 @@ The asset pipeline provides a framework to concatenate and minify or compress Ja
Prior to Rails 3.1 these features were added through third-party Ruby libraries such as Jammit and Sprockets. Rails 3.1 is integrated with Sprockets through Action Pack which depends on the +sprockets+ gem, by default.
-By having this as a core feature of Rails, all developers can benefit from the power of having their assets pre-processed, compressed and minified by one central library, Sprockets. This is part of Rails' "fast by default" strategy as outlined by DHH in his keynote at RailsConf 2011.
+Making the asset pipeline a core feature of Rails means that all developers can benefit from the power of having their assets pre-processed, compressed and minified by one central library, Sprockets. This is part of Rails' "fast by default" strategy as outlined by DHH in his keynote at RailsConf 2011.
In Rails 3.1, the asset pipeline is enabled by default. It can be disabled in +config/application.rb+ by putting this line inside the application class definition:
@@ -25,34 +25,34 @@ In Rails 3.1, the asset pipeline is enabled by default. It can be disabled in +c
config.assets.enabled = false
</ruby>
-You can also disable it while creating a new application by passing the <tt>--skip-sprockets</tt> option.
+You can also disable the asset pipeline while creating a new application by passing the <tt>--skip-sprockets</tt> option.
<plain>
rails new appname --skip-sprockets
</plain>
-It is recommended that you use the defaults for all new apps.
+You should use the defaults for all new applications unless you have a specific reason to avoid the asset pipeline.
h4. Main Features
-The first feature of the pipeline is to concatenate assets. This is important in a production environment, as it reduces the number of requests that a browser must make to render a web page.
+The first feature of the pipeline is to concatenate assets. This is important in a production environment, because it can reduce the number of requests that a browser must make to render a web page. Web browsers are limited in the number of requests that they can make in parallel, so fewer requests can mean faster loading for your application.
-While Rails already has a feature to concatenate these types of assets by placing +:cache => true+ at the end of tags such as +javascript_include_tag+ and +stylesheet_link_tag+, it has a series of limitations. For example, it cannot generate the caches in advance, and it is not able to transparently include assets provided by third-party libraries.
+Rails 2.x introduced the ability to concatenate JavaScript and CSS assets by placing +:cache => true+ at the end of the +javascript_include_tag+ and +stylesheet_link_tag+ methods. But this technique has some limitations. For example, it cannot generate the caches in advance, and it is not able to transparently include assets provided by third-party libraries.
-The default behavior in Rails 3.1 and onward is to concatenate all files into one master file each for JS and CSS. However, you can separate files or groups of files if required (see below). In production, an MD5 fingerprint is inserted into each filename so that the file is cached by the web browser but can be invalidated if the fingerprint is altered.
+Starting with version 3.1, Rails defaults to concatenating all JavaScript files into one master +.js+ file and all CSS files into one master +.css+ file. As you'll learn later in this guide, you can customize this strategy to group files any way you like. In production, Rails inserts an MD5 fingerprint into each filename so that the file is cached by the web browser. You can invalidate the cache by altering this fingerprint, which happens automatically whenever you change the file contents..
-The second feature is to minify or compress assets. For CSS, this usually involves removing whitespace and comments. For JavaScript, more complex processes can be applied. You can choose from a set of built in options or specify your own.
+The second feature of the asset pipeline is asset minification or compression. For CSS files, this is done by removing whitespace and comments. For JavaScript, more complex processes can be applied. You can choose from a set of built in options or specify your own.
-The third feature is the ability to code these assets using another language, or language extension. These include Sass for CSS, CoffeeScript for JavaScript, and ERB for both.
+The third feature of the asset pipeline is that it allows coding assets via a higher-level language, with precompilation down to the actual assets. Supported languages include Sass for CSS, CoffeeScript for JavaScript, and ERB for both by default.
h4. What is Fingerprinting and Why Should I Care?
-Fingerprinting is a technique whereby the filenames of content that is static or infrequently updated are altered to be unique to the content contained in the file.
+Fingerprinting is a technique that makes the name of a file dependent on the contents of the file. When the file contents change, the filename is also changed. For content that is static or infrequently changed, this provides an easy way to tell whether two versions of a file are identical, even across different servers or deployment dates.
-When a filename is unique and based on its content, HTTP headers can be set to encourage caches everywhere (at ISPs, in browsers) to keep their own copy of the content. When the content is updated, the fingerprint will change and the remote clients will request the new file. This is generally known as _cache busting_.
+When a filename is unique and based on its content, HTTP headers can be set to encourage caches everywhere (whether at CDNs, at ISPs, in networking equipment, or in web browsers) to keep their own copy of the content. When the content is updated, the fingerprint will change. This will cause the remote clients to request a new copy of the content. This is generally known as _cache busting_.
-The most effective technique is to insert a hash of the content into the name, usually at the end. For example a CSS file +global.css+ is hashed and the filename is updated to incorporate the digest, for example becoming:
+The technique that Rails uses for fingerprinting is to insert a hash of the content into the name, usually at the end. For example a CSS file +global.css+ could be renamed with an MD5 digest of its contents:
<plain>
global-908e25f4bf641868d8683022a5b62f54.css
@@ -60,30 +60,32 @@ global-908e25f4bf641868d8683022a5b62f54.css
This is the strategy adopted by the Rails asset pipeline.
-Rails' old strategy was to append a query string to every asset linked with a built-in helper. In the source the generated code looked like this:
+Rails' old strategy was to append a date-based query string to every asset linked with a built-in helper. In the source the generated code looked like this:
<plain>
/stylesheets/global.css?1309495796
</plain>
-This has several disadvantages:
+The query string strategy has several disadvantages:
<ol>
<li>
- <strong>Not all caches will cache content with a query string</strong>.<br>
+ <strong>Not all caches will reliably cache content where the filename only differs by query parameters</strong>.<br>
"Steve Souders recommends":http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/, "...avoiding a querystring for cacheable resources". He found that in this case 5-20% of requests will not be cached. Query strings in particular do not work at all with some CDNs for cache invalidation.
</li>
<li>
<strong>The file name can change between nodes in multi-server environments.</strong><br>
- The query string in Rails is based on the modification time of the files. When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request.
+ The default query string in Rails 2.x is based on the modification time of the files. When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request.
+ </li>
+ <li>
+ <strong>Too much cache invalidation</strong><br />
+ When static assets are deployed with each new release of code, the mtime of _all_ these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed.
</li>
</ol>
-The other problem is that when static assets are deployed with each new release of code, the mtime of _all_ these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed.
+Fingerprinting fixes these problems by avoiding query strings, and by ensuring that filenames are consistent based on their content.
-Fingerprinting fixes these problems by avoiding query strings, and by ensuring filenames are consistent based on their content.
-
-Fingerprinting is enabled by default for production and disabled for all the others environments. You can enable or disable it in your configuration through the +config.assets.digest+ option.
+Fingerprinting is enabled by default for production and disabled for all other environments. You can enable or disable it in your configuration through the +config.assets.digest+ option.
More reading:
@@ -95,34 +97,86 @@ h3. How to Use the Asset Pipeline
In previous versions of Rails, all assets were located in subdirectories of +public+ such as +images+, +javascripts+ and +stylesheets+. With the asset pipeline, the preferred location for these assets is now the +app/assets+ directory. Files in this directory are served by the Sprockets middleware included in the sprockets gem.
-This is not to say that assets can (or should) no longer be placed in +public+; they still can be and will be served as static files by the application or web server. You would only use +app/assets+ if you wish your files to undergo some pre-processing before they are served.
+Assets can still be placed in the +public+ hierarchy. Any assets under +public+ will be served as static files by the application or web server. You should use +app/assets+ for files that must undergo some pre-processing before they are served.
-In production, the default is to precompile these files to +public/assets+ so that they can be more efficiently delivered by the web server.
+In production, Rails precompiles these files to +public/assets+ by default. The precompiled copies are then served as static assets by the web server. The files in +app/assets+ are never served directly in production.
-When a scaffold or controller is generated for the application, Rails also generates a JavaScript file (or CoffeeScript file if the +coffee-rails+ gem is in the +Gemfile+) and a Cascading Style Sheet file (or SCSS file if +sass-rails+ is in the +Gemfile+) for that controller.
+When you generate a scaffold or a controller, Rails also generates a JavaScript file (or CoffeeScript file if the +coffee-rails+ gem is in the +Gemfile+) and a Cascading Style Sheet file (or SCSS file if +sass-rails+ is in the +Gemfile+) for that controller.
-For example, if a +ProjectsController+ is generated, there will be a new file at +app/assets/javascripts/projects.js.coffee+ and another at +app/assets/stylesheets/projects.css.scss+. You should put any JavaScript or CSS unique to a controller inside their respective asset files, as these files can then be loaded just for these controllers with lines such as +<%= javascript_include_tag params[:controller] %>+ or +<%= stylesheet_link_tag params[:controller] %>+.
+For example, if you generate a +ProjectsController+, Rails will also add a new file at +app/assets/javascripts/projects.js.coffee+ and another at +app/assets/stylesheets/projects.css.scss+. You should put any JavaScript or CSS unique to a controller inside their respective asset files, as these files can then be loaded just for these controllers with lines such as +<%= javascript_include_tag params[:controller] %>+ or +<%= stylesheet_link_tag params[:controller] %>+.
-NOTE: You will need a "ExecJS":https://github.com/sstephenson/execjs#readme supported runtime in order to use CoffeeScript. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. Check "ExecJS":https://github.com/sstephenson/execjs#readme documentation to know all supported JavaScript runtimes.
+NOTE: You must have an "ExecJS":https://github.com/sstephenson/execjs#readme supported runtime in order to use CoffeeScript. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. Check "ExecJS":https://github.com/sstephenson/execjs#readme documentation to know all supported JavaScript runtimes.
h4. Asset Organization
-Assets can be placed inside an application in one of three locations: +app/assets+, +lib/assets+ or +vendor/assets+.
+Pipeline assets can be placed inside an application in one of three locations: +app/assets+, +lib/assets+ or +vendor/assets+.
+app/assets+ is for assets that are owned by the application, such as custom images, JavaScript files or stylesheets.
+lib/assets+ is for your own libraries' code that doesn't really fit into the scope of the application or those libraries which are shared across applications.
-+vendor/assets+ is for assets that are owned by outside entities, such as code for JavaScript plugins.
++vendor/assets+ is for assets that are owned by outside entities, such as code for JavaScript plugins and CSS frameworks.
+
+h5. Search paths
+
+When a file is referenced from a manifest or a helper, Sprockets searches the three default asset locations for it.
+
+The default locations are: +app/assets/images+ and the subdirectories +javascripts+ and +stylesheets+ in all three asset locations.
+
+For example, these files:
-All subdirectories that exist within these three locations are added to the search path for Sprockets (visible by calling +Rails.application.config.assets.paths+ in a console). When an asset is requested, these paths are traversed to see if they contain an asset matching the name specified. Once an asset has been found, it's processed by Sprockets and served.
+<plain>
+app/assets/javascripts/home.js
+lib/assets/javascripts/moovinator.js
+vendor/assets/javascripts/slider.js
+</plain>
-You can add additional (fully qualified) paths to the pipeline in +config/application.rb+. For example:
+would be referenced in a manifest like this:
+
+<plain>
+//= require home
+//= require moovinator
+//= require slider
+</plain>
+
+Assets inside subdirectories can also be accessed.
+
+<plain>
+app/assets/javascripts/sub/something.js
+</plain>
+
+is referenced as:
+
+<plain>
+//= require sub/something
+</plain>
+
+You can view the search path by inspecting +Rails.application.config.assets.paths+ in the Rails console.
+
+Additional (fully qualified) paths can be added to the pipeline in +config/application.rb+. For example:
<ruby>
config.assets.paths << Rails.root.join("app", "assets", "flash")
</ruby>
+Paths are traversed in the order that they occur in the search path.
+
+It is important to note that files you want to reference outside a manifest must be added to the precompile array or they will not be available in the production environment.
+
+h5. Using index files
+
+Sprockets uses files named +index+ (with the relevant extensions) for a special purpose.
+
+For example, if you have a jQuery library with many modules, which is stored in +lib/assets/library_name+, the file +lib/assets/library_name/index.js+ serves as the manifest for all files in this library. This file could include a list of all the required files in order, or a simple <tt>require_tree</tt> directive.
+
+The library as a whole can be accessed in the site's application manifest like so:
+
+<plain>
+//= require library_name
+</plain>
+
+This simplifies maintenance and keeps things clean by allowing related code to be grouped before inclusion elsewhere.
+
h4. Coding Links to Assets
Sprockets does not add any new methods to access your assets - you still use the familiar +javascript_include_tag+ and +stylesheet_link_tag+.
@@ -138,7 +192,7 @@ In regular views you can access images in the +assets/images+ directory like thi
<%= image_tag "rails.png" %>
</erb>
-Provided that the pipeline is enabled within your application (and not disabled in the current environment context), this file is served by Sprockets. If a file exists at +public/assets/rails.png+ it is served by the webserver.
+Provided that the pipeline is enabled within your application (and not disabled in the current environment context), this file is served by Sprockets. If a file exists at +public/assets/rails.png+ it is served by the web server.
Alternatively, a request for a file with an MD5 hash such as +public/assets/rails-af27b6a414e6da00003503148be9b409.png+ is treated the same way. How these hashes are generated is covered in the "In Production":#in-production section later on in this guide.
@@ -150,9 +204,11 @@ Images can also be organized into subdirectories if required, and they can be ac
<%= image_tag "icons/rails.png" %>
</erb>
+WARNING: If you're precompiling your assets (see "In Production":#in-production below), linking to an asset that does not exist will raise an exception in the calling page. This includes linking to a blank string. As such, be careful using <tt>image_tag</tt> and the other helpers with user-supplied data.
+
h5. CSS and ERB
-If you add an +erb+ extension to a CSS asset, making it something such as +application.css.erb+, then helpers like +asset_path+ are available in your CSS rules:
+The asset pipeline automatically evaluates ERB. This means that if you add an +erb+ extension to a CSS asset (for example, +application.css.erb+), then helpers like +asset_path+ are available in your CSS rules:
<plain>
.class { background-image: url(<%= asset_path 'image.png' %>) }
@@ -194,7 +250,7 @@ $('#logo').attr({
This writes the path to the particular asset being referenced.
-Similarly, you can use the +asset_path+ helper in CoffeeScript files with +erb+ extension (eg. +application.js.coffee.erb+):
+Similarly, you can use the +asset_path+ helper in CoffeeScript files with +erb+ extension (e.g., +application.js.coffee.erb+):
<plain>
$('#logo').attr src: "<%= asset_path('logo.png') %>"
@@ -202,9 +258,9 @@ $('#logo').attr src: "<%= asset_path('logo.png') %>"
h4. Manifest Files and Directives
-Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ -- instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if necessary, concatenates them into one single file and then compresses them (if +Rails.application.config.assets.compress+ is true). By serving one file rather than many, the load time of pages are greatly reduced as there are fewer requests to make.
+Sprockets uses manifest files to determine which assets to include and serve. These manifest files contain _directives_ -- instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if necessary, concatenates them into one single file and then compresses them (if +Rails.application.config.assets.compress+ is true). By serving one file rather than many, the load time of pages can be greatly reduced because the browser makes fewer requests.
-For example, in the default Rails application there's a +app/assets/javascripts/application.js+ file which contains the following lines:
+For example, a new Rails application includes a default +app/assets/javascripts/application.js+ file which contains the following lines:
<plain>
// ...
@@ -217,11 +273,11 @@ In JavaScript files, the directives begin with +//=+. In this case, the file is
NOTE. In Rails 3.1 the +jquery-rails+ gem provides the +jquery.js+ and +jquery_ujs.js+ files via the asset pipeline. You won't see them in the application tree.
-The +require_tree+ directive tells Sprockets to recursively include _all_ JavaScript files in this directory into the output. Only a path relative to the manifest file can be specified. There is also a +require_directory+ directive which includes all JavaScript files only in the directory specified (no nesting).
+The +require_tree+ directive tells Sprockets to recursively include _all_ JavaScript files in the specified directory into the output. These paths must be specified relative to the manifest file. You can also use the +require_directory+ directive which includes all JavaScript files only in the directory specified, without recursion.
-Directives are processed top to bottom, but the order in which files are included by +require_tree+ is unspecified. You should not rely on any particular order among those. If you need to ensure some particular JavaScript ends up above some other, require it before in the manifest. Note that the family of +require+ directives prevents files from being included twice in the output.
+Directives are processed top to bottom, but the order in which files are included by +require_tree+ is unspecified. You should not rely on any particular order among those. If you need to ensure some particular JavaScript ends up above some other in the concatenated file, require the prerequisite file first in the manifest. Note that the family of +require+ directives prevents files from being included twice in the output.
-There's also a default +app/assets/stylesheets/application.css+ file which contains these lines:
+Rails also creates a default +app/assets/stylesheets/application.css+ file which contains these lines:
<plain>
/* ...
@@ -230,15 +286,15 @@ There's also a default +app/assets/stylesheets/application.css+ file which conta
*/
</plain>
-The directives that work in the JavaScript files also work in stylesheets, obviously including stylesheets rather than JavaScript files. The +require_tree+ directive here works the same way as the JavaScript one, requiring all stylesheets from the current directory.
+The directives that work in the JavaScript files also work in stylesheets (though obviously including stylesheets rather than JavaScript files). The +require_tree+ directive in a CSS manifest works the same way as the JavaScript one, requiring all stylesheets from the current directory.
In this example +require_self+ is used. This puts the CSS contained within the file (if any) at the precise location of the +require_self+ call. If +require_self+ is called more than once, only the last call is respected.
-NOTE. If you want to use multiple Sass files, use the "Sass +@import+ rule":http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import instead of the Sprockets directives. Using Sprockets directives all Sass files exist within their own scope, making variables or mixins only available within the document they were defined in.
+NOTE. If you want to use multiple Sass files, you should generally use the "Sass +@import+ rule":http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import instead of these Sprockets directives. Using Sprockets directives all Sass files exist within their own scope, making variables or mixins only available within the document they were defined in.
You can have as many manifest files as you need. For example the +admin.css+ and +admin.js+ manifest could contain the JS and CSS files that are used for the admin section of an application.
-The same remarks about ordering made above apply. In particular, you can specify individual files and they are compiled in the order specified:
+The same remarks about ordering made above apply. In particular, you can specify individual files and they are compiled in the order specified. For example, you might concatenate three CSS files together this way:
<plain>
/* ...
@@ -253,15 +309,15 @@ h4. Preprocessing
The file extensions used on an asset determine what preprocessing is applied. When a controller or a scaffold is generated with the default Rails gemset, a CoffeeScript file and a SCSS file are generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which generated an +app/assets/javascripts/projects.js.coffee+ and an +app/assets/stylesheets/projects.css.scss+ file.
-When these files are requested, they are processed by the processors provided by the +coffee-script+ and +sass-rails+ gems and then sent back to the browser as JavaScript and CSS respectively.
+When these files are requested, they are processed by the processors provided by the +coffee-script+ and +sass+ gems and then sent back to the browser as JavaScript and CSS respectively.
-Additional layers of preprocessing can be requested by adding other extensions, where each extension is processed in a right-to-left manner. These should be used in the order the processing should be applied. For example, a stylesheet called +app/assets/stylesheets/projects.css.scss.erb+ is first processed as ERB, then SCSS and finally served as CSS. The same applies to a JavaScript file -- +app/assets/javascripts/projects.js.coffee.erb+ is processed as ERB, CoffeeScript, and served as JavaScript.
+Additional layers of preprocessing can be requested by adding other extensions, where each extension is processed in a right-to-left manner. These should be used in the order the processing should be applied. For example, a stylesheet called +app/assets/stylesheets/projects.css.scss.erb+ is first processed as ERB, then SCSS, and finally served as CSS. The same applies to a JavaScript file -- +app/assets/javascripts/projects.js.coffee.erb+ is processed as ERB, then CoffeeScript, and served as JavaScript.
Keep in mind that the order of these preprocessors is important. For example, if you called your JavaScript file +app/assets/javascripts/projects.js.erb.coffee+ then it would be processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore you would run into problems.
h3. In Development
-In development mode assets are served as separate files in the order they are specified in the manifest file.
+In development mode, assets are served as separate files in the order they are specified in the manifest file.
This manifest +app/assets/javascripts/application.js+:
@@ -274,9 +330,9 @@ This manifest +app/assets/javascripts/application.js+:
would generate this HTML:
<html>
-<script src="/assets/core.js?body=1" type="text/javascript"></script>
-<script src="/assets/projects.js?body=1" type="text/javascript"></script>
-<script src="/assets/tickets.js?body=1" type="text/javascript"></script>
+<script src="/assets/core.js?body=1"></script>
+<script src="/assets/projects.js?body=1"></script>
+<script src="/assets/tickets.js?body=1"></script>
</html>
The +body+ param is required by Sprockets.
@@ -289,10 +345,10 @@ You can turn off debug mode by updating +config/environments/development.rb+ to
config.assets.debug = false
</ruby>
-When debug mode is off Sprockets concatenates and runs the necessary preprocessors on all files. With debug mode turned off the manifest above would generate instead:
+When debug mode is off, Sprockets concatenates and runs the necessary preprocessors on all files. With debug mode turned off the manifest above would generate instead:
<html>
-<script src="/assets/application.js" type="text/javascript"></script>
+<script src="/assets/application.js"></script>
</html>
Assets are compiled and cached on the first request after the server is started. Sprockets sets a +must-revalidate+ Cache-Control HTTP header to reduce request overhead on subsequent requests -- on these the browser gets a 304 (Not Modified) response.
@@ -312,7 +368,7 @@ You could potentially also enable compression in development mode as a sanity ch
h3. In Production
-In the production environment Rails uses the fingerprinting scheme outlined above. By default it is assumed that assets have been precompiled and will be served as static assets by your web server.
+In the production environment Rails uses the fingerprinting scheme outlined above. By default Rails assumes that assets have been precompiled and will be served as static assets by your web server.
During the precompilation phase an MD5 is generated from the contents of the compiled files, and inserted into the filenames as they are written to disc. These fingerprinted names are used by the Rails helpers in place of the manifest name.
@@ -326,11 +382,11 @@ For example this:
generates something like this:
<html>
-<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js" type="text/javascript"></script>
-<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" media="screen" rel="stylesheet" type="text/css" />
+<script src="/assets/application-908e25f4bf641868d8683022a5b62f54.js"></script>
+<link href="/assets/application-4dd5b109ee3439da54f5bdfd78a80473.css" media="screen" rel="stylesheet" />
</html>
-The fingerprinting behavior is controlled by the setting of +config.assets.digest+ setting in Rails (which is +true+ for production, +false+ for everything else).
+The fingerprinting behavior is controlled by the setting of +config.assets.digest+ setting in Rails (which defaults to +true+ for production and +false+ for everything else).
NOTE: Under normal circumstances the default option should not be changed. If there are no digests in the filenames, and far-future headers are set, remote clients will never know to refetch the files when their content changes.
@@ -338,9 +394,9 @@ h4. Precompiling Assets
Rails comes bundled with a rake task to compile the asset manifests and other files in the pipeline to the disk.
-Compiled assets are written to the location specified in +config.assets.prefix+. The default setting will use the +public/assets+ directory.
+Compiled assets are written to the location specified in +config.assets.prefix+. By default, this is the +public/assets+ directory.
-You must use this task either during deployment or locally if you do not have write access to your production filesystem.
+You can call this task on the server during deployment to create compiled versions of your assets directly on the server. See the next section for information on compiling locally.
The rake task is:
@@ -349,15 +405,17 @@ bundle exec rake assets:precompile
</plain>
For faster asset precompiles, you can partially load your application by setting
-+config.assets.initialize_on_precompile+ to false, though in that case templates
++config.assets.initialize_on_precompile+ to false in +config/application.rb+, though in that case templates
cannot see application objects or methods. *Heroku requires this to be false.*
WARNING: If you set +config.assets.initialize_on_precompile+ to false, be sure to
test +rake assets:precompile+ locally before deploying. It may expose bugs where
your assets reference application objects or methods, since those are still
-in scope in development mode regardless of the value of this flag.
+in scope in development mode regardless of the value of this flag. Changing this flag also affects
+engines. Engines can define assets for precompilation as well. Since the complete environment is not loaded,
+engines (or other gems) will not be loaded, which can cause missing assets.
-Capistrano (v2.8.0 and above) has a recipe to handle this in deployment. Add the following line to +Capfile+:
+Capistrano (v2.8.0 and above) includes a recipe to handle this in deployment. Add the following line to +Capfile+:
<erb>
load 'deploy/assets'
@@ -369,19 +427,21 @@ It is important that this folder is shared between deployments so that remotely
NOTE. If you are precompiling your assets locally, you can use +bundle install --without assets+ on the server to avoid installing the assets gems (the gems in the assets group in the Gemfile).
-The default matcher for compiling files includes +application.js+, +application.css+ and all files that do not end in +js+ or +css+:
+The default matcher for compiling files includes +application.js+, +application.css+ and all non-JS/CSS files (this will include all image assets automatically):
<ruby>
-[ /\w<plus>\.(?!js|css).<plus>/, /application.(css|js)$/ ]
+[ Proc.new{ |path| !File.extname(path).in?(['.js', '.css']) }, /application.(css|js)$/ ]
</ruby>
+NOTE. The matcher (and other members of the precompile array; see below) is applied to final compiled file names. This means that anything that compiles to JS/CSS is excluded, as well as raw JS/CSS files; for example, +.coffee+ and +.scss+ files are *not* automatically included as they compile to JS/CSS.
+
If you have other manifests or individual stylesheets and JavaScript files to include, you can add them to the +precompile+ array:
<erb>
config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
</erb>
-The rake task also generates a +manifest.yml+ that contains a list with all your assets and their respective fingerprints. This is used by the Rails helper methods and avoids handing the mapping requests back to Sprockets. A typical manifest file looks like:
+The rake task also generates a +manifest.yml+ that contains a list with all your assets and their respective fingerprints. This is used by the Rails helper methods to avoid handing the mapping requests back to Sprockets. A typical manifest file looks like:
<plain>
---
@@ -402,18 +462,15 @@ config.assets.manifest = '/path/to/some/other/location'
NOTE: If there are missing precompiled files in production you will get an <tt>Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError</tt> exception indicating the name of the missing file(s).
-h5. Server Configuration
+h5. Far-future Expires header
-Precompiled assets exist on the filesystem and are served directly by your webserver. They do not have far-future headers by default, so to get the benefit of fingerprinting you'll have to update your server configuration to add them.
+Precompiled assets exist on the filesystem and are served directly by your web server. They do not have far-future headers by default, so to get the benefit of fingerprinting you'll have to update your server configuration to add them.
For Apache:
<plain>
<LocationMatch "^/assets/.*$">
- # Some browsers still send conditional-GET requests if there's a
- # Last-Modified header or an ETag header even if they haven't
- # reached the expiry date sent in the Expires header.
- Header unset Last-Modified
+ # Use of ETag is discouraged when Last-Modified is present
Header unset ETag
FileETag None
# RFC says only cache for 1 year
@@ -429,16 +486,14 @@ location ~ ^/assets/ {
expires 1y;
add_header Cache-Control public;
- # Some browsers still send conditional-GET requests if there's a
- # Last-Modified header or an ETag header even if they haven't
- # reached the expiry date sent in the Expires header.
- add_header Last-Modified "";
add_header ETag "";
break;
}
</plain>
-When files are precompiled, Sprockets also creates a "gzipped":http://en.wikipedia.org/wiki/Gzip (.gz) version of your assets. Web servers are typically configured to use a moderate compression ratio as a compromise, but since precompilation happens once Sprockets uses the maximum compression ratio, thus reducing the size of the data transfer to the minimum. One the other hand, web servers can be configured to serve compressed content directly from disk, rather than deflating non-compressed files themselves.
+h5. GZip compression
+
+When files are precompiled, Sprockets also creates a "gzipped":http://en.wikipedia.org/wiki/Gzip (.gz) version of your assets. Web servers are typically configured to use a moderate compression ratio as a compromise, but since precompilation happens once, Sprockets uses the maximum compression ratio, thus reducing the size of the data transfer to the minimum. On the other hand, web servers can be configured to serve compressed content directly from disk, rather than deflating non-compressed files themselves.
Nginx is able to do this automatically enabling +gzip_static+:
@@ -459,7 +514,42 @@ This directive is available if the core module that provides this feature was co
If you're compiling nginx with Phusion Passenger you'll need to pass that option when prompted.
-Unfortunately, a robust configuration for Apache is possible but tricky, please Google around.
+A robust configuration for Apache is possible but tricky; please Google around. (Or help update this Guide if you have a good example configuration for Apache.)
+
+h4. Local Precompilation
+
+There are several reasons why you might want to precompile your assets locally. Among them are:
+
+* You may not have write access to your production file system.
+* You may be deploying to more than one server, and want to avoid the duplication of work.
+* You may be doing frequent deploys that do not include asset changes.
+
+Local compilation allows you to commit the compiled files into source control, and deploy as normal.
+
+There are two caveats:
+
+* You must not run the Capistrano deployment task that precompiles assets.
+* You must change the following two application configuration settings.
+
+In <tt>config/environments/development.rb</tt>, place the following line:
+
+<erb>
+config.assets.prefix = "/dev-assets"
+</erb>
+
+You will also need this in application.rb:
+
+<erb>
+config.assets.initialize_on_precompile = false
+</erb>
+
+The +prefix+ change makes Rails use a different URL for serving assets in development mode, and pass all requests to Sprockets. The prefix is still set to +/assets+ in the production environment. Without this change, the application would serve the precompiled assets from +public/assets+ in development, and you would not see any local changes until you compile assets again.
+
+The +initialize_on_precompile+ change tells the precompile task to run without invoking Rails. This is because the precompile task runs in production mode by default, and will attempt to connect to your specified production database. Please note that you cannot have code in pipeline files that relies on Rails resources (such as the database) when compiling locally with this option.
+
+You will also need to ensure that any compressors or minifiers are available on your development system.
+
+In practice, this will allow you to precompile locally, have those files in your working tree, and commit those files to source control when needed. Development mode will work as expected.
h4. Live Compilation
@@ -475,9 +565,9 @@ On the first request the assets are compiled and cached as outlined in developme
Sprockets also sets the +Cache-Control+ HTTP header to +max-age=31536000+. This signals all caches between your server and the client browser that this content (the file served) can be cached for 1 year. The effect of this is to reduce the number of requests for this asset from your server; the asset has a good chance of being in the local browser cache or some intermediate cache.
-This mode uses more memory, performs poorer than the default and is not recommended.
+This mode uses more memory, performs more poorly than the default and is not recommended.
-When deploying a production application to a system without any pre-existing JavaScript runtimes, you may want to add one to your Gemfile:
+If you are deploying a production application to a system without any pre-existing JavaScript runtimes, you may want to add one to your Gemfile:
<plain>
group :production do
@@ -501,9 +591,9 @@ The +config.assets.compress+ must be set to +true+ to enable CSS compression.
h4. JavaScript Compression
-Possible options for JavaScript compression are +:closure+, +:uglifier+ and +:yui+. These require the use of the +closure-compiler+, +uglifier+ or +yui-compressor+ gems respectively.
+Possible options for JavaScript compression are +:closure+, +:uglifier+ and +:yui+. These require the use of the +closure-compiler+, +uglifier+ or +yui-compressor+ gems, respectively.
-The default Gemfile includes "uglifier":https://github.com/lautis/uglifier. This gem wraps "UglifierJS":https://github.com/mishoo/UglifyJS (written for NodeJS) in Ruby. It compresses your code by removing white space and other magical things like changing your +if+ and +else+ statements to ternary operators where possible.
+The default Gemfile includes "uglifier":https://github.com/lautis/uglifier. This gem wraps "UglifierJS":https://github.com/mishoo/UglifyJS (written for NodeJS) in Ruby. It compresses your code by removing white space. It also includes other optimizations such as changing your +if+ and +else+ statements to ternary operators where possible.
The following line invokes +uglifier+ for JavaScript compression.
@@ -511,13 +601,13 @@ The following line invokes +uglifier+ for JavaScript compression.
config.assets.js_compressor = :uglifier
</erb>
-The +config.assets.compress+ must be set to +true+ to enable JavaScript compression
+Note that +config.assets.compress+ must be set to +true+ to enable JavaScript compression
-NOTE: You will need a "ExecJS":https://github.com/sstephenson/execjs#readme supported runtime in order to use +uglifier+. If you are using Mac OS X or Windows you have installed a JavaScript runtime in your operating system. Check "ExecJS":https://github.com/sstephenson/execjs#readme documentation to know all supported JavaScript runtimes.
+NOTE: You will need an "ExecJS":https://github.com/sstephenson/execjs#readme supported runtime in order to use +uglifier+. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. Check the "ExecJS":https://github.com/sstephenson/execjs#readme documentation for information on all of the supported JavaScript runtimes.
h4. Using Your Own Compressor
-The compressor config settings for CSS and JavaScript also take any Object. This object must have a +compress+ method that takes a string as the sole argument and it must return a string.
+The compressor config settings for CSS and JavaScript also take any object. This object must have a +compress+ method that takes a string as the sole argument and it must return a string.
<erb>
class Transformer
@@ -527,7 +617,7 @@ class Transformer
end
</erb>
-To enable this, pass a +new+ Object to the config option in +application.rb+:
+To enable this, pass a +new+ object to the config option in +application.rb+:
<erb>
config.assets.css_compressor = Transformer.new
@@ -544,20 +634,20 @@ This can be changed to something else:
config.assets.prefix = "/some_other_path"
</erb>
-This is a handy option if you have any existing project (pre Rails 3.1) that already uses this path or you wish to use this path for a new resource.
+This is a handy option if you are updating an existing project (pre Rails 3.1) that already uses this path or you wish to use this path for a new resource.
h4. X-Sendfile Headers
-The X-Sendfile header is a directive to the server to ignore the response from the application, and instead serve the file specified in the headers. This option is off by default, but can be enabled if your server supports it. When enabled, this passes responsibility for serving the file to the web server, which is faster.
+The X-Sendfile header is a directive to the web server to ignore the response from the application, and instead serve a specified file from disk. This option is off by default, but can be enabled if your server supports it. When enabled, this passes responsibility for serving the file to the web server, which is faster.
-Apache and nginx support this option which is enabled in <tt>config/environments/production.rb</tt>.
+Apache and nginx support this option, which can be enabled in <tt>config/environments/production.rb</tt>.
<erb>
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
</erb>
-WARNING: If you are upgrading an existing application and intend to use this option, take care to paste this configuration option only into +production.rb+ (and not +application.rb+) and any other environment you define with production behavior.
+WARNING: If you are upgrading an existing application and intend to use this option, take care to paste this configuration option only into +production.rb+ and any other environments you define with production behavior (not +application.rb+).
h3. How Caching Works
@@ -577,7 +667,7 @@ TODO: Registering gems on "Tilt":https://github.com/rtomayko/tilt enabling Sproc
h3. Upgrading from Old Versions of Rails
-There are two issues when upgrading. The first is moving the files to the new locations. See the section above for guidance on the correct locations for different file types.
+There are two issues when upgrading. The first is moving the files from +public/+ to the new locations. See "Asset Organization":#asset-organization above for guidance on the correct locations for different file types.
The second is updating the various environment files with the correct default options. The following changes reflect the defaults in version 3.1.0.
@@ -620,14 +710,14 @@ config.assets.compile = false
# Generate digests for assets URLs.
config.assets.digest = true
-# Defaults to Rails.root.join("public/assets")
+# Defaults to nil and saved in location specified by config.assets.prefix
# config.assets.manifest = YOUR_PATH
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( search.js )
</erb>
-There are no changes to +test.rb+. The defaults in the test environment are: +config.assets.compile+ is true and +config.assets.compress+, +config.assets.debug+ and +config.assets.digest+ are false.
+You should not need to change +test.rb+. The defaults in the test environment are: +config.assets.compile+ is true and +config.assets.compress+, +config.assets.debug+ and +config.assets.digest+ are false.
The following should also be added to +Gemfile+:
@@ -641,7 +731,7 @@ group :assets do
end
</plain>
-If you use the +assets+ group with Bundler, please make sure that your +config/application.rb+ has the following Bundler require statement.
+If you use the +assets+ group with Bundler, please make sure that your +config/application.rb+ has the following Bundler require statement:
<ruby>
if defined?(Bundler)
@@ -652,7 +742,7 @@ if defined?(Bundler)
end
</ruby>
-Instead of the old Rails 3.0 one
+Instead of the old Rails 3.0 version:
<ruby>
# If you have a Gemfile, require the gems listed there, including any gems
diff --git a/railties/guides/source/association_basics.textile b/guides/source/association_basics.textile
index 6829eb8ef4..8ddc56bef1 100644
--- a/railties/guides/source/association_basics.textile
+++ b/guides/source/association_basics.textile
@@ -342,9 +342,9 @@ In designing a data model, you will sometimes find a model that should have a re
<ruby>
class Employee < ActiveRecord::Base
- has_many :subordinates, :class_name => "Employee"
- belongs_to :manager, :class_name => "Employee",
+ has_many :subordinates, :class_name => "Employee",
:foreign_key => "manager_id"
+ belongs_to :manager, :class_name => "Employee"
end
</ruby>
@@ -358,6 +358,7 @@ Here are a few things you should know to make efficient use of Active Record ass
* Avoiding name collisions
* Updating the schema
* Controlling association scope
+* Bi-directional associations
h4. Controlling Caching
@@ -501,6 +502,59 @@ module MyApplication
end
</ruby>
+h4. Bi-directional Associations
+
+It's normal for associations to work in two directions, requiring declaration on two different models:
+
+<ruby>
+class Customer < ActiveRecord::Base
+ has_many :orders
+end
+
+class Order < ActiveRecord::Base
+ belongs_to :customer
+end
+</ruby>
+
+By default, Active Record doesn't know about the connection between these associations. This can lead to two copies of an object getting out of sync:
+
+<ruby>
+c = Customer.first
+o = c.orders.first
+c.first_name == o.customer.first_name # => true
+c.first_name = 'Manny'
+c.first_name == o.customer.first_name # => false
+</ruby>
+
+This happens because c and o.customer are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the +:inverse_of+ option so that you can inform it of these relations:
+
+<ruby>
+class Customer < ActiveRecord::Base
+ has_many :orders, :inverse_of => :customer
+end
+
+class Order < ActiveRecord::Base
+ belongs_to :customer, :inverse_of => :orders
+end
+</ruby>
+
+With these changes, Active Record will only load one copy of the customer object, preventing inconsistencies and making your application more efficient:
+
+<ruby>
+c = Customer.first
+o = c.orders.first
+c.first_name == o.customer.first_name # => true
+c.first_name = 'Manny'
+c.first_name == o.customer.first_name # => true
+</ruby>
+
+There are a few limitations to +inverse_of+ support:
+
+* They do not work with <tt>:through</tt> associations.
+* They do not work with <tt>:polymorphic</tt> associations.
+* They do not work with <tt>:as</tt> associations.
+* For +belongs_to+ associations, +has_many+ inverse associations are ignored.
+
h3. Detailed Association Reference
The following sections give the details of each type of association, including the methods that they add and the options that you can use when declaring an association.
@@ -594,6 +648,7 @@ The +belongs_to+ association supports these options:
* +:dependent+
* +:foreign_key+
* +:include+
+* +:inverse_of+
* +:polymorphic+
* +:readonly+
* +:select+
@@ -720,6 +775,20 @@ end
NOTE: There's no need to use +:include+ for immediate associations - that is, if you have +Order belongs_to :customer+, then the customer is eager-loaded automatically when it's needed.
+h6(#belongs_to-inverse_of). +:inverse_of+
+
+The +:inverse_of+ option specifies the name of the +has_many+ or +has_one+ association that is the inverse of this association. Does not work in combination with the +:polymorphic+ options.
+
+<ruby>
+class Customer < ActiveRecord::Base
+ has_many :orders, :inverse_of => :customer
+end
+
+class Order < ActiveRecord::Base
+ belongs_to :customer, :inverse_of => :orders
+end
+</ruby>
+
h6(#belongs_to-polymorphic). +:polymorphic+
Passing +true+ to the +:polymorphic+ option indicates that this is a polymorphic association. Polymorphic associations were discussed in detail <a href="#polymorphic-associations">earlier in this guide</a>.
@@ -859,6 +928,7 @@ The +has_one+ association supports these options:
* +:dependent+
* +:foreign_key+
* +:include+
+* +:inverse_of+
* +:order+
* +:primary_key+
* +:readonly+
@@ -899,6 +969,9 @@ end
h6(#has_one-dependent). +:dependent+
If you set the +:dependent+ option to +:destroy+, then deleting this object will call the +destroy+ method on the associated object to delete that object. If you set the +:dependent+ option to +:delete+, then deleting this object will delete the associated object _without_ calling its +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the association object to +NULL+.
+If you set the +:dependent+ option to +:restrict+, then the deletion of the object is restricted if a dependent associated object exist and a +DeleteRestrictionError+ exception is raised.
+
+NOTE: The default behavior for +:dependent => :restrict+ is to raise a +DeleteRestrictionError+ when associated objects exist. Since Rails 4.0 this behavior is being deprecated in favor of adding an error to the base model. To silence the warning in Rails 4.0, you should fix your code to not expect this Exception and add +config.active_record.dependent_restrict_raises = false+ to your application config.
h6(#has_one-foreign_key). +:foreign_key+
@@ -948,6 +1021,20 @@ class Representative < ActiveRecord::Base
end
</ruby>
+h6(#has_one-inverse_of). +:inverse_of+
+
+The +:inverse_of+ option specifies the name of the +belongs_to+ association that is the inverse of this association. Does not work in combination with the +:through+ or +:as+ options.
+
+<ruby>
+class Supplier < ActiveRecord::Base
+ has_one :account, :inverse_of => :supplier
+end
+
+class Account < ActiveRecord::Base
+ belongs_to :supplier, :inverse_of => :account
+end
+</ruby>
+
h6(#has_one-order). +:order+
The +:order+ option dictates the order in which associated objects will be received (in the syntax used by an SQL +ORDER BY+ clause). Because a +has_one+ association will only retrieve a single associated object, this option should not be needed.
@@ -1120,7 +1207,7 @@ h6(#has_many-collection-find). <tt><em>collection</em>.find(...)</tt>
The <tt><em>collection</em>.find</tt> method finds objects within the collection. It uses the same syntax and options as +ActiveRecord::Base.find+.
<ruby>
-@open_orders = @customer.orders.where(:open => 1)
+@open_orders = @customer.orders.find(1)
</ruby>
h6(#has_many-collection-where). <tt><em>collection</em>.where(...)</tt>
@@ -1177,6 +1264,7 @@ The +has_many+ association supports these options:
* +:foreign_key+
* +:group+
* +:include+
+* +:inverse_of+
* +:limit+
* +:offset+
* +:order+
@@ -1234,7 +1322,7 @@ If you need to evaluate conditions dynamically at runtime, use a proc:
<ruby>
class Customer < ActiveRecord::Base
has_many :latest_orders, :class_name => "Order",
- :conditions => proc { "orders.created_at > #{10.hours.ago.to_s(:db).inspect}" }
+ :conditions => proc { ["orders.created_at > ?", 10.hours.ago] }
end
</ruby>
@@ -1242,11 +1330,14 @@ h6(#has_many-counter_sql). +:counter_sql+
Normally Rails automatically generates the proper SQL to count the association members. With the +:counter_sql+ option, you can specify a complete SQL statement to count them yourself.
-NOTE: If you specify +:finder_sql+ but not +:counter_sql+, then the counter SQL will be generated by substituting +SELECT COUNT(*) FROM+ for the +SELECT ... FROM+ clause of your +:finder_sql+ statement.
+NOTE: If you specify +:finder_sql+ but not +:counter_sql+, then the counter SQL will be generated by substituting the +SELECT ... FROM+ clause of your +:finder_sql+ statement by +SELECT COUNT(*) FROM+.
h6(#has_many-dependent). +:dependent+
If you set the +:dependent+ option to +:destroy+, then deleting this object will call the +destroy+ method on the associated objects to delete those objects. If you set the +:dependent+ option to +:delete_all+, then deleting this object will delete the associated objects _without_ calling their +destroy+ method. If you set the +:dependent+ option to +:nullify+, then deleting this object will set the foreign key in the associated objects to +NULL+.
+If you set the +:dependent+ option to +:restrict+, then the deletion of the object is restricted if a dependent associated object exist and a +DeleteRestrictionError+ exception is raised.
+
+NOTE: The default behavior for +:dependent => :restrict+ is to raise a +DeleteRestrictionError+ when associated objects exist. Since Rails 4.0 this behavior is being deprecated in favor of adding an error to the base model. To silence the warning in Rails 4.0, you should fix your code to not expect this Exception and add +config.active_record.dependent_restrict_raises = false+ to your application config.
NOTE: This option is ignored when you use the +:through+ option on the association.
@@ -1316,6 +1407,20 @@ class LineItem < ActiveRecord::Base
end
</ruby>
+h6(#has_many-inverse_of). +:inverse_of+
+
+The +:inverse_of+ option specifies the name of the +belongs_to+ association that is the inverse of this association. Does not work in combination with the +:through+ or +:as+ options.
+
+<ruby>
+class Customer < ActiveRecord::Base
+ has_many :orders, :inverse_of => :customer
+end
+
+class Order < ActiveRecord::Base
+ belongs_to :customer, :inverse_of => :orders
+end
+</ruby>
+
h6(#has_many-limit). +:limit+
The +:limit+ option lets you restrict the total number of objects that will be fetched through an association.
@@ -1545,12 +1650,9 @@ h6(#has_and_belongs_to_many-collection-find). <tt><em>collection</em>.find(...)<
The <tt><em>collection</em>.find</tt> method finds objects within the collection. It uses the same syntax and options as +ActiveRecord::Base.find+. It also adds the additional condition that the object must be in the collection.
<ruby>
-@new_assemblies = @part.assemblies.all(
- :conditions => ["created_at > ?", 2.days.ago])
+@assembly = @part.assemblies.find(1)
</ruby>
-NOTE: Beginning with Rails 3, supplying options to the +ActiveRecord::Base.find+ method is discouraged. Use <tt><em>collection</em>.where</tt> instead when you need to pass conditions.
-
h6(#has_and_belongs_to_many-collection-where). <tt><em>collection</em>.where(...)</tt>
The <tt><em>collection</em>.where</tt> method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed. It also adds the additional condition that the object must be in the collection.
@@ -1669,7 +1771,7 @@ h6(#has_and_belongs_to_many-counter_sql). +:counter_sql+
Normally Rails automatically generates the proper SQL to count the association members. With the +:counter_sql+ option, you can specify a complete SQL statement to count them yourself.
-NOTE: If you specify +:finder_sql+ but not +:counter_sql+, then the counter SQL will be generated by substituting +SELECT COUNT(*) FROM+ for the +SELECT ... FROM+ clause of your +:finder_sql+ statement.
+NOTE: If you specify +:finder_sql+ but not +:counter_sql+, then the counter SQL will be generated by substituting the +SELECT ... FROM+ clause of your +:finder_sql+ statement by +SELECT COUNT(*) FROM+.
h6(#has_and_belongs_to_many-delete_sql). +:delete_sql+
diff --git a/railties/guides/source/caching_with_rails.textile b/guides/source/caching_with_rails.textile
index 4273d0dd64..34a100cd3a 100644
--- a/railties/guides/source/caching_with_rails.textile
+++ b/guides/source/caching_with_rails.textile
@@ -64,15 +64,37 @@ end
If you want a more complicated expiration scheme, you can use cache sweepers to expire cached objects when things change. This is covered in the section on Sweepers.
-NOTE: Page caching ignores all parameters. For example +/products?page=1+ will be written out to the filesystem as +products.html+ with no reference to the +page+ parameter. Thus, if someone requests +/products?page=2+ later, they will get the cached first page. Be careful when page caching GET parameters in the URL!
+By default, page caching automatically gzips files (for example, to +products.html.gz+ if user requests +/products+) to reduce the size of data transmitted (web servers are typically configured to use a moderate compression ratio as a compromise, but since precompilation happens once, compression ratio is maximum).
+
+Nginx is able to serve compressed content directly from disk by enabling +gzip_static+:
+
+<plain>
+location / {
+ gzip_static on; # to serve pre-gzipped version
+}
+</plain>
+
+You can disable gzipping by setting +:gzip+ option to false (for example, if action returns image):
+
+<ruby>
+caches_page :image, :gzip => false
+</ruby>
+
+Or, you can set custom gzip compression level (level names are taken from +Zlib+ constants):
+
+<ruby>
+caches_page :image, :gzip => :best_speed
+</ruby>
+
+NOTE: Page caching ignores all parameters. For example +/products?page=1+ will be written out to the filesystem as +products.html+ with no reference to the +page+ parameter. Thus, if someone requests +/products?page=2+ later, they will get the cached first page. A workaround for this limitation is to include the parameters in the page's path, e.g. +/productions/page/1+.
INFO: Page caching runs in an after filter. Thus, invalid requests won't generate spurious cache entries as long as you halt them. Typically, a redirection in some before filter that checks request preconditions does the job.
h4. Action Caching
-One of the issues with Page Caching is that you cannot use it for pages that require to restrict access somehow. This is where Action Caching comes in. Action Caching works like Page Caching except for the fact that the incoming web request does go from the webserver to the Rails stack and Action Pack so that before filters can be run on it before the cache is served. This allows authentication and other restriction to be run while still serving the result of the output from a cached copy.
+Page Caching cannot be used for actions that have before filters - for example, pages that require authentication. This is where Action Caching comes in. Action Caching works like Page Caching except the incoming web request hits the Rails stack so that before filters can be run on it before the cache is served. This allows authentication and other restrictions to be run while still serving the result of the output from a cached copy.
-Clearing the cache works in the exact same way as with Page Caching.
+Clearing the cache works in a similar way to Page Caching, except you use +expire_action+ instead of +expire_page+.
Let's say you only wanted authenticated users to call actions on +ProductsController+.
@@ -135,7 +157,7 @@ and you can expire it using the +expire_fragment+ method, like so:
expire_fragment(:controller => 'products', :action => 'recent', :action_suffix => 'all_products')
</ruby>
-If you don't want the cache block to bind to the action that called it, You can also use globally keyed fragments by calling the +cache+ method with a key, like so:
+If you don't want the cache block to bind to the action that called it, you can also use globally keyed fragments by calling the +cache+ method with a key:
<ruby>
<% cache('all_available_products') do %>
@@ -207,6 +229,42 @@ class ProductsController < ActionController
end
</ruby>
+Sometimes it is necessary to disambiguate the controller when you call +expire_action+, such as when there are two identically named controllers in separate namespaces:
+
+<ruby>
+class ProductsController < ActionController
+ caches_action :index
+
+ def index
+ @products = Product.all
+ end
+end
+
+module Admin
+ class ProductsController < ActionController
+ cache_sweeper :product_sweeper
+
+ def new
+ @product = Product.new
+ end
+
+ def create
+ @product = Product.create(params[:product])
+ end
+ end
+end
+
+class ProductSweeper < ActionController::Caching::Sweeper
+ observe Product
+
+ def after_create(product)
+ expire_action(:controller => '/products', :action => 'index')
+ end
+end
+</ruby>
+
+Note the use of '/products' here rather than 'products'. If you wanted to expire an action cache for the +Admin::ProductsController+, you would use 'admin/products' instead.
+
h4. SQL Caching
Query caching is a Rails feature that caches the result set returned by each query so that if Rails encounters the same query again for that request, it will use the cached result set as opposed to running the query against the database again.
@@ -235,7 +293,9 @@ However, it's important to note that query caches are created at the start of an
h3. Cache Stores
-Rails provides different stores for the cached data created by action and fragment caches. Page caches are always stored on disk.
+Rails provides different stores for the cached data created by <b>action</b> and <b>fragment</b> caches.
+
+TIP: Page caches are always stored on disk.
h4. Configuration
@@ -245,7 +305,7 @@ You can set up your application's default cache store by calling +config.cache_s
config.cache_store = :memory_store
</ruby>
-Alternatively, you can call +ActionController::Base.cache_store+ outside of a configuration block.
+NOTE: Alternatively, you can call +ActionController::Base.cache_store+ outside of a configuration block.
You can access the cache by calling +Rails.cache+.
@@ -272,7 +332,7 @@ h4. ActiveSupport::Cache::MemoryStore
This cache store keeps entries in memory in the same Ruby process. The cache store has a bounded size specified by the +:size+ options to the initializer (default is 32Mb). When the cache exceeds the allotted size, a cleanup will occur and the least recently used entries will be removed.
<ruby>
-ActionController::Base.cache_store = :memory_store, :size => 64.megabytes
+config.cache_store = :memory_store, :size => 64.megabytes
</ruby>
If you're running multiple Ruby on Rails server processes (which is the case if you're using mongrel_cluster or Phusion Passenger), then your Rails server process instances won't be able to share cache data with each other. This cache store is not appropriate for large application deployments, but can work well for small, low traffic sites with only a couple of server processes or for development and test environments.
@@ -284,7 +344,7 @@ h4. ActiveSupport::Cache::FileStore
This cache store uses the file system to store entries. The path to the directory where the store files will be stored must be specified when initializing the cache.
<ruby>
-ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
+config.cache_store = :file_store, "/path/to/cache/directory"
</ruby>
With this cache store, multiple server processes on the same host can share a cache. Servers processes running on different hosts could share a cache by using a shared file system, but that set up would not be ideal and is not recommended. The cache store is appropriate for low to medium traffic sites that are served off one or two hosts.
@@ -293,14 +353,14 @@ Note that the cache will grow until the disk is full unless you periodically cle
h4. ActiveSupport::Cache::MemCacheStore
-This cache store uses Danga's +memcached+ server to provide a centralized cache for your application. Rails uses the bundled +memcached-client+ gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very a high performance and redundancy.
+This cache store uses Danga's +memcached+ server to provide a centralized cache for your application. Rails uses the bundled +memcache-client+ gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very a high performance and redundancy.
When initializing the cache, you need to specify the addresses for all memcached servers in your cluster. If none is specified, it will assume memcached is running on the local host on the default port, but this is not an ideal set up for larger sites.
The +write+ and +fetch+ methods on this cache accept two additional options that take advantage of features specific to memcached. You can specify +:raw+ to send a value directly to the server with no serialization. The value must be a string or number. You can use memcached direct operation like +increment+ and +decrement+ only on raw values. You can also specify +:unless_exist+ if you don't want memcached to overwrite an existing entry.
<ruby>
-ActionController::Base.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
+config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
</ruby>
h4. ActiveSupport::Cache::EhcacheStore
@@ -308,7 +368,7 @@ h4. ActiveSupport::Cache::EhcacheStore
If you are using JRuby you can use Terracotta's Ehcache as the cache store for your application. Ehcache is an open source Java cache that also offers an enterprise version with increased scalability, management, and commercial support. You must first install the jruby-ehcache-rails3 gem (version 1.1.0 or later) to use this cache store.
<ruby>
-ActionController::Base.cache_store = :ehcache_store
+config.cache_store = :ehcache_store
</ruby>
When initializing the cache, you may use the +:ehcache_config+ option to specify the Ehcache config file to use (where the default is "ehcache.xml" in your Rails config directory), and the :cache_name option to provide a custom name for your cache (the default is rails_cache).
@@ -332,6 +392,14 @@ caches_action :index, :expires_in => 60.seconds, :unless_exist => true
For more information about Ehcache, see "http://ehcache.org/":http://ehcache.org/ .
For more information about Ehcache for JRuby and Rails, see "http://ehcache.org/documentation/jruby.html":http://ehcache.org/documentation/jruby.html
+h4. ActiveSupport::Cache::NullStore
+
+This cache store implementation is meant to be used only in development or test environments and it never stores anything. This can be very useful in development when you have code that interacts directly with +Rails.cache+, but caching may interfere with being able to see the results of code changes. With this cache store, all +fetch+ and +read+ operations will result in a miss.
+
+<ruby>
+config.cache_store = :null_store
+</ruby>
+
h4. Custom Cache Stores
You can create your own custom cache store by simply extending +ActiveSupport::Cache::Store+ and implementing the appropriate methods. In this way, you can swap in any number of caching technologies into your Rails application.
@@ -339,7 +407,7 @@ You can create your own custom cache store by simply extending +ActiveSupport::C
To use a custom cache store, simple set the cache store to a new instance of the class.
<ruby>
-ActionController::Base.cache_store = MyCacheStore.new
+config.cache_store = MyCacheStore.new
</ruby>
h4. Cache Keys
diff --git a/railties/guides/source/command_line.textile b/guides/source/command_line.textile
index f6b33d283c..b656a0857a 100644
--- a/railties/guides/source/command_line.textile
+++ b/guides/source/command_line.textile
@@ -12,7 +12,7 @@ endprologue.
NOTE: This tutorial assumes you have basic Rails knowledge from reading the "Getting Started with Rails Guide":getting_started.html.
-WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
+WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not work in earlier versions of Rails.
h3. Command Line Basics
@@ -31,12 +31,12 @@ h4. +rails new+
The first thing we'll want to do is create a new Rails application by running the +rails new+ command after installing Rails.
-WARNING: You can install the rails gem by typing +gem install rails+, if you don't have it already. Follow the instructions in the "Rails 3 Release Notes":/3_0_release_notes.html
+TIP: You can install the rails gem by typing +gem install rails+, if you don't have it already.
<shell>
$ rails new commandsapp
create
- create README
+ create README.rdoc
create .gitignore
create Rakefile
create config.ru
@@ -45,8 +45,6 @@ $ rails new commandsapp
...
create tmp/cache
create tmp/pids
- create vendor/plugins
- create vendor/plugins/.gitkeep
</shell>
Rails will set you up with what seems like a huge amount of stuff for such a tiny command! You've got the entire Rails directory structure now with all the code you need to run our simple application right out of the box.
@@ -81,6 +79,8 @@ The server can be run on a different port using the +-p+ option. The default dev
$ rails server -e production -p 4000
</shell>
+The +-b+ option binds Rails to the specified ip, by default it is 0.0.0.0. You can run a server as a daemon by passing a +-d+ option.
+
h4. +rails generate+
The +rails generate+ command uses templates to create a whole lot of things. Running +rails generate+ by itself gives a list of available generators:
@@ -185,8 +185,6 @@ $ rails server
=> Booting WEBrick...
</shell>
-WARNING: Make sure that you do not have any "tilde backup" files in +app/views/(controller)+, or else WEBrick will _not_ show the expected output. This seems to be a *bug* in Rails 2.3.0.
-
The URL will be "http://localhost:3000/greetings/hello":http://localhost:3000/greetings/hello.
INFO: With a normal, plain-old Rails application, your URLs will generally follow the pattern of http://(host)/(controller)/(action), and a URL like http://(host)/(controller) will hit the *index* action of that controller.
@@ -278,6 +276,12 @@ The +console+ command lets you interact with your Rails application from the com
You can also use the alias "c" to invoke the console: <tt>rails c</tt>.
+You can specify the environment in which the +console+ command should operate using the +-e+ switch.
+
+<shell>
+$ rails console -e staging
+</shell>
+
If you wish to test out some code without changing any data, you can do that by invoking +rails console --sandbox+.
<shell>
@@ -293,18 +297,6 @@ h4. +rails dbconsole+
You can also use the alias "db" to invoke the dbconsole: <tt>rails db</tt>.
-h4. +rails plugin+
-
-The +rails plugin+ command simplifies plugin management. Plugins can be installed by name or their repository URLs. You need to have Git installed if you want to install a plugin from a Git repo. The same holds for Subversion too.
-
-<shell>
-$ rails plugin install https://github.com/technoweenie/acts_as_paranoid.git
-+ ./CHANGELOG
-+ ./MIT-LICENSE
-...
-...
-</shell>
-
h4. +rails runner+
<tt>runner</tt> runs Ruby code in the context of Rails non-interactively. For instance:
@@ -379,17 +371,16 @@ h4. +about+
<shell>
$ rake about
About your application's environment
-Ruby version 1.8.7 (x86_64-linux)
+Ruby version 1.9.3 (x86_64-linux)
RubyGems version 1.3.6
-Rack version 1.1
-Rails version 3.1.0
+Rack version 1.3
+Rails version 4.0.0.beta
JavaScript Runtime Node.js (V8)
-Active Record version 3.1.0
-Action Pack version 3.1.0
-Active Resource version 3.1.0
-Action Mailer version 3.1.0
-Active Support version 3.1.0
-Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::RemoteIp, ActionDispatch::Callbacks, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::MethodOverride, ActionDispatch::Head
+Active Record version 4.0.0.beta
+Action Pack version 4.0.0.beta
+Action Mailer version 4.0.0.beta
+Active Support version 4.0.0.beta
+Middleware ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, ActionDispatch::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport
Application root /home/foobar/commandsapp
Environment development
Database adapter sqlite3
@@ -413,12 +404,10 @@ The +doc:+ namespace has the tools to generate documentation for your app, API d
* +rake doc:app+ generates documentation for your application in +doc/app+.
* +rake doc:guides+ generates Rails guides in +doc/guides+.
* +rake doc:rails+ generates API documentation for Rails in +doc/api+.
-* +rake doc:plugins+ generates API documentation for all the plugins installed in the application in +doc/plugins+.
-* +rake doc:clobber_plugins+ removes the generated documentation for all plugins.
h4. +notes+
-+rake notes+ will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is only done in files with extension +.builder+, +.rb+, +.rxml+, +.rhtml+ and +.erb+ for both default and custom annotations.
++rake notes+ will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension +.builder+, +.rb+, +.erb+, +.haml+ and +.slim+ for both default and custom annotations.
<shell>
$ rake notes
@@ -455,6 +444,18 @@ app/model/post.rb:
NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines.
+By default, +rake notes+ will look in the +app+, +config+, +lib+, +script+ and +test+ directories. If you would like to search other directories, you can provide them as a comma separated list in an environment variable +SOURCE_ANNOTATION_DIRECTORIES+.
+
+<shell>
+$ export SOURCE_ANNOTATION_DIRECTORIES='rspec,vendor'
+$ rake notes
+(in /home/foobar/commandsapp)
+app/model/user.rb:
+ * [ 35] [FIXME] User should have a subscription at this point
+rspec/model/user_spec.rb:
+ * [122] [TODO] Verify the user that has a subscription works
+</shell>
+
h4. +routes+
+rake routes+ will list all of your defined routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with.
@@ -507,8 +508,8 @@ $ rails new . --git --database=postgresql
create tmp/pids
create Rakefile
add 'Rakefile'
- create README
-add 'README'
+ create README.rdoc
+add 'README.rdoc'
create app/controllers/application_controller.rb
add 'app/controllers/application_controller.rb'
create app/helpers/application_helper.rb
diff --git a/railties/guides/source/configuring.textile b/guides/source/configuring.textile
index baf944cf8d..f114075cae 100644
--- a/railties/guides/source/configuring.textile
+++ b/guides/source/configuring.textile
@@ -40,7 +40,9 @@ Rails will use that particular setting to configure Active Record.
h4. Rails General Configuration
-* +config.after_initialize+ takes a block which will be ran _after_ Rails has finished initializing the application. That includes the initialization of the framework itself, plugins, engines, and all the application's initializers in +config/initializers+. Useful for configuring values set up by other initializers:
+These configuration methods are to be called on a +Rails::Railtie+ object, such as a subclass of +Rails::Engine+ or +Rails::Application+.
+
+* +config.after_initialize+ takes a block which will be run _after_ Rails has finished initializing the application. That includes the initialization of the framework itself, engines, and all the application's initializers in +config/initializers+. Note that this block _will_ be run for rake tasks. Useful for configuring values set up by other initializers:
<ruby>
config.after_initialize do
@@ -64,37 +66,56 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is
* +config.autoload_paths+ accepts an array of paths from which Rails will autoload constants. Default is all directories under +app+.
-* +config.cache_classes+ controls whether or not application classes and modules should be reloaded on each request. Defaults to true in development mode, and false in test and production modes. Can also be enabled with +threadsafe!+.
+* +config.cache_classes+ controls whether or not application classes and modules should be reloaded on each request. Defaults to false in development mode, and true in test and production modes. Can also be enabled with +threadsafe!+.
* +config.action_view.cache_template_loading+ controls whether or not templates should be reloaded on each request. Defaults to whatever is set for +config.cache_classes+.
-* +config.cache_store+ configures which cache store to use for Rails caching. Options include one of the symbols +:memory_store+, +:file_store+, +:mem_cache_store+, or an object that implements the cache API. Defaults to +:file_store+ if the directory +tmp/cache+ exists, and to +:memory_store+ otherwise.
+* +config.cache_store+ configures which cache store to use for Rails caching. Options include one of the symbols +:memory_store+, +:file_store+, +:mem_cache_store+, +:null_store+, or an object that implements the cache API. Defaults to +:file_store+ if the directory +tmp/cache+ exists, and to +:memory_store+ otherwise.
* +config.colorize_logging+ specifies whether or not to use ANSI color codes when logging information. Defaults to true.
* +config.consider_all_requests_local+ is a flag. If true then any error will cause detailed debugging information to be dumped in the HTTP response, and the +Rails::Info+ controller will show the application runtime context in +/rails/info/properties+. True by default in development and test environments, and false in production mode. For finer-grained control, set this to false and implement +local_request?+ in controllers to specify which requests should provide debugging information on errors.
+* +config.console+ allows you to set class that will be used as console you run +rails console+. It's best to run it in +console+ block:
+
+<ruby>
+console do
+ # this block is called only when running console,
+ # so we can safely require pry here
+ require "pry"
+ config.console = Pry
+end
+</ruby>
+
* +config.dependency_loading+ is a flag that allows you to disable constant autoloading setting it to false. It only has effect if +config.cache_classes+ is true, which it is by default in production mode. This flag is set to false by +config.threadsafe!+.
* +config.eager_load_paths+ accepts an array of paths from which Rails will eager load on boot if cache classes is enabled. Defaults to every folder in the +app+ directory of the application.
* +config.encoding+ sets up the application-wide encoding. Defaults to UTF-8.
+* +config.exceptions_app+ sets the exceptions application invoked by the ShowException middleware when an exception happens. Defaults to +ActionDispatch::PublicExceptions.new(Rails.public_path)+.
+
+* +config.file_watcher+ the class used to detect file updates in the filesystem when +config.reload_classes_only_on_change+ is true. Must conform to +ActiveSupport::FileUpdateChecker+ API.
+
* +config.filter_parameters+ used for filtering out the parameters that you don't want shown in the logs, such as passwords or credit card numbers.
-* +config.force_ssl+ forces all requests to be under HTTPS protocol by using +Rack::SSL+ middleware.
+* +config.force_ssl+ forces all requests to be under HTTPS protocol by using +ActionDispatch::SSL+ middleware.
* +config.log_level+ defines the verbosity of the Rails logger. This option defaults to +:debug+ for all modes except production, where it defaults to +:info+.
+* +config.log_tags+ accepts a list of methods that respond to +request+ object. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications.
+
* +config.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby +Logger+ class. Defaults to an instance of +ActiveSupport::BufferedLogger+, with auto flushing off in production mode.
* +config.middleware+ allows you to configure the application's middleware. This is covered in depth in the "Configuring Middleware":#configuring-middleware section below.
-* +config.plugins+ accepts the list of plugins to load. The default is +nil+ in which case all plugins will be loaded. If this is set to +[]+, no plugins will be loaded. Otherwise, plugins will be loaded in the order specified. This option lets you enforce some particular loading order, useful when dependencies between plugins require it. For that use case, put first the plugins you want to be loaded in a certain order, and then the special symbol +:all+ to have the rest loaded without the need to specify them.
-
* +config.preload_frameworks+ enables or disables preloading all frameworks at startup. Enabled by +config.threadsafe!+. Defaults to +nil+, so is disabled.
-* +config.reload_plugins+ enables or disables plugin reloading. Defaults to false.
+* +config.queue+ configures a different queue implementation for the application. Defaults to +Rails::Queueing::Queue+. Note that, if the default queue is changed, the default +queue_consumer+ is not going to be initialized, it is up to the new queue implementation to handle starting and shutting down its own consumer(s).
+
+* +config.queue_consumer+ configures a different consumer implementation for the default queue. Defaults to +Rails::Queueing::ThreadedConsumer+.
+
+* +config.reload_classes_only_on_change+ enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to true. If +config.cache_classes+ is true, this option is ignored.
* +config.secret_token+ used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get +config.secret_token+ initialized to a random key in +config/initializers/secret_token.rb+.
@@ -146,6 +167,7 @@ Rails 3.1, by default, is set up to use the +sprockets+ gem to manage assets wit
* +config.assets.compile+ is a boolean that can be used to turn on live Sprockets compilation in production.
+* +config.assets.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby +Logger+ class. Defaults to the same configured at +config.logger+. Setting +config.assets.logger+ to false will turn off served assets logging.
h4. Configuring Generators
@@ -164,13 +186,13 @@ The full set of methods that can be used in this block are as follows:
* +force_plural+ allows pluralized model names. Defaults to +false+.
* +helper+ defines whether or not to generate helpers. Defaults to +true+.
* +integration_tool+ defines which integration tool to use. Defaults to +nil+.
-* +javascripts+ turns on the hook for javascripts in generators. Used in Rails for when the +scaffold+ generator is ran. Defaults to +true+.
+* +javascripts+ turns on the hook for javascripts in generators. Used in Rails for when the +scaffold+ generator is run. Defaults to +true+.
* +javascript_engine+ configures the engine to be used (for eg. coffee) when generating assets. Defaults to +nil+.
* +orm+ defines which orm to use. Defaults to +false+ and will use Active Record by default.
* +performance_tool+ defines which performance tool to use. Defaults to +nil+.
* +resource_controller+ defines which generator to use for generating a controller when using +rails generate resource+. Defaults to +:controller+.
* +scaffold_controller+ different from +resource_controller+, defines which generator to use for generating a _scaffolded_ controller when using +rails generate scaffold+. Defaults to +:scaffold_controller+.
-* +stylesheets+ turns on the hook for stylesheets in generators. Used in Rails for when the +scaffold+ generator is ran, but this hook can be used in other generates as well. Defaults to +true+.
+* +stylesheets+ turns on the hook for stylesheets in generators. Used in Rails for when the +scaffold+ generator is run, but this hook can be used in other generates as well. Defaults to +true+.
* +stylesheet_engine+ configures the stylesheet engine (for eg. sass) to be used when generating assets. Defaults to +:css+.
* +test_framework+ defines which test framework to use. Defaults to +false+ and will use Test::Unit by default.
* +template_engine+ defines which template engine to use, such as ERB or Haml. Defaults to +:erb+.
@@ -179,13 +201,14 @@ 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+ forces every request to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to +true+. Options passed to this can be configured by using +config.ssl_options+.
+* +ActionDispatch::SSL+ forces every request to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to +true+. Options passed to this can be configured by using +config.ssl_options+.
* +ActionDispatch::Static+ is used to serve static assets. Disabled if +config.serve_static_assets+ is +true+.
* +Rack::Lock+ wraps 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.
* +Rack::Runtime+ sets an +X-Runtime+ header, containing the time (in seconds) taken to execute the request.
* +Rails::Rack::Logger+ notifies the logs that the request has began. After request is complete, flushes all the logs.
* +ActionDispatch::ShowExceptions+ rescues any exception returned by the application and renders nice exception pages if the request is local or if +config.consider_all_requests_local+ is set to +true+. If +config.action_dispatch.show_exceptions+ is set to +false+, exceptions will be raised regardless.
+* +ActionDispatch::RequestId+ makes a unique X-Request-Id header available to the response and enables the +ActionDispatch::Request#uuid+ method.
* +ActionDispatch::RemoteIp+ checks for IP spoofing attacks. Configurable with the +config.action_dispatch.ip_spoofing_check+ and +config.action_dispatch.trusted_proxies+ settings.
* +Rack::Sendfile+ intercepts responses whose body is being served from a file and replaces it with a server specific X-Sendfile header. Configurable with +config.action_dispatch.x_sendfile_header+.
* +ActionDispatch::Callbacks+ runs the prepare callbacks before serving the request.
@@ -195,7 +218,7 @@ Every Rails application comes with a standard set of middleware which it uses in
* +ActionDispatch::Session::CookieStore+ is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the +config.action_controller.session_store+ to an alternate value. Additionally, options passed to this can be configured by using +config.action_controller.session_options+.
* +ActionDispatch::Flash+ sets up the +flash+ keys. Only available if +config.action_controller.session_store+ is set to a value.
* +ActionDispatch::ParamsParser+ parses out parameters from the request into +params+.
-* +Rack::MethodOverride+ allows the method to be overridden if +params[:_method]+ is set. This is the middleware which supports the PUT and DELETE HTTP method types.
+* +Rack::MethodOverride+ allows the method to be overridden if +params[:_method]+ is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types.
* +ActionDispatch::Head+ converts HEAD requests to GET requests and serves them as so.
* +ActionDispatch::BestStandardsSupport+ enables "best standards support" so that IE8 renders some elements correctly.
@@ -261,6 +284,12 @@ h4. Configuring Active Record
* +config.active_record.whitelist_attributes+ will create an empty whitelist of attributes available for mass-assignment security for all models in your app.
+* +config.active_record.auto_explain_threshold_in_seconds+ configures the threshold for automatic EXPLAINs (+nil+ disables this feature). Queries exceeding the threshold get their query plan logged. Default is 0.5 in development mode.
+
+* +config.active_record.dependent_restrict_raises+ will control the behavior when an object with a <tt>:dependent => :restrict</tt> association is deleted. Setting this to false will prevent +DeleteRestrictionError+ from being raised and instead will add an error on the model object. Defaults to false in the development mode.
+
+* +config.active_record.mass_assignment_sanitizer+ will determine the strictness of the mass assignment sanitization within Rails. Defaults to +:strict+. In this mode, mass assigning any non-+attr_accessible+ attribute in a +create+ or +update_attributes+ call will raise an exception. Setting this option to +:logger+ will only print to the log file when an attribute is being assigned and will not raise an exception.
+
The MySQL adapter adds one additional configuration option:
* +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether Active Record will consider all +tinyint(1)+ columns in a MySQL database to be booleans and is true by default.
@@ -321,7 +350,7 @@ h4. Configuring Action Dispatch
h4. Configuring Action View
-There are only a few configuration options for Action View, starting with four on +ActionView::Base+:
+<tt>config.action_view</tt> includes a small number of configuration settings:
* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is
@@ -329,9 +358,9 @@ There are only a few configuration options for Action View, starting with four o
Proc.new { |html_tag, instance| %Q(<div class="field_with_errors">#{html_tag}</div>).html_safe }
</ruby>
-* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+.
+* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+. If you want your form builder class to be loaded after initialization (so it's reloaded on each request in development), you can pass it as a +String+
-* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action Mailer. Set to +nil+ to disable logging.
+* +config.action_view.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Action View. Set to +nil+ to disable logging.
* +config.action_view.erb_trim_mode+ gives the trim mode to be used by ERB. It defaults to +'-'+. See the "ERB documentation":http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/ for more information.
@@ -361,6 +390,16 @@ And can reference in the view with the following code:
* +config.action_view.cache_asset_ids+ With the cache enabled, the asset tag helper methods will make fewer expensive file system calls (the default implementation checks the file system timestamp). However this prevents you from modifying any asset files while the server is running.
+* +config.action_view.embed_authenticity_token_in_remote_forms+ allows you to set the default behavior for +authenticity_token+ in forms with +:remote => true+. By default it's set to false, which means that remote forms will not include +authenticity_token+, which is helpful when you're fragment-caching the form. Remote forms get the authenticity from the +meta+ tag, so embedding is unnecessary unless you support browsers without JavaScript. In such case you can either pass +:authenticity_token => true+ as a form option or set this config setting to +true+
+
+* +config.action_view.prefix_partial_path_with_controller_namespace+ determines whether or not partials are looked up from a subdirectory in templates rendered from namespaced controllers. For example, consider a controller named +Admin::PostsController+ which renders this template:
+
+<erb>
+<%= render @post %>
+<erb>
+
+The default setting is +true+, which uses the partial at +/admin/posts/_post.erb+. Setting the value to +false+ would render +/posts/_post.erb+, which is the same behavior as rendering from a non-namespaced controller such as +PostsController+.
+
h4. Configuring Action Mailer
There are a number of settings available on +config.action_mailer+:
@@ -403,21 +442,15 @@ config.action_mailer.observers = ["MailObserver"]
config.action_mailer.interceptors = ["MailInterceptor"]
</ruby>
-h4. Configuring Active Resource
-
-There is a single configuration setting available on +config.active_resource+:
-
-* +config.active_resource.logger+ accepts a logger conforming to the interface of Log4r or the default Ruby Logger class, which is then used to log information from Active Resource. Set to +nil+ to disable logging.
-
h4. Configuring Active Support
There are a few configuration options available in Active Support:
* +config.active_support.bare+ enables or disables the loading of +active_support/all+ when booting Rails. Defaults to +nil+, which means +active_support/all+ is loaded.
-* +config.active_support.escape_html_entities_in_json+ enables or disables the escaping of HTML entities in JSON serialization. Defaults to +true+.
+* +config.active_support.escape_html_entities_in_json+ enables or disables the escaping of HTML entities in JSON serialization. Defaults to +false+.
-* +config.active_support.use_standard_json_time_format+ enables or disables serializing dates to ISO 8601 format. Defaults to +false+.
+* +config.active_support.use_standard_json_time_format+ enables or disables serializing dates to ISO 8601 format. Defaults to +true+.
* +ActiveSupport::BufferedLogger.silencer+ is set to +false+ to disable the ability to silence logging in a block. The default is +true+.
@@ -431,6 +464,107 @@ There are a few configuration options available in Active Support:
* +ActiveSupport::Logger.silencer+ is set to +false+ to disable the ability to silence logging in a block. The default is +true+.
+h4. Configuring a Database
+
+Just about every Rails application will interact with a database. The database to use is specified in a configuration file called +config/database.yml+. If you open this file in a new Rails application, you'll see a default database configured to use SQLite3. The file contains sections for three different environments in which Rails can run by default:
+
+* The +development+ environment is used on your development/local computer as you interact manually with the application.
+* The +test+ environment is used when running automated tests.
+* The +production+ environment is used when you deploy your application for the world to use.
+
+TIP: You don't have to update the database configurations manually. If you look at the options of the application generator, you will see that one of the options is named <tt>--database</tt>. This option allows you to choose an adapter from a list of the most used relational databases. You can even run the generator repeatedly: <tt>cd .. && rails new blog --database=mysql</tt>. When you confirm the overwriting of the +config/database.yml+ file, your application will be configured for MySQL instead of SQLite. Detailed examples of the common database connections are below.
+
+h5. Configuring an SQLite3 Database
+
+Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using an SQLite database when creating a new project, but you can always change it later.
+
+Here's the section of the default configuration file (<tt>config/database.yml</tt>) with connection information for the development environment:
+
+<yaml>
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 5000
+</yaml>
+
+NOTE: Rails uses an SQLite3 database for data storage by default because it is a zero configuration database that just works. Rails also supports MySQL and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it.
+
+h5. Configuring a MySQL Database
+
+If you choose to use MySQL instead of the shipped SQLite3 database, your +config/database.yml+ will look a little different. Here's the development section:
+
+<yaml>
+development:
+ adapter: mysql2
+ encoding: utf8
+ database: blog_development
+ pool: 5
+ username: root
+ password:
+ socket: /tmp/mysql.sock
+</yaml>
+
+If your development computer's MySQL installation includes a root user with an empty password, this configuration should work for you. Otherwise, change the username and password in the +development+ section as appropriate.
+
+h5. Configuring a PostgreSQL Database
+
+If you choose to use PostgreSQL, your +config/database.yml+ will be customized to use PostgreSQL databases:
+
+<yaml>
+development:
+ adapter: postgresql
+ encoding: unicode
+ database: blog_development
+ pool: 5
+ username: blog
+ password:
+</yaml>
+
+Prepared Statements can be disabled thus:
+
+<yaml>
+production:
+ adapter: postgresql
+ prepared_statements: false
+</yaml>
+
+h5. Configuring an SQLite3 Database for JRuby Platform
+
+If you choose to use SQLite3 and are using JRuby, your +config/database.yml+ will look a little different. Here's the development section:
+
+<yaml>
+development:
+ adapter: jdbcsqlite3
+ database: db/development.sqlite3
+</yaml>
+
+h5. Configuring a MySQL Database for JRuby Platform
+
+If you choose to use MySQL and are using JRuby, your +config/database.yml+ will look a little different. Here's the development section:
+
+<yaml>
+development:
+ adapter: jdbcmysql
+ database: blog_development
+ username: root
+ password:
+</yaml>
+
+h5. Configuring a PostgreSQL Database for JRuby Platform
+
+If you choose to use PostgreSQL and are using JRuby, your +config/database.yml+ will look a little different. Here's the development section:
+
+<yaml>
+development:
+ adapter: jdbcpostgresql
+ encoding: unicode
+ database: blog_development
+ username: blog
+ password:
+</yaml>
+
+Change the username and password in the +development+ section as appropriate.
h3. Rails Environment Settings
@@ -447,7 +581,7 @@ Some parts of Rails can also be configured externally by supplying environment v
h3. Using Initializer Files
-After loading the framework and any gems and plugins in your application, Rails turns to loading initializers. An initializer is any Ruby file stored under +config/initializers+ in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks, plugins and gems are loaded, such as options to configure settings for these parts.
+After loading the framework and any gems in your application, Rails turns to loading initializers. An initializer is any Ruby file stored under +config/initializers+ in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks and gems are loaded, such as options to configure settings for these parts.
NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the initializers folder on down.
@@ -455,19 +589,19 @@ TIP: If you have any ordering dependency in your initializers, you can control t
h3. Initialization events
-Rails has 5 initialization events which can be hooked into (listed in the order that they are ran):
+Rails has 5 initialization events which can be hooked into (listed in the order that they are run):
* +before_configuration+: This is run as soon as the application constant inherits from +Rails::Application+. The +config+ calls are evaluated before this happens.
* +before_initialize+: This is run directly before the initialization process of the application occurs with the +:bootstrap_hook+ initializer near the beginning of the Rails initialization process.
-* +to_prepare+: Run after the initializers are ran for all Railties (including the application itself), but before eager loading and the middleware stack is built. More importantly, will run upon every request in +development+, but only once (during boot-up) in +production+ and +test+.
+* +to_prepare+: Run after the initializers are run for all Railties (including the application itself), but before eager loading and the middleware stack is built. More importantly, will run upon every request in +development+, but only once (during boot-up) in +production+ and +test+.
-* +before_eager_load+: This is run directly before eager loading occurs, which is the default behaviour for the _production_ environment and not for the +development+ environment.
+* +before_eager_load+: This is run directly before eager loading occurs, which is the default behaviour for the +production+ environment and not for the +development+ environment.
* +after_initialize+: Run directly after the initialization of the application, but before the application initializers are run.
-To define an event for these hooks, use the block syntax within a +Rails::Aplication+, +Rails::Railtie+ or +Rails::Engine+ subclass:
+To define an event for these hooks, use the block syntax within a +Rails::Application+, +Rails::Railtie+ or +Rails::Engine+ subclass:
<ruby>
module YourApp
@@ -522,7 +656,7 @@ Serves as a placeholder so that +:load_environment_config+ can be defined to run
*+initialize_logger+* Initializes the logger (an +ActiveSupport::BufferedLogger+ object) for the application and makes it accessible at +Rails.logger+, provided that no initializer inserted before this point has defined +Rails.logger+.
-*+initialize_cache+* If +RAILS_CACHE+ isn't set yet, initializes the cache by referencing the value in +config.cache_store+ and stores the outcome as +RAILS_CACHE+. If this object responds to the +middleware+ method, its middleware is inserted before +Rack::Runtime+ in the middleware stack.
+*+initialize_cache+* If +Rails.cache+ isn't set yet, initializes the cache by referencing the value in +config.cache_store+ and stores the outcome as +Rails.cache+. If this object responds to the +middleware+ method, its middleware is inserted before +Rack::Runtime+ in the middleware stack.
*+set_clear_dependencies_hook+* Provides a hook for +active_record.set_dispatch_hooks+ to use, which will run before this initializer. This initializer -- which runs only if +cache_classes+ is set to +false+ -- uses +ActionDispatch::Callbacks.after+ to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request.
@@ -560,7 +694,7 @@ The error occurred while evaluating nil.each
*+action_controller.logger+* Sets +ActionController::Base.logger+ -- if it's not already set -- to +Rails.logger+.
-*+action_controller.initialize_framework_caches+* Sets +ActionController::Base.cache_store+ -- if it's not already set -- to +RAILS_CACHE+.
+*+action_controller.initialize_framework_caches+* Sets +ActionController::Base.cache_store+ -- if it's not already set -- to +Rails.cache+.
*+action_controller.set_configs+* Sets up Action Controller by using the settings in +config.action_controller+ by +send+'ing the method names as setters to +ActionController::Base+ and passing the values through.
@@ -584,8 +718,6 @@ The error occurred while evaluating nil.each
*+action_mailer.compile_config_methods+* Initializes methods for the config settings specified so that they are quicker to access.
-*+active_resource.set_configs+* Sets up Active Resource by using the settings in +config.active_resource+ by +send+'ing the method names as setters to +ActiveResource::Base+ and passing the values through.
-
*+set_load_path+* This initializer runs before +bootstrap_hook+. Adds the +vendor+, +lib+, all directories of +app+ and any paths specified by +config.load_paths+ to +$LOAD_PATH+.
*+set_autoload_paths+* This initializer runs before +bootstrap_hook+. Adds all sub-directories of +app+ and paths specified by +config.autoload_paths+ to +ActiveSupport::Dependencies.autoload_paths+.
@@ -602,9 +734,9 @@ The error occurred while evaluating nil.each
*+prepend_helpers_path+* Adds the directory +app/helpers+ from the application, railties and engines to the lookup path for helpers for the application.
-*+load_config_initializers+* Loads all Ruby files from +config/initializers+ in the application, railties and engines. The files in this directory can be used to hold configuration settings that should be made after all of the frameworks and plugins are loaded.
+*+load_config_initializers+* Loads all Ruby files from +config/initializers+ in the application, railties and engines. The files in this directory can be used to hold configuration settings that should be made after all of the frameworks are loaded.
-*+engines_blank_point+* Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are ran.
+*+engines_blank_point+* Provides a point-in-initialization to hook into if you wish to do anything before engines are loaded. After this point, all railtie and engine initializers are run.
*+add_generator_templates+* Finds templates for generators at +lib/templates+ for the application, railities and engines and adds these to the +config.generators.templates+ setting, which will make the templates available for all generators to reference.
@@ -623,3 +755,21 @@ The error occurred while evaluating nil.each
*+set_routes_reloader+* Configures Action Dispatch to reload the routes file using +ActionDispatch::Callbacks.to_prepare+.
*+disable_dependency_loading+* Disables the automatic dependency loading if the +config.cache_classes+ is set to true and +config.dependency_loading+ is set to false.
+
+h3. Database pooling
+
+Active Record database connections are managed by +ActiveRecord::ConnectionAdapters::ConnectionPool+ which ensures that a connection pool synchronizes the amount of thread access to a limited number of database connections. This limit defaults to 5 and can be configured in +database.yml+.
+
+<ruby>
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 5000
+</ruby>
+
+Since the connection pooling is handled inside of ActiveRecord by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit.
+
+Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue.
+
+NOTE. If you have enabled +Rails.threadsafe!+ mode then there could be a chance that several threads may be accessing multiple connections simultaneously. So depending on your current request load, you could very well have multiple threads contending for a limited amount of connections.
diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/guides/source/contributing_to_ruby_on_rails.textile
index 5848172510..df475a2359 100644
--- a/railties/guides/source/contributing_to_ruby_on_rails.textile
+++ b/guides/source/contributing_to_ruby_on_rails.textile
@@ -8,29 +8,29 @@ This guide covers ways in which _you_ can become a part of the ongoing developme
* Contributing to the Ruby on Rails documentation
* Contributing to the Ruby on Rails code
-Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation. All with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches.
+Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation -- all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches.
endprologue.
h3. Reporting an Issue
-Ruby on Rails uses "GitHub Issue Tracking":https://github.com/rails/rails/issues to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) GitHub account in order to either submit an issue, comment on them or create pull requests.
+Ruby on Rails uses "GitHub Issue Tracking":https://github.com/rails/rails/issues to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) GitHub account in order to submit an issue, to comment on them or to create pull requests.
NOTE: Bugs in the most recent released version of Ruby on Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this guide you'll find out how to get edge Rails for testing.
h4. Creating a Bug Report
-If you've found a problem in Ruby on Rails which is not a security risk do a search in GitHub Issues in case it was already reported. If you find no issue addressing it you can "add a new one":https://github.com/rails/rails/issues/new. (See the next section for reporting security issues.)
+If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under "Issues":https://github.com/rails/rails/issues in case it was already reported. If you find no issue addressing it you can "add a new one":https://github.com/rails/rails/issues/new. (See the next section for reporting security issues.)
-At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need to at least post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix.
+At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need at least to post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself -- and others -- to replicate the bug and figure out a fix.
-Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment.
+Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment.
h4. Special Treatment for Security Issues
WARNING: Please do not report security vulnerabilities with public GitHub issue reports. The "Rails security policy page":http://rubyonrails.org/security details the procedure to follow for security issues.
-h4. What About Feature Requests?
+h4. What about Feature Requests?
Please don't put "feature request" items into GitHub Issues. If there's a new feature that you want to see added to Ruby on Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Ruby on Rails. If you enter a wishlist item in GitHub Issues with no code, you can expect it to be marked "invalid" as soon as it's reviewed.
@@ -38,11 +38,11 @@ h3. Running the Test Suite
To move on from submitting bugs to helping resolve existing issues or contributing your own code to Ruby on Rails, you _must_ be able to run its test suite. In this section of the guide you'll learn how to set up the tests on your own computer.
-h4. Install git
+h4. Install Git
Ruby on Rails uses git for source code control. The "git homepage":http://git-scm.com/ has installation instructions. There are a variety of resources on the net that will help you get familiar with git:
-* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by.
+* "Everyday Git":http://schacon.github.com/git/everyday.html will teach you just enough about git to get by.
* The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow.
* "GitHub":http://help.github.com offers links to a variety of git resources.
* "Pro Git":http://progit.org/book/ is an entire book about git with a Creative Commons license.
@@ -66,7 +66,7 @@ Install first libxml2 and libxslt together with their development files for Noko
$ sudo apt-get install libxml2 libxml2-dev libxslt1-dev
</shell>
-Also, SQLite3 and its development files for the +sqlite3-ruby+ gem, in Ubuntu you're done with
+Also, SQLite3 and its development files for the +sqlite3-ruby+ gem -- in Ubuntu you're done with just
<shell>
$ sudo apt-get install sqlite3 libsqlite3-dev
@@ -76,6 +76,7 @@ Get a recent version of "Bundler":http://gembundler.com/:
<shell>
$ gem install bundler
+$ gem update bundler
</shell>
and run:
@@ -84,53 +85,60 @@ and run:
$ bundle install --without db
</shell>
-This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back at these soon. With dependencies installed, you can run the test suite with:
+This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back to these soon. With dependencies installed, you can run the test suite with:
<shell>
-$ rake test
+$ bundle exec rake test
</shell>
-You can also run tests for a specific framework, like Action Pack, by going into its directory and executing the same command:
+You can also run tests for a specific component, like Action Pack, by going into its directory and executing the same command:
<shell>
$ cd actionpack
-$ rake test
+$ bundle exec rake test
</shell>
-If you want to run tests from the specific directory use the +TEST_DIR+ environment variable. For example, this will run tests inside +railties/test/generators+ directory only:
+If you want to run the tests located in a specific directory use the +TEST_DIR+ environment variable. For example, this will run the tests of the +railties/test/generators+ directory only:
<shell>
$ cd railties
-$ TEST_DIR=generators rake test
+$ TEST_DIR=generators bundle exec rake test
+</shell>
+
+You can run any single test separately too:
+
+<shell>
+$ cd actionpack
+$ ruby -Itest test/template/form_helper_test.rb
</shell>
h4. Warnings
-The test suite runs with warnings enabled. Ideally Ruby on Rails should issue no warning, but there may be a few, and also some from third-party libraries. Please ignore (or fix!) them if any, and submit patches that do not issue new warnings.
+The test suite runs with warnings enabled. Ideally, Ruby on Rails should issue no warnings, but there may be a few, as well as some from third-party libraries. Please ignore (or fix!) them, if any, and submit patches that do not issue new warnings.
-As of this writing they are specially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
+As of this writing (December, 2010) they are specially noisy with Ruby 1.9. If you are sure about what you are doing and would like to have a more clear output, there's a way to override the flag:
<shell>
-$ RUBYOPT=-W0 rake test
+$ RUBYOPT=-W0 bundle exec rake test
</shell>
h4. Testing Active Record
-The test suite of Active Record attempts to run four times, once for SQLite3, once for each of the two MySQL gems (+mysql+ and +mysql2+), and once for PostgreSQL. We are going to see now how to setup the environment for them.
+The test suite of Active Record attempts to run four times: once for SQLite3, once for each of the two MySQL gems (+mysql+ and +mysql2+), and once for PostgreSQL. We are going to see now how to set up the environment for them.
WARNING: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite3. Subtle differences between the various adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
-h5. Set up Database Configuration
+h5. Database Configuration
The Active Record test suite requires a custom config file: +activerecord/test/config.yml+. An example is provided in +activerecord/test/config.example.yml+ which can be copied and used as needed for your environment.
h5. SQLite3
-The gem +sqlite3-ruby+ does not belong to the "db" group indeed, if you followed the instructions above you're ready. This is how you run the Active Record test suite only for SQLite3:
+The gem +sqlite3-ruby+ does not belong to the "db" group. Indeed, if you followed the instructions above you're ready. This is how you run the Active Record test suite only for SQLite3:
<shell>
$ cd activerecord
-$ rake test_sqlite3
+$ bundle exec rake test_sqlite3
</shell>
h5. MySQL and PostgreSQL
@@ -167,13 +175,13 @@ $ cd activerecord
$ rake mysql:build_databases
</shell>
-PostgreSQL's authentication works differently. A simple way to setup the development environment for example is to run with your development account
+PostgreSQL's authentication works differently. A simple way to set up the development environment for example is to run with your development account
<shell>
$ sudo -u postgres createuser --superuser $USER
</shell>
-and after that create the test databases with
+and then create the test databases with
<shell>
$ cd activerecord
@@ -182,9 +190,9 @@ $ rake postgresql:build_databases
NOTE: Using the rake task to create the test databases ensures they have the correct character set and collation.
-If you’re using another database, check the files under +activerecord/test/connections+ for default connection information. You can edit these files if you _must_ on your machine to provide different credentials, but obviously you should not push any such changes back to Rails.
+If you’re using another database, check the files under +activerecord/test/connections+ for default connection information. You can edit these files to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails.
-You can now run tests as you did for +sqlite3+, the tasks are
+You can now run the tests as you did for +sqlite3+. The tasks are respectively
<shell>
test_mysql
@@ -192,17 +200,23 @@ test_mysql2
test_postgresql
</shell>
-respectively. As we mentioned before
+As we mentioned before
<shell>
-$ rake test
+$ bundle exec rake test
</shell>
will now run the four of them in turn.
-You can also invoke +test_jdbcmysql+, +test_jdbcsqlite3+ or +test_jdbcpostgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the continuous integration server runs.
+You can also run any single test separately:
-h4. Older versions of Ruby on Rails
+<shell>
+$ ARCONN=sqlite3 ruby -Itest test/cases/associations/has_many_associations_test.rb
+</shell>
+
+You can invoke +test_jdbcmysql+, +test_jdbcsqlite3+ or +test_jdbcpostgresql+ also. See the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/travis.rb+ for the test suite run by the continuous integration server.
+
+h4. Older Versions of Ruby on Rails
If you want to add a fix to older versions of Ruby on Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to the 3-0-stable branch:
@@ -215,13 +229,13 @@ TIP: You may want to "put your git branch name in your shell prompt":http://qugs
h3. Helping to Resolve Existing Issues
-As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the "Everyone's Issues":https://github.com/rails/rails/issues?sort=created&direction=desc&state=open&page=1 list in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually:
+As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the "Everyone's Issues":https://github.com/rails/rails/issues list in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually:
h4. Verifying Bug Reports
-For starters, it helps to just verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the issue saying that you're seeing the same thing.
+For starters, it helps just to verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the issue saying that you're seeing the same thing.
-If something is very vague, can you help squish it down into something specific? Maybe you can provide additional information to help reproduce a bug, or eliminate needless steps that aren't required to help demonstrate the problem.
+If something is very vague, can you help squash it down into something specific? Maybe you can provide additional information to help reproduce a bug, or help by eliminating needless steps that aren't required to demonstrate the problem.
If you find a bug report without a test, it's very useful to contribute a failing test. This is also a great way to get started exploring the source code: looking at the existing test files will teach you how to write more tests. New tests are best contributed in the form of a patch, as explained later on in the "Contributing to the Rails Code" section.
@@ -229,49 +243,51 @@ Anything you can do to make bug reports more succinct or easier to reproduce is
h4. Testing Patches
-You can also help out by examining pull requests that have been submitted to Ruby on Rails via GitHub. To apply someone's changes you need to first create a dedicated branch:
+You can also help out by examining pull requests that have been submitted to Ruby on Rails via GitHub. To apply someone's changes you need first to create a dedicated branch:
<shell>
$ git checkout -b testing_branch
</shell>
-Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to the topic branch located at https://github.com/JohnSmith/rails.
+Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails.
<shell>
$ git remote add JohnSmith git://github.com/JohnSmith/rails.git
-$ git pull JohnSmith topic
+$ git pull JohnSmith orange
</shell>
After applying their branch, test it out! Here are some things to think about:
* Does the change actually work?
* Are you happy with the tests? Can you follow what they're testing? Are there any tests missing?
-* Does it have proper documentation coverage? Should documentation elsewhere be updated?
+* Does it have the proper documentation coverage? Should documentation elsewhere be updated?
* Do you like the implementation? Can you think of a nicer or faster way to implement a part of their change?
Once you're happy that the pull request contains a good change, comment on the GitHub issue indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like:
<blockquote>
-I like the way you've restructured that code in generate_finder_sql, much nicer. The tests look good too.
+I like the way you've restructured that code in generate_finder_sql -- much nicer. The tests look good too.
</blockquote>
If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request.
h3. Contributing to the Rails Documentation
-Ruby on Rails has two main sets of documentation: The guides help you to learn Ruby on Rails, and the API is a reference.
+Ruby on Rails has two main sets of documentation: the guides help you in learning about Ruby on Rails, and the API is a reference.
-You can help improve the Rails guides by making them more coherent, adding missing information, correcting factual errors, fixing typos, bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see "Translating Rails Guides":https://wiki.github.com/lifo/docrails/translating-rails-guides.
+You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see "Translating Rails Guides":https://wiki.github.com/lifo/docrails/translating-rails-guides.
-If you're confident about your changes, you can push them yourself directly via "docrails":https://github.com/lifo/docrails. docrails is a branch with an *open commit policy* and public write access. Commits to docrails are still reviewed, but that happens after they are pushed. docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
+If you're confident about your changes, you can push them directly yourself via "docrails":https://github.com/lifo/docrails. Docrails is a branch with an *open commit policy* and public write access. Commits to docrails are still reviewed, but this happens after they are pushed. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation.
If you are unsure of the documentation changes, you can create an issue in the "Rails":https://github.com/rails/rails/issues issues tracker on GitHub.
When working with documentation, please take into account the "API Documentation Guidelines":api_documentation_guidelines.html and the "Ruby on Rails Guides Guidelines":ruby_on_rails_guides_guidelines.html.
-NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. docrails is only used for isolated documentation improvements.
+NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. Docrails is only used for isolated documentation improvements.
-WARNING: docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails.
+NOTE: To help our CI servers you can add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes.
+
+WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails.
h3. Contributing to the Rails Code
@@ -290,37 +306,37 @@ $ cd rails
$ git checkout -b my_new_branch
</shell>
-It doesn’t really matter what name you use, because this branch will only exist on your local computer and your personal repository on Github. It won't be part of the Rails git repository.
+It doesn’t matter much what name you use, because this branch will only exist on your local computer and your personal repository on Github. It won't be part of the Rails git repository.
h4. Write Your Code
Now get busy and add or edit code. You’re on your branch now, so you can write whatever you want (you can check to make sure you’re on the right branch with +git branch -a+). But if you’re planning to submit your change back for inclusion in Rails, keep a few things in mind:
-* Get the code right
-* Use Rails idioms and helpers
-* Include tests that fail without your code, and pass with it
-* Update the documentation, the surrounding one, examples elsewhere, guides, whatever is affected by your contribution
+* Get the code right.
+* Use Rails idioms and helpers.
+* Include tests that fail without your code, and pass with it.
+* Update the (surrounding) documentation, examples elsewhere, and the guides: whatever is affected by your contribution.
h4. Follow the Coding Conventions
Rails follows a simple set of coding style conventions.
-* Two spaces, no tabs.
-* No trailing whitespace. Blank lines should not have any space.
+* Two spaces, no tabs (for indentation).
+* No trailing whitespace. Blank lines should not have any spaces.
* Indent after private/protected.
* Prefer +&amp;&amp;+/+||+ over +and+/+or+.
-* Prefer class << self block over self.method for class methods.
-* +MyClass.my_method(my_arg)+ not +my_method( my_arg )+ or +my_method my_arg+.
-* a = b and not a=b.
-* Follow the conventions you see used in the source already.
+* Prefer class << self over self.method for class methods.
+* Use +MyClass.my_method(my_arg)+ not +my_method( my_arg )+ or +my_method my_arg+.
+* Use a = b and not a=b.
+* Follow the conventions in the source you see used already.
-These are some guidelines and please use your best judgment in using them.
+The above are guidelines -- please use your best judgment in using them.
h4. Sanity Check
You should not be the only person who looks at the code before you submit it. You know at least one other Rails developer, right? Show them what you’re doing and ask for feedback. Doing this in private before you push a patch out publicly is the “smoke test” for a patch: if you can’t convince one other developer of the beauty of your code, you’re unlikely to convince the core team either.
-You might also want to check out the "RailsBridge BugMash":http://wiki.railsbridge.org/projects/railsbridge/wiki/BugMash as a way to get involved in a group effort to improve Rails. This can help you get started and help check your code when you're writing your first patches.
+You might want also to check out the "RailsBridge BugMash":http://wiki.railsbridge.org/projects/railsbridge/wiki/BugMash as a way to get involved in a group effort to improve Rails. This can help you get started and help you check your code when you're writing your first patches.
h4. Commit Your Changes
@@ -330,13 +346,15 @@ When you're happy with the code on your computer, you need to commit the changes
$ git commit -a -m "Here is a commit message on what I changed in this commit"
</shell>
-h4. Update master
+TIP. Please squash your commits into a single commit when appropriate. This simplifies future cherry picks, and also keeps the git log clean.
+
+h4. Update Master
It’s pretty likely that other changes to master have happened while you were working. Go get them:
<shell>
$ git checkout master
-$ git pull
+$ git pull --rebase
</shell>
Now reapply your patch on top of the latest changes:
@@ -368,21 +386,21 @@ h4. Issue a Pull Request
Navigate to the Rails repository you just pushed to (e.g. https://github.com/your-user-name/rails) and press "Pull Request" in the upper right hand corner.
-Write your branch name in branch field (is filled with master by default) and press "Update Commit Range"
+Write your branch name in the branch field (this is filled with "master" by default) and press "Update Commit Range".
-Ensure the changesets you introduced are included in the "Commits" tab and that the "Files Changed" incorporate all of your changes.
+Ensure the changesets you introduced are included in the "Commits" tab. Ensure that the "Files Changed" incorporate all of your changes.
-Fill in some details about your potential patch including a meaningful title. When finished, press "Send pull request." Rails Core will be notified about your submission.
+Fill in some details about your potential patch including a meaningful title. When finished, press "Send pull request". The Rails core team will be notified about your submission.
-h4. Get Some Feedback
+h4. Get some Feedback
Now you need to get other people to look at your patch, just as you've looked at other people's patches. You can use the "rubyonrails-core mailing list":http://groups.google.com/group/rubyonrails-core/ or the #rails-contrib channel on IRC freenode for this. You might also try just talking to Rails developers that you know.
h4. Iterate as Necessary
-It’s entirely possible that the feedback you get will suggest changes. Don’t get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it’s worth making the tweaks and resubmitting. If the feedback is that your code doesn’t belong in the core, you might still think about releasing it as a plugin.
+It’s entirely possible that the feedback you get will suggest changes. Don’t get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it’s worth making the tweaks and resubmitting. If the feedback is that your code doesn’t belong in the core, you might still think about releasing it as a gem.
-And then...think about your next contribution!
+And then ... think about your next contribution!
h3. Rails Contributors
diff --git a/railties/guides/source/credits.html.erb b/guides/source/credits.html.erb
index da6bd6acdf..da6bd6acdf 100644
--- a/railties/guides/source/credits.html.erb
+++ b/guides/source/credits.html.erb
diff --git a/railties/guides/source/debugging_rails_applications.textile b/guides/source/debugging_rails_applications.textile
index 3552c68418..45fa4ada78 100644
--- a/railties/guides/source/debugging_rails_applications.textile
+++ b/guides/source/debugging_rails_applications.textile
@@ -124,7 +124,7 @@ h4. Log Levels
When something is logged it's printed into the corresponding log if the log level of the message is equal or higher than the configured log level. If you want to know the current log level you can call the +Rails.logger.level+ method.
-The available log levels are: +:debug+, +:info+, +:warn+, +:error+, and +:fatal+, corresponding to the log level numbers from 0 up to 4 respectively. To change the default log level, use
+The available log levels are: +:debug+, +:info+, +:warn+, +:error+, +:fatal+, and +:unknown+, corresponding to the log level numbers from 0 up to 5 respectively. To change the default log level, use
<ruby>
config.log_level = :warn # In any environment initializer, or
@@ -191,7 +191,7 @@ Completed in 0.01224 (81 reqs/sec) | DB: 0.00044 (3%) | 302 Found [http://localh
Adding extra logging like this makes it easy to search for unexpected or unusual behavior in your logs. If you add extra logging, be sure to make sensible use of log levels, to avoid filling your production logs with useless trivia.
-h3. Debugging with +ruby-debug+
+h3. Debugging with the +debugger+ gem
When your code is behaving in unexpected ways, you can try printing to logs or the console to diagnose the problem. Unfortunately, there are times when this sort of error tracking is not effective in finding the root cause of a problem. When you actually need to journey into your running source code, the debugger is your best companion.
@@ -199,17 +199,13 @@ The debugger can also help you if you want to learn about the Rails source code
h4. Setup
-The debugger used by Rails, +ruby-debug+, comes as a gem. To install it, just run:
+Rails uses the +debugger+ gem to set breakpoints and step through live code. To install it, just run:
<shell>
-$ sudo gem install ruby-debug
+$ gem install debugger
</shell>
-TIP: If you are using Ruby 1.9, you can install a compatible version of +ruby-debug+ by running +sudo gem install ruby-debug19+
-
-In case you want to download a particular version or get the source code, refer to the "project's page on rubyforge":http://rubyforge.org/projects/ruby-debug/.
-
-Rails has had built-in support for ruby-debug since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the +debugger+ method.
+Rails has had built-in support for debugging since Rails 2.0. Inside any Rails application you can invoke the debugger by calling the +debugger+ method.
Here's an example:
@@ -238,11 +234,11 @@ $ rails server --debugger
...
</shell>
-TIP: In development mode, you can dynamically +require \'ruby-debug\'+ instead of restarting the server, if it was started without +--debugger+.
+TIP: In development mode, you can dynamically +require \'debugger\'+ instead of restarting the server, if it was started without +--debugger+.
h4. The Shell
-As soon as your application calls the +debugger+ method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at ruby-debug's prompt +(rdb:n)+. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run.
+As soon as your application calls the +debugger+ method, the debugger will be started in a debugger shell inside the terminal window where you launched your application server, and you will be placed at the debugger's prompt +(rdb:n)+. The _n_ is the thread number. The prompt will also show you the next line of code that is waiting to run.
If you got there by a browser request, the browser tab containing the request will be hung until the debugger has finished and the trace has finished processing the entire request.
@@ -270,7 +266,7 @@ continue edit frame method putl set tmate where
TIP: To view the help menu for any command use +help &lt;command-name&gt;+ in active debug mode. For example: _+help var+_
-The next command to learn is one of the most useful: +list+. You can also abbreviate ruby-debug commands by supplying just enough letters to distinguish them from other commands, so you can also use +l+ for the +list+ command.
+The next command to learn is one of the most useful: +list+. You can abbreviate any debugging command by supplying just enough letters to distinguish them from other commands, so you can also use +l+ for the +list+ command.
This command shows you where you are in the code by printing 10 lines centered around the current line; the current line in this particular case is line 6 and is marked by +=>+.
@@ -347,7 +343,7 @@ h4. The Context
When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack.
-ruby-debug creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped.
+The debugger creates a context when a stopping point or an event is reached. The context has information about the suspended program which enables a debugger to inspect the frame stack, evaluate variables from the perspective of the debugged program, and contains information about the place where the debugged program is stopped.
At any time you can call the +backtrace+ command (or its alias +where+) to print the backtrace of the application. This can be very helpful to know how you got where you are. If you ever wondered about how you got somewhere in your code, then +backtrace+ will supply the answer.
@@ -463,9 +459,9 @@ h4. Step by Step
Now you should know where you are in the running trace and be able to print the available variables. But lets continue and move on with the application execution.
-Use +step+ (abbreviated +s+) to continue running your program until the next logical stopping point and return control to ruby-debug.
+Use +step+ (abbreviated +s+) to continue running your program until the next logical stopping point and return control to the debugger.
-TIP: You can also use +step+ _n_+ and +step- _n_+ to move forward or backward _n_ steps respectively.
+TIP: You can also use <tt>step<plus> n</tt> and <tt>step- n</tt> to move forward or backward +n+ steps respectively.
You may also use +next+ which is similar to step, but function or method calls that appear within the line of code are executed without stopping. As with step, you may use plus sign to move _n_ steps.
@@ -485,12 +481,12 @@ class Author < ActiveRecord::Base
end
</ruby>
-TIP: You can use ruby-debug while using +rails console+. Just remember to +require "ruby-debug"+ before calling the +debugger+ method.
+TIP: You can use the debugger while using +rails console+. Just remember to +require "debugger"+ before calling the +debugger+ method.
<shell>
$ rails console
Loading development environment (Rails 3.1.0)
->> require "ruby-debug"
+>> require "debugger"
=> []
>> author = Author.first
=> #<Author id: 1, first_name: "Bob", last_name: "Smith", created_at: "2008-07-31 12:46:10", updated_at: "2008-07-31 12:46:10">
@@ -603,7 +599,7 @@ A simple quit tries to terminate all threads in effect. Therefore your server wi
h4. Settings
-There are some settings that can be configured in ruby-debug to make it easier to debug your code. Here are a few of the available options:
+The +debugger+ gem can automatically show the code you're stepping through and reload it when you change it in an editor. Here are a few of the available options:
* +set reload+: Reload source code when changed.
* +set autolist+: Execute +list+ command on every breakpoint.
@@ -612,7 +608,7 @@ There are some settings that can be configured in ruby-debug to make it easier t
You can see the full list by using +help set+. Use +help set _subcommand_+ to learn about a particular +set+ command.
-TIP: You can include any number of these configuration lines inside a +.rdebugrc+ file in your HOME directory. ruby-debug will read this file every time it is loaded and configure itself accordingly.
+TIP: You can save these settings in an +.rdebugrc+ file in your home directory. The debugger reads these global settings when it starts.
Here's a good start for an +.rdebugrc+:
@@ -637,7 +633,7 @@ If a Ruby object does not go out of scope, the Ruby Garbage Collector won't swee
To install it run:
<shell>
-$ sudo gem install bleak_house
+$ gem install bleak_house
</shell>
Then setup your application for profiling. Then add the following at the bottom of config/environment.rb:
@@ -703,11 +699,12 @@ There are some Rails plugins to help you to find errors and debug your applicati
h3. References
* "ruby-debug Homepage":http://www.datanoise.com/ruby-debug
+* "debugger Homepage":http://github.com/cldwalker/debugger
* "Article: Debugging a Rails application with ruby-debug":http://www.sitepoint.com/article/debug-rails-app-ruby-debug/
* "ruby-debug Basics screencast":http://brian.maybeyoureinsane.net/blog/2007/05/07/ruby-debug-basics-screencast/
-* "Ryan Bate's ruby-debug screencast":http://railscasts.com/episodes/54-debugging-with-ruby-debug
-* "Ryan Bate's stack trace screencast":http://railscasts.com/episodes/24-the-stack-trace
-* "Ryan Bate's logger screencast":http://railscasts.com/episodes/56-the-logger
+* "Ryan Bates' debugging ruby (revised) screencast":http://railscasts.com/episodes/54-debugging-ruby-revised
+* "Ryan Bates' stack trace screencast":http://railscasts.com/episodes/24-the-stack-trace
+* "Ryan Bates' logger screencast":http://railscasts.com/episodes/56-the-logger
* "Debugging with ruby-debug":http://bashdb.sourceforge.net/ruby-debug.html
* "ruby-debug cheat sheet":http://cheat.errtheblog.com/s/rdebug/
* "Ruby on Rails Wiki: How to Configure Logging":http://wiki.rubyonrails.org/rails/pages/HowtoConfigureLogging
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
new file mode 100644
index 0000000000..2acdcca39c
--- /dev/null
+++ b/guides/source/documents.yaml
@@ -0,0 +1,167 @@
+-
+ name: Start Here
+ documents:
+ -
+ name: Getting Started with Rails
+ url: getting_started.html
+ description: Everything you need to know to install Rails and create your first application.
+-
+ name: Models
+ documents:
+ -
+ name: Rails Database Migrations
+ url: migrations.html
+ description: This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner.
+ -
+ name: Active Record Validations and Callbacks
+ url: active_record_validations_callbacks.html
+ description: This guide covers how you can use Active Record validations and callbacks.
+ -
+ name: Active Record Associations
+ url: association_basics.html
+ description: This guide covers all the associations provided by Active Record.
+ -
+ name: Active Record Query Interface
+ url: active_record_querying.html
+ description: This guide covers the database query interface provided by Active Record.
+-
+ name: Views
+ documents:
+ -
+ name: Layouts and Rendering in Rails
+ url: layouts_and_rendering.html
+ description: This guide covers the basic layout features of Action Controller and Action View, including rendering and redirecting, using content_for blocks, and working with partials.
+ -
+ name: Action View Form Helpers
+ url: form_helpers.html
+ description: Guide to using built-in Form helpers.
+-
+ name: Controllers
+ documents:
+ -
+ name: Action Controller Overview
+ url: action_controller_overview.html
+ description: This guide covers how controllers work and how they fit into the request cycle in your application. It includes sessions, filters, and cookies, data streaming, and dealing with exceptions raised by a request, among other topics.
+ -
+ name: Rails Routing from the Outside In
+ url: routing.html
+ description: This guide covers the user-facing features of Rails routing. If you want to understand how to use routing in your own Rails applications, start here.
+-
+ name: Digging Deeper
+ documents:
+ -
+ name: Active Support Core Extensions
+ url: active_support_core_extensions.html
+ description: This guide documents the Ruby core extensions defined in Active Support.
+ -
+ name: Rails Internationalization API
+ url: i18n.html
+ description: This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on.
+ -
+ name: Action Mailer Basics
+ url: action_mailer_basics.html
+ work_in_progress: true
+ description: This guide describes how to use Action Mailer to send and receive emails.
+ -
+ name: Testing Rails Applications
+ url: testing.html
+ work_in_progress: true
+ description: This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from 'What is a test?' to the testing APIs. Enjoy.
+ -
+ name: Securing Rails Applications
+ url: security.html
+ description: This guide describes common security problems in web applications and how to avoid them with Rails.
+ -
+ name: Debugging Rails Applications
+ url: debugging_rails_applications.html
+ description: This guide describes how to debug Rails applications. It covers the different ways of achieving this and how to understand what is happening "behind the scenes" of your code.
+ -
+ name: Performance Testing Rails Applications
+ url: performance_testing.html
+ description: This guide covers the various ways of performance testing a Ruby on Rails application.
+ -
+ name: Configuring Rails Applications
+ url: configuring.html
+ description: This guide covers the basic configuration settings for a Rails application.
+ -
+ name: Rails Command Line Tools and Rake Tasks
+ url: command_line.html
+ description: This guide covers the command line tools and rake tasks provided by Rails.
+ -
+ name: Caching with Rails
+ work_in_progress: true
+ url: caching_with_rails.html
+ description: Various caching techniques provided by Rails.
+ -
+ name: Asset Pipeline
+ url: asset_pipeline.html
+ description: This guide documents the asset pipeline.
+ -
+ name: Getting Started with Engines
+ url: engines.html
+ description: This guide explains how to write a mountable engine.
+ work_in_progress: true
+ -
+ name: The Rails Initialization Process
+ work_in_progress: true
+ url: initialization.html
+ description: This guide explains the internals of the Rails initialization process as of Rails 3.1
+-
+ name: Extending Rails
+ documents:
+ -
+ name: The Basics of Creating Rails Plugins
+ work_in_progress: true
+ url: plugins.html
+ description: This guide covers how to build a plugin to extend the functionality of Rails.
+ -
+ name: Rails on Rack
+ url: rails_on_rack.html
+ description: This guide covers Rails integration with Rack and interfacing with other Rack components.
+ -
+ name: Creating and Customizing Rails Generators
+ url: generators.html
+ description: This guide covers the process of adding a brand new generator to your extension or providing an alternative to an element of a built-in Rails generator (such as providing alternative test stubs for the scaffold generator).
+-
+ name: Contributing to Ruby on Rails
+ documents:
+ -
+ name: Contributing to Ruby on Rails
+ url: contributing_to_ruby_on_rails.html
+ description: Rails is not 'somebody else's framework.' This guide covers a variety of ways that you can get involved in the ongoing development of Rails.
+ -
+ name: API Documentation Guidelines
+ url: api_documentation_guidelines.html
+ description: This guide documents the Ruby on Rails API documentation guidelines.
+ -
+ name: Ruby on Rails Guides Guidelines
+ url: ruby_on_rails_guides_guidelines.html
+ description: This guide documents the Ruby on Rails guides guidelines.
+-
+ name: Release Notes
+ documents:
+ -
+ name: Upgrading Ruby on Rails
+ url: upgrading_ruby_on_rails.html
+ work_in_progress: true
+ description: This guide helps in upgrading applications to latest Ruby on Rails versions.
+ -
+ name: Ruby on Rails 3.2 Release Notes
+ url: 3_2_release_notes.html
+ description: Release notes for Rails 3.2.
+ -
+ name: Ruby on Rails 3.1 Release Notes
+ url: 3_1_release_notes.html
+ description: Release notes for Rails 3.1.
+ -
+ name: Ruby on Rails 3.0 Release Notes
+ url: 3_0_release_notes.html
+ description: Release notes for Rails 3.0.
+ -
+ name: Ruby on Rails 2.3 Release Notes
+ url: 2_3_release_notes.html
+ description: Release notes for Rails 2.3.
+ -
+ name: Ruby on Rails 2.2 Release Notes
+ url: 2_2_release_notes.html
+ description: Release notes for Rails 2.2.
diff --git a/guides/source/engines.textile b/guides/source/engines.textile
new file mode 100644
index 0000000000..c35305a822
--- /dev/null
+++ b/guides/source/engines.textile
@@ -0,0 +1,764 @@
+h2. Getting Started with Engines
+
+In this guide you will learn about engines and how they can be used to provide additional functionality to their host applications through a clean and very easy-to-use interface. You will learn the following things in this guide:
+
+* What makes an engine
+* How to generate an engine
+* Building features for the engine
+* Hooking the engine into an application
+* Overriding engine functionality in the application
+
+endprologue.
+
+h3. What are engines?
+
+Engines can be considered miniature applications that provide functionality to their host applications. A Rails application is actually just a "supercharged" engine, with the +Rails::Application+ class inheriting a lot of its behaviour from +Rails::Engine+.
+
+Therefore, engines and applications can be thought of almost the same thing, just with very minor differences, as you'll see throughout this guide. Engines and applications also share a common structure.
+
+Engines are also closely related to plugins where the two share a common +lib+ directory structure and are both generated using the +rails plugin new+ generator. The difference being that an engine is considered a "full plugin" by Rails as indicated by the +--full+ option that's passed to the generator command, but this guide will refer to them simply as "engines" throughout. An engine *can* be a plugin, and a plugin *can* be an engine.
+
+The engine that will be created in this guide will be called "blorgh". The engine will provide blogging functionality to its host applications, allowing for new posts and comments to be created. At the beginning of this guide, you will be working solely within the engine itself, but in later sections you'll see how to hook it into an application.
+
+Engines can also be isolated from their host applications. This means that an application is able to have a path provided by a routing helper such as +posts_path+ and use an engine also that provides a path also called +posts_path+, and the two would not clash. Along with this, controllers, models and table names are also namespaced. You'll see how to do this later in this guide.
+
+It's important to keep in mind at all times that the application should *always* take precedence over its engines. An application is the object that has final say in what goes on in the universe (with the universe being the application's environment) where the engine should only be enhancing it, rather than changing it drastically.
+
+To see demonstrations of other engines, check out "Devise":https://github.com/plataformatec/devise, an engine that provides authentication for its parent applications, or "Forem":https://github.com/radar/forem, an engine that provides forum functionality. There's also "Spree":https://github.com/spree/spree which provides an e-commerce platform, and "RefineryCMS":https://github.com/resolve/refinerycms, a CMS engine.
+
+Finally, engines would not have be possible without the work of James Adam, Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever meet them, don't forget to say thanks!
+
+h3. Generating an engine
+
+To generate an engine with Rails 3.1, you will need to run the plugin generator and pass it the +--full+ and +--mountable+ options. To generate the beginnings of the "blorgh" engine you will need to run this command in a terminal:
+
+<shell>
+$ rails plugin new blorgh --full --mountable
+</shell>
+
+The +--full+ option tells the plugin generator that you want to create an engine (which is a mountable plugin, hence the option name), creating the basic directory structure of an engine by providing things such as the foundations of an +app+ folder, as well a +config/routes.rb+ file. This generator also provides a file at +lib/blorgh/engine.rb+ which is identical in function to an application's +config/application.rb+ file.
+
+The +--mountable+ option tells the generator to mount the engine inside the dummy testing application located at +test/dummy+ inside the engine. It does this by placing this line in to the dummy application's +config/routes.rb+ file, located at +test/dummy/config/routes.rb+ inside the engine:
+
+<ruby>
+mount Blorgh::Engine, :at => "blorgh"
+</ruby>
+
+h4. Inside an engine
+
+h5. Critical files
+
+At the root of this brand new engine's directory, lives a +blorgh.gemspec+ file. When you include the engine into the application later on, you will do so with this line in a Rails application's +Gemfile+:
+
+<ruby>
+gem 'blorgh', :path => "vendor/engines/blorgh"
+</ruby>
+
+By specifying it as a gem within the +Gemfile+, Bundler will load it as such, parsing this +blorgh.gemspec+ file and requiring a file within the +lib+ directory called +lib/blorgh.rb+. This file requires the +blorgh/engine.rb+ file (located at +lib/blorgh/engine.rb+) and defines a base module called +Blorgh+.
+
+<ruby>
+require "blorgh/engine"
+
+module Blorgh
+end
+</ruby>
+
+TIP: Some engines choose to use this file to put global configuration options for their engine. It's a relatively good idea, and so if you're wanting offer configuration options, the file where your engine's +module+ is defined is perfect for that. Place the methods inside the module and you'll be good to go.
+
+Within +lib/blorgh/engine.rb+ is the base class for the engine:
+
+<ruby>
+module Blorgh
+ class Engine < Rails::Engine
+ isolate_namespace Blorgh
+ end
+end
+</ruby>
+
+By inheriting from the +Rails::Engine+ class, this gem notifies Rails that there's an engine at the specified path, and will correctly mount the engine inside the application, performing tasks such as adding the +app+ directory of the engine to the load path for models, mailers, controllers and views.
+
+The +isolate_namespace+ method here deserves special notice. This call is responsible for isolating the controllers, models, routes and other things into their own namespace, away from similar components inside the application. Without this, there is a possibility that the engine's components could "leak" into the application, causing unwanted disruption, or that important engine components could be overridden by similarly named things within the application. One of the examples of such conflicts are helpers. Without calling +isolate_namespace+, engine's helpers would be included in application's controllers.
+
+NOTE: It is *highly* recommended that the +isolate_namespace+ line be left within the +Engine+ class definition. Without it, classes generated in an engine *may* conflict with an application.
+
+What this isolation of the namespace means is that a model generated by a call to +rails g model+ such as +rails g model post+ wouldn't be called +Post+, but instead be namespaced and called +Blorgh::Post+. In addition to this, the table for the model is namespaced, becoming +blorgh_posts+, rather than simply +posts+. Similar to the model namespacing, a controller called +PostsController+ would be +Blorgh::Postscontroller+ and the views for that controller would not be at +app/views/posts+, but rather +app/views/blorgh/posts+. Mailers would be namespaced as well.
+
+Finally, routes will also be isolated within the engine. This is one of the most important parts about namespacing, and is discussed later in the "Routes":#routes section of this guide.
+
+h5. +app+ directory
+
+Inside the +app+ directory there is the standard +assets+, +controllers+, +helpers+, +mailers+, +models+ and +views+ directories that you should be familiar with from an application. The +helpers+, +mailers+ and +models+ directories are empty and so aren't described in this section. We'll look more into models in a future section, when we're writing the engine.
+
+Within the +app/assets+ directory, there is the +images+, +javascripts+ and +stylesheets+ directories which, again, you should be familiar with due to their similarities of an application. One difference here however is that each directory contains a sub-directory with the engine name. Because this engine is going to be namespaced, its assets should be too.
+
+Within the +app/controllers+ directory there is a +blorgh+ directory and inside that a file called +application_controller.rb+. This file will provide any common functionality for the controllers of the engine. The +blorgh+ directory is where the other controllers for the engine will go. By placing them within this namespaced directory, you prevent them from possibly clashing with identically-named controllers within other engines or even within the application.
+
+NOTE: The +ApplicationController+ class is called as such inside an engine -- rather than +EngineController+ -- mainly due to that if you consider that an engine is really just a mini-application, it makes sense. You should also be able to convert an application to an engine with relatively little pain, and this is just one of the ways to make that process easier, albeit however so slightly.
+
+Lastly, the +app/views+ directory contains a +layouts+ folder which contains file at +blorgh/application.html.erb+ which allows you to specify a layout for the engine. If this engine is to be used as a stand-alone engine, then you would add any customization to its layout in this file, rather than the applications +app/views/layouts/application.html.erb+ file.
+
+If you don't want to force a layout on to users of the engine, then you can delete this file and reference a different layout in the controllers of your engine.
+
+h5. +script+ directory
+
+This directory contains one file, +script/rails+, which enables you to use the +rails+ sub-commands and generators just like you would within an application. This means that you will very easily be able to generate new controllers and models for this engine by running commands like this:
+
+<shell>
+rails g model
+</shell>
+
+Keeping in mind, of course, that anything generated with these commands inside an engine that has +isolate_namespace+ inside the Engine class will be namespaced.
+
+h5. +test+ directory
+
+The +test+ directory is where tests for the engine will go. To test the engine, there is a cut-down version of a Rails application embedded within it at +test/dummy+. This application will mount the engine in the +test/dummy/config/routes.rb+ file:
+
+<ruby>
+Rails.application.routes.draw do
+ mount Blorgh::Engine => "/blorgh"
+end
+</ruby>
+
+This line mounts the engine at the path of +/blorgh+, which will make it accessible through the application only at that path.
+
+Also in the test directory is the +test/integration+ directory, where integration tests for the engine should be placed. Other directories can be created in the +test+ directory also. For example, you may wish to create a +test/unit+ directory for your unit tests.
+
+h3. Providing engine functionality
+
+The engine that this guide covers will provide posting and commenting functionality and follows a similar thread to the "Getting Started Guide":getting_started.html, with some new twists.
+
+h4. Generating a post resource
+
+The first thing to generate for a blog engine is the +Post+ model and related controller. To quickly generate this, you can use the Rails scaffold generator.
+
+<shell>
+$ rails generate scaffold post title:string text:text
+</shell>
+
+This command will output this information:
+
+<shell>
+invoke active_record
+create db/migrate/[timestamp]_create_blorgh_posts.rb
+create app/models/blorgh/post.rb
+invoke test_unit
+create test/unit/blorgh/post_test.rb
+create test/fixtures/blorgh/posts.yml
+ route resources :posts
+invoke scaffold_controller
+create app/controllers/blorgh/posts_controller.rb
+invoke erb
+create app/views/blorgh/posts
+create app/views/blorgh/posts/index.html.erb
+create app/views/blorgh/posts/edit.html.erb
+create app/views/blorgh/posts/show.html.erb
+create app/views/blorgh/posts/new.html.erb
+create app/views/blorgh/posts/_form.html.erb
+invoke test_unit
+create test/functional/blorgh/posts_controller_test.rb
+invoke helper
+create app/helpers/blorgh/posts_helper.rb
+invoke test_unit
+create test/unit/helpers/blorgh/posts_helper_test.rb
+invoke assets
+invoke js
+create app/assets/javascripts/blorgh/posts.js
+invoke css
+create app/assets/stylesheets/blorgh/posts.css
+invoke css
+create app/assets/stylesheets/scaffold.css
+</shell>
+
+The first thing that the scaffold generator does is invoke the +active_record+ generator, which generates a migration and a model for the resource. Note here, however, that the migration is called +create_blorgh_posts+ rather than the usual +create_posts+. This is due to the +isolate_namespace+ method called in the +Blorgh::Engine+ class's definition. The model here is also namespaced, being placed at +app/models/blorgh/post.rb+ rather than +app/models/post.rb+ due to the +isolate_namespace+ call within the +Engine+ class.
+
+Next, the +test_unit+ generator is invoked for this model, generating a unit test at +test/unit/blorgh/post_test.rb+ (rather than +test/unit/post_test.rb+) and a fixture at +test/fixtures/blorgh/posts.yml+ (rather than +test/fixtures/posts.yml+).
+
+After that, a line for the resource is inserted into the +config/routes.rb+ file for the engine. This line is simply +resources :posts+, turning the +config/routes.rb+ file for the engine into this:
+
+<ruby>
+Blorgh::Engine.routes.draw do
+ resources :posts
+end
+</ruby>
+
+Note here that the routes are drawn upon the +Blorgh::Engine+ object rather than the +YourApp::Application+ class. This is so that the engine routes are confined to the engine itself and can be mounted at a specific point as shown in the "test directory":#test-directory section. This is also what causes the engine's routes to be isolated from those routes that are within the application. This is discussed further in the "Routes":#routes section of this guide.
+
+Next, the +scaffold_controller+ generator is invoked, generating a controlled called +Blorgh::PostsController+ (at +app/controllers/blorgh/posts_controller.rb+) and its related views at +app/views/blorgh/posts+. This generator also generates a functional test for the controller (+test/functional/blorgh/posts_controller_test.rb+) and a helper (+app/helpers/blorgh/posts_controller.rb+).
+
+Everything this generator has created is neatly namespaced. The controller's class is defined within the +Blorgh+ module:
+
+<ruby>
+module Blorgh
+ class PostsController < ApplicationController
+ ...
+ end
+end
+</ruby>
+
+NOTE: The +ApplicationController+ class being inherited from here is the +Blorgh::ApplicationController+, not an application's +ApplicationController+.
+
+The helper inside +app/helpers/blorgh/posts_helper.rb+ is also namespaced:
+
+<ruby>
+module Blorgh
+ class PostsHelper
+ ...
+ end
+end
+</ruby>
+
+This helps prevent conflicts with any other engine or application that may have a post resource also.
+
+Finally, two files that are the assets for this resource are generated, +app/assets/javascripts/blorgh/posts.js+ and +app/assets/javascripts/blorgh/posts.css+. You'll see how to use these a little later.
+
+By default, the scaffold styling is not applied to the engine as the engine's layout file, +app/views/blorgh/application.html.erb+ doesn't load it. To make this apply, insert this line into the +<head>+ tag of this layout:
+
+<erb>
+<%= stylesheet_link_tag "scaffold" %>
+</erb>
+
+You can see what the engine has so far by running +rake db:migrate+ at the root of our engine to run the migration generated by the scaffold generator, and then running +rails server+ in +test/dummy+. When you open +http://localhost:3000/blorgh/posts+ you will see the default scaffold that has been generated. Click around! You've just generated your first engine's first functions.
+
+If you'd rather play around in the console, +rails console+ will also work just like a Rails application. Remember: the +Post+ model is namespaced, so to reference it you must call it as +Blorgh::Post+.
+
+<ruby>
+>> Blorgh::Post.find(1)
+=> #<Blorgh::Post id: 1 ...>
+</ruby>
+
+One final thing is that the +posts+ resource for this engine should be the root of the engine. Whenever someone goes to the root path where the engine is mounted, they should be shown a list of posts. This can be made to happen if this line is inserted into the +config/routes.rb+ file inside the engine:
+
+<ruby>
+root :to => "posts#index"
+</ruby>
+
+Now people will only need to go to the root of the engine to see all the posts, rather than visiting +/posts+. This means that instead of +http://localhost:3000/blorgh/posts+, you only need to go to +http://localhost:3000/blorgh+ now.
+
+h4. Generating a comments resource
+
+Now that the engine has the ability to create new blog posts, it only makes sense to add commenting functionality as well. To do get this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones.
+
+Run the model generator and tell it to generate a +Comment+ model, with the related table having two columns: a +post_id+ integer and +text+ text column.
+
+<shell>
+$ rails generate model Comment post_id:integer text:text
+</shell>
+
+This will output the following:
+
+<shell>
+invoke active_record
+create db/migrate/[timestamp]_create_blorgh_comments.rb
+create app/models/blorgh/comment.rb
+invoke test_unit
+create test/unit/blorgh/comment_test.rb
+create test/fixtures/blorgh/comments.yml
+</shell>
+
+This generator call will generate just the necessary model files it needs, namespacing the files under a +blorgh+ directory and creating a model class called +Blorgh::Comment+.
+
+To show the comments on a post, edit +app/views/blorgh/posts/show.html.erb+ and add this line before the "Edit" link:
+
+<erb>
+<h3>Comments</h3>
+<%= render @post.comments %>
+</erb>
+
+This line will require there to be a +has_many+ association for comments defined on the +Blorgh::Post+ model, which there isn't right now. To define one, open +app/models/blorgh/post.rb+ and add this line into the model:
+
+<ruby>
+has_many :comments
+</ruby>
+
+Turning the model into this:
+
+<ruby>
+module Blorgh
+ class Post < ActiveRecord::Base
+ has_many :comments
+ end
+end
+</ruby>
+
+NOTE: Because the +has_many+ is defined inside a class that is inside the +Blorgh+ module, Rails will know that you want to use the +Blorgh::Comment+ model for these objects, so there's no need to specify that using the +:class_name+ option here.
+
+Next, there needs to be a form so that comments can be created on a post. To add this, put this line underneath the call to +render @post.comments+ in +app/views/blorgh/posts/show.html.erb+:
+
+<erb>
+<%= render "blorgh/comments/form" %>
+</erb>
+
+Next, the partial that this line will render needs to exist. Create a new directory at +app/views/blorgh/comments+ and in it a new file called +_form.html.erb+ which has this content to create the required partial:
+
+<erb>
+<h3>New comment</h3>
+<%= form_for [@post, @post.comments.build] do |f| %>
+ <p>
+ <%= f.label :text %><br />
+ <%= f.text_area :text %>
+ </p>
+ <%= f.submit %>
+<% end %>
+</erb>
+
+When this form is submitted, it is going to attempt to perform a +POST+ request to a route of +/posts/:post_id/comments+ within the engine. This route doesn't exist at the moment, but can be created by changing the +resources :posts+ line inside +config/routes.rb+ into these lines:
+
+<ruby>
+resources :posts do
+ resources :comments
+end
+</ruby>
+
+This creates a nested route for the comments, which is what the form requires.
+
+The route now exists, but the controller that this route goes to does not. To create it, run this command:
+
+<shell>
+$ rails g controller comments
+</shell>
+
+This will generate the following things:
+
+<shell>
+create app/controllers/blorgh/comments_controller.rb
+invoke erb
+ exist app/views/blorgh/comments
+invoke test_unit
+create test/functional/blorgh/comments_controller_test.rb
+invoke helper
+create app/helpers/blorgh/comments_helper.rb
+invoke test_unit
+create test/unit/helpers/blorgh/comments_helper_test.rb
+invoke assets
+invoke js
+create app/assets/javascripts/blorgh/comments.js
+invoke css
+create app/assets/stylesheets/blorgh/comments.css
+</shell>
+
+The form will be making a +POST+ request to +/posts/:post_id/comments+, which will correspond with the +create+ action in +Blorgh::CommentsController+. This action needs to be created and can be done by putting the following lines inside the class definition in +app/controllers/blorgh/comments_controller.rb+:
+
+<ruby>
+def create
+ @post = Post.find(params[:post_id])
+ @comment = @post.comments.build(params[:comment])
+ flash[:notice] = "Comment has been created!"
+ redirect_to post_path
+end
+</ruby>
+
+This is the final part required to get the new comment form working. Displaying the comments however, is not quite right yet. If you were to create a comment right now you would see this error:
+
+<plain>
+Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder], :formats=>[:html], :locale=>[:en, :en]}. Searched in:
+ * "/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views"
+ * "/Users/ryan/Sites/side_projects/blorgh/app/views"
+</plain>
+
+The engine is unable to find the partial required for rendering the comments. Rails has looked firstly in the application's (+test/dummy+) +app/views+ directory and then in the engine's +app/views+ directory. When it can't find it, it will throw this error. The engine knows to look for +blorgh/comments/comment+ because the model object it is receiving is from the +Blorgh::Comment+ class.
+
+This partial will be responsible for rendering just the comment text, for now. Create a new file at +app/views/blorgh/comments/_comment.html.erb+ and put this line inside it:
+
+<erb>
+<%= comment_counter + 1 %>. <%= comment.text %>
+</erb>
+
+The +comment_counter+ local variable is given to us by the +<%= render @post.comments %>+ call, as it will define this automatically and increment the counter as it iterates through each comment. It's used in this example to display a small number next to each comment when it's created.
+
+That completes the comment function of the blogging engine. Now it's time to use it within an application.
+
+h3. Hooking into an application
+
+Using an engine within an application is very easy. This section covers how to mount the engine into an application and the initial setup required for it, as well as linking the engine to a +User+ class provided by the application to provide ownership for posts and comments within the engine.
+
+h4. Mounting the engine
+
+First, the engine needs to be specified inside the application's +Gemfile+. If there isn't an application handy to test this out in, generate one using the +rails new+ command outside of the engine directory like this:
+
+<shell>
+$ rails new unicorn
+</shell>
+
+Usually, specifying the engine inside the Gemfile would be done by specifying it as a normal, everyday gem.
+
+<ruby>
+gem 'devise'
+</ruby>
+
+Because the +blorgh+ engine is still under development, it will need to have a +:path+ option for its +Gemfile+ specification:
+
+<ruby>
+gem 'blorgh', :path => "/path/to/blorgh"
+</ruby>
+
+If the whole +blorgh+ engine directory is copied to +vendor/engines/blorgh+ then it could be specified in the +Gemfile+ like this:
+
+<ruby>
+gem 'blorgh', :path => "vendor/engines/blorgh"
+</ruby>
+
+As described earlier, by placing the gem in the +Gemfile+ it will be loaded when Rails is loaded, as it will first require +lib/blorgh.rb+ in the engine and then +lib/blorgh/engine.rb+, which is the file that defines the major pieces of functionality for the engine.
+
+To make the engine's functionality accessible from within an application, it needs to be mounted in that application's +config/routes.rb+ file:
+
+<ruby>
+mount Blorgh::Engine, :at => "/blog"
+</ruby>
+
+This line will mount the engine at +/blog+ in the application. Making it accessible at +http://localhost:3000/blog+ when the application runs with +rails server+.
+
+NOTE: Other engines, such as Devise, handle this a little differently by making you specify custom helpers such as +devise_for+ in the routes. These helpers do exactly the same thing, mounting pieces of the engines's functionality at a pre-defined path which may be customizable.
+
+h4. Engine setup
+
+The engine contains migrations for the +blorgh_posts+ and +blorgh_comments+ table which need to be created in the application's database so that the engine's models can query them correctly. To copy these migrations into the application use this command:
+
+<shell>
+$ rake blorgh:install:migrations
+</shell>
+
+If you have multiple engines that need migrations copied over, use +railties:install:migrations+ instead:
+
+<shell>
+$ rake railties:install:migrations
+</shell>
+
+This command, when run for the first time will copy over all the migrations from the engine. When run the next time, it will only copy over migrations that haven't been copied over already. The first run for this command will output something such as this:
+
+<shell>
+Copied migration [timestamp_1]_create_blorgh_posts.rb from blorgh
+Copied migration [timestamp_2]_create_blorgh_comments.rb from blorgh
+</shell>
+
+The first timestamp (+\[timestamp_1\]+) will be the current time and the second timestamp (+\[timestamp_2\]+) will be the current time plus a second. The reason for this is so that the migrations for the engine are run after any existing migrations in the application.
+
+To run these migrations within the context of the application, simply run +rake db:migrate+. When accessing the engine through +http://localhost:3000/blog+, the posts will be empty. This is because the table created inside the application is different from the one created within the engine. Go ahead, play around with the newly mounted engine. You'll find that it's the same as when it was only an engine.
+
+If you would like to run migrations only from one engine, you can do it by specifying +SCOPE+:
+
+<shell>
+rake db:migrate SCOPE=blorgh
+</shell>
+
+This may be useful if you want to revert engine's migrations before removing it. In order to revert all migrations from blorgh engine you can run such code:
+
+<shell>
+rake db:migrate SCOPE=blorgh VERSION=0
+</shell>
+
+h4. Using a class provided by the application
+
+h5. Using a model provided by the application
+
+When an engine is created, it may want to use specific classes from an application to provide links between the pieces of the engine and the pieces of the application. In the case of the +blorgh+ engine, making posts and comments have authors would make a lot of sense.
+
+Usually, an application would have a +User+ class that would provide the objects that would represent the posts' and comments' authors, but there could be a case where the application calls this class something different, such as +Person+. It's because of this reason that the engine should not hardcode the associations to be exactly for a +User+ class, but should allow for some flexibility around what the class is called.
+
+To keep it simple in this case, the application will have a class called +User+ which will represent the users of the application. It can be generated using this command inside the application:
+
+<shell>
+rails g model user name:string
+</shell>
+
+The +rake db:migrate+ command needs to be run here to ensure that our application has the +users+ table for future use.
+
+Also, to keep it simple, the posts form will have a new text field called +author_name+ where users can elect to put their name. The engine will then take this name and create a new +User+ object from it or find one that already has that name, and then associate the post with it.
+
+First, the +author_name+ text field needs to be added to the +app/views/blorgh/posts/_form.html.erb+ partial inside the engine. This can be added above the +title+ field with this code:
+
+<erb>
+<div class="field">
+ <%= f.label :author_name %><br />
+ <%= f.text_field :author_name %>
+</div>
+</erb>
+
+The +Blorgh::Post+ model should then have some code to convert the +author_name+ field into an actual +User+ object and associate it as that post's +author+ before the post is saved. It will also need to have an +attr_accessor+ setup for this field so that the setter and getter methods are defined for it.
+
+To do all this, you'll need to add the +attr_accessor+ for +author_name+, the association for the author and the +before_save+ call into +app/models/blorgh/post.rb+. The +author+ association will be hard-coded to the +User+ class for the time being.
+
+<ruby>
+attr_accessor :author_name
+belongs_to :author, :class_name => "User"
+
+before_save :set_author
+
+private
+ def set_author
+ self.author = User.find_or_create_by_name(author_name)
+ end
+</ruby>
+
+By defining that the +author+ association's object is represented by the +User+ class a link is established between the engine and the application. There needs to be a way of associating the records in the +blorgh_posts+ table with the records in the +users+ table. Because the association is called +author+, there should be an +author_id+ column added to the +blorgh_posts+ table.
+
+To generate this new column, run this command within the engine:
+
+<shell>
+$ rails g migration add_author_id_to_blorgh_posts author_id:integer
+</shell>
+
+NOTE: Due to the migration's name and the column specification after it, Rails will automatically know that you want to add a column to a specific table and write that into the migration for you. You don't need to tell it any more than this.
+
+This migration will need to be run on the application. To do that, it must first be copied using this command:
+
+<shell>
+$ rake blorgh:install:migrations
+</shell>
+
+Notice here that only _one_ migration was copied over here. This is because the first two migrations were copied over the first time this command was run.
+
+<plain>
+NOTE Migration [timestamp]_create_blorgh_posts.rb from blorgh has been skipped. Migration with the same name already exists.
+NOTE Migration [timestamp]_create_blorgh_comments.rb from blorgh has been skipped. Migration with the same name already exists.
+Copied migration [timestamp]_add_author_id_to_blorgh_posts.rb from blorgh
+</plain>
+
+Run this migration using this command:
+
+<shell>
+$ rake db:migrate
+</shell>
+
+Now with all the pieces in place, an action will take place that will associate an author -- represented by a record in the +users+ table -- with a post, represented by the +blorgh_posts+ table from the engine.
+
+Finally, the author's name should be displayed on the post's page. Add this code above the "Title" output inside +app/views/blorgh/posts/show.html.erb+:
+
+<erb>
+<p>
+ <b>Author:</b>
+ <%= @post.author %>
+</p>
+</erb>
+
+By outputting +@post.author+ using the +<%=+ tag the +to_s+ method will be called on the object. By default, this will look quite ugly:
+
+<plain>
+#<User:0x00000100ccb3b0>
+</plain>
+
+This is undesirable and it would be much better to have the user's name there. To do this, add a +to_s+ method to the +User+ class within the application:
+
+<ruby>
+def to_s
+ name
+end
+</ruby>
+
+Now instead of the ugly Ruby object output the author's name will be displayed.
+
+h5. Using a controller provided by the application
+
+Because Rails controllers generally share code for things like authentication and accessing session variables, by default they inherit from <tt>ApplicationController</tt>. Rails engines, however are scoped to run independently from the main application, so each engine gets a scoped +ApplicationController+. This namespace prevents code collisions, but often engine controllers should access methods in the main application's +ApplicationController+. An easy way to provide this access is to change the engine's scoped +ApplicationController+ to inherit from the main application's +ApplicationController+. For our Blorgh engine this would be done by changing +app/controllers/blorgh/application_controller.rb+ to look like:
+
+<ruby>
+class Blorgh::ApplicationController < ApplicationController
+end
+</ruby>
+
+By default, the engine's controllers inherit from <tt>Blorgh::ApplicationController</tt>. So, after making this change they will have access to the main applications +ApplicationController+ as though they were part of the main application.
+
+This change does require that the engine is run from a Rails application that has an +ApplicationController+.
+
+h4. Configuring an engine
+
+This section covers firstly how you can make the +user_class+ setting of the Blorgh engine configurable, followed by general configuration tips for the engine.
+
+h5. Setting configuration settings in the application
+
+The next step is to make the class that represents a +User+ in the application customizable for the engine. This is because, as explained before, that class may not always be +User+. To make this customizable, the engine will have a configuration setting called +user_class+ that will be used to specify what the class representing users is inside the application.
+
+To define this configuration setting, you should use a +mattr_accessor+ inside the +Blorgh+ module for the engine, located at +lib/blorgh.rb+ inside the engine. Inside this module, put this line:
+
+<ruby>
+mattr_accessor :user_class
+</ruby>
+
+This method works like its brothers +attr_accessor+ and +cattr_accessor+, but provides a setter and getter method on the module with the specified name. To use it, it must be referenced using +Blorgh.user_class+.
+
+The next step is switching the +Blorgh::Post+ model over to this new setting. For the +belongs_to+ association inside this model (+app/models/blorgh/post.rb+), it will now become this:
+
+<ruby>
+belongs_to :author, :class_name => Blorgh.user_class
+</ruby>
+
+The +set_author+ method also located in this class should also use this class:
+
+<ruby>
+self.author = Blorgh.user_class.constantize.find_or_create_by_name(author_name)
+</ruby>
+
+To save having to call +constantize+ on the +user_class+ result all the time, you could instead just override the +user_class+ getter method inside the +Blorgh+ module in the +lib/blorgh.rb+ file to always call +constantize+ on the saved value before returning the result:
+
+<ruby>
+def self.user_class
+ @@user_class.constantize
+end
+</ruby>
+
+This would then turn the above code for +self.author=+ into this:
+
+<ruby>
+self.author = Blorgh.user_class.find_or_create_by_name(author_name)
+</ruby>
+
+Resulting in something a little shorter, and more implicit in its behaviour. The +user_class+ method should always return a +Class+ object.
+
+To set this configuration setting within the application, an initializer should be used. By using an initializer, the configuration will be set up before the application starts and calls the engine's models which may depend on this configuration setting existing.
+
+Create a new initializer at +config/initializers/blorgh.rb+ inside the application where the +blorgh+ engine is installed and put this content in it:
+
+<ruby>
+Blorgh.user_class = "User"
+</ruby>
+
+WARNING: It's very important here to use the +String+ version of the class, rather than the class itself. If you were to use the class, Rails would attempt to load that class and then reference the related table, which could lead to problems if the table wasn't already existing. Therefore, a +String+ should be used and then converted to a class using +constantize+ in the engine later on.
+
+Go ahead and try to create a new post. You will see that it works exactly in the same way as before, except this time the engine is using the configuration setting in +config/initializers/blorgh.rb+ to learn what the class is.
+
+There are now no strict dependencies on what the class is, only what the class's API must be. The engine simply requires this class to define a +find_or_create_by_name+ method which returns an object of that class to be associated with a post when it's created. This object, of course, should have some sort of identifier by which it can be referenced.
+
+h5. General engine configuration
+
+Within an engine, there may come a time where you wish to use things such as initializers, internationalization or other configuration options. The great news is that these things are entirely possible because a Rails engine shares much the same functionality as a Rails application. In fact, a Rails application's functionality is actually a superset of what is provided by engines!
+
+If you wish to use an initializer -- code that should run before the engine is loaded -- the place for it is the +config/initializers+ folder. This directory's functionality is explained in the "Initializers section":http://guides.rubyonrails.org/configuring.html#initializers of the Configuring guide, and works precisely the same way as the +config/initializers+ directory inside an application. Same goes for if you want to use a standard initializer.
+
+For locales, simply place the locale files in the +config/locales+ directory, just like you would in an application.
+
+h3. Testing an engine
+
+When an engine is generated there is a smaller dummy application created inside it at +test/dummy+. This application is used as a mounting point for the engine to make testing the engine extremely simple. You may extend this application by generating controllers, models or views from within the directory, and then use those to test your engine.
+
+The +test+ directory should be treated like a typical Rails testing environment, allowing for unit, functional and integration tests.
+
+h4. Functional tests
+
+A matter worth taking into consideration when writing functional tests is that the tests are going to be running on an application -- the +test/dummy+ application -- rather than your engine. This is due to the setup of the testing environment; an engine needs an application as a host for testing its main functionality, especially controllers. This means that if you were to make a typical +GET+ to a controller in a controller's functional test like this:
+
+<ruby>
+get :index
+</ruby>
+
+It may not function correctly. This is because the application doesn't know how to route these requests to the engine unless you explicitly tell it *how*. To do this, you must pass the +:use_route+ option (as a parameter) on these requests also:
+
+<ruby>
+get :index, :use_route => :blorgh
+</ruby>
+
+This tells the application that you still want to perform a +GET+ request to the +index+ action of this controller, just that you want to use the engine's route to get there, rather than the application.
+
+h3. Improving engine functionality
+
+This section looks at overriding or adding functionality to the views, controllers and models provided by an engine.
+
+h4. Overriding views
+
+When Rails looks for a view to render, it will first look in the +app/views+ directory of the application. If it cannot find the view there, then it will check in the +app/views+ directories of all engines which have this directory.
+
+In the +blorgh+ engine, there is a currently a file at +app/views/blorgh/posts/index.html.erb+. When the engine is asked to render the view for +Blorgh::PostsController+'s +index+ action, it will first see if it can find it at +app/views/blorgh/posts/index.html.erb+ within the application and then if it cannot it will look inside the engine.
+
+By overriding this view in the application, by simply creating a new file at +app/views/blorgh/posts/index.html.erb+, you can completely change what this view would normally output.
+
+Try this now by creating a new file at +app/views/blorgh/posts/index.html.erb+ and put this content in it:
+
+<erb>
+<h1>Posts</h1>
+<%= link_to "New Post", new_post_path %>
+<% @posts.each do |post| %>
+ <h2><%= post.title %></h2>
+ <small>By <%= post.author %></small>
+ <%= simple_format(post.text) %>
+ <hr>
+<% end %>
+</erb>
+
+h4. Routes
+
+Routes inside an engine are, by default, isolated from the application. This is done by the +isolate_namespace+ call inside the +Engine+ class. This essentially means that the application and its engines can have identically named routes, and that they will not clash.
+
+Routes inside an engine are drawn on the +Engine+ class within +config/routes.rb+, like this:
+
+<ruby>
+Blorgh::Engine.routes.draw do
+ resources :posts
+end
+</ruby>
+
+By having isolated routes such as this, if you wish to link to an area of an engine from within an application, you will need to use the engine's routing proxy method. Calls to normal routing methods such as +posts_path+ may end up going to undesired locations if both the application and the engine both have such a helper defined.
+
+For instance, the following example would go to the application's +posts_path+ if that template was rendered from the application, or the engine's +posts_path+ if it was rendered from the engine:
+
+<erb>
+<%= link_to "Blog posts", posts_path %>
+</erb>
+
+To make this route always use the engine's +posts_path+ routing helper method, we must call the method on the routing proxy method that shares the same name as the engine.
+
+<erb>
+<%= link_to "Blog posts", blorgh.posts_path %>
+</erb>
+
+If you wish to reference the application inside the engine in a similar way, use the +main_app+ helper:
+
+<erb>
+<%= link_to "Home", main_app.root_path %>
+</erb>
+
+If you were to use this inside an engine, it would *always* go to the application's root. If you were to leave off the +main_app+ "routing proxy" method call, it could potentially go to the engine's or application's root, depending on where it was called from.
+
+If a template is rendered from within an engine and it's attempting to use one of the application's routing helper methods, it may result in an undefined method call. If you encounter such an issue, ensure that you're not attempting to call the application's routing methods without the +main_app+ prefix from within the engine.
+
+h4. Assets
+
+Assets within an engine work in an identical way to a full application. Because the engine class inherits from +Rails::Engine+, the application will know to look up in the engine's +app/assets+ and +lib/assets+ directories for potential assets.
+
+Much like all the other components of an engine, the assets should also be namespaced. This means if you have an asset called +style.css+, it should be placed at +app/assets/stylesheets/[engine name]/style.css+, rather than +app/assets/stylesheets/style.css+. If this asset wasn't namespaced, then there is a possibility that the host application could have an asset named identically, in which case the application's asset would take precedence and the engine's one would be all but ignored.
+
+Imagine that you did have an asset located at +app/assets/stylesheets/blorgh/style.css+ To include this asset inside an application, just use +stylesheet_link_tag+ and reference the asset as if it were inside the engine:
+
+<erb>
+<%= stylesheet_link_tag "blorgh/style.css" %>
+</erb>
+
+You can also specify these assets as dependencies of other assets using the Asset Pipeline require statements in processed files:
+
+<plain>
+/*
+ *= require blorgh/style
+*/
+</plain>
+
+h4. Separate Assets & Precompiling
+
+There are some situations where your engine's assets not required by the host application. For example, say that you've created
+an admin functionality that only exists for your engine. In this case, the host application doesn't need to require +admin.css+
+or +admin.js+. Only the gem's admin layout needs these assets. It doesn't make sense for the host app to include +"blorg/admin.css"+ in it's stylesheets. In this situation, you should explicitly define these assets for precompilation.
+This tells sprockets to add you engine assets when +rake assets:precompile+ is ran.
+
+You can define assets for precompilation in +engine.rb+
+
+<ruby>
+initializer "blorgh.assets.precompile" do |app|
+ app.config.assets.precompile += %w(admin.css admin.js)
+end
+</ruby>
+
+For more information, read the "Asset Pipeline guide":http://guides.rubyonrails.org/asset_pipeline.html
+
+h4. Other gem dependencies
+
+Gem dependencies inside an engine should be specified inside the +.gemspec+ file that's at the root of the engine. The reason for this is because the engine may be installed as a gem. If dependencies were to be specified inside the +Gemfile+, these would not be recognised by a traditional gem install and so they would not be installed, causing the engine to malfunction.
+
+To specify a dependency that should be installed with the engine during a traditional +gem install+, specify it inside the +Gem::Specification+ block inside the +.gemspec+ file in the engine:
+
+<ruby>
+s.add_dependency "moo"
+</ruby>
+
+To specify a dependency that should only be installed as a development dependency of the application, specify it like this:
+
+<ruby>
+s.add_development_dependency "moo"
+</ruby>
+
+Both kinds of dependencies will be installed when +bundle install+ is run inside the application. The development dependencies for the gem will only be used when the tests for the engine are running.
diff --git a/railties/guides/source/form_helpers.textile b/guides/source/form_helpers.textile
index 821bb305f6..033b33ec3b 100644
--- a/railties/guides/source/form_helpers.textile
+++ b/guides/source/form_helpers.textile
@@ -39,7 +39,7 @@ When called without arguments like this, it creates a +&lt;form&gt;+ tag which,
</form>
</html>
-Now, you'll notice that the HTML contains something extra: a +div+ element with two hidden input elements inside. This div is important, because the form cannot be successfully submitted without it. The first input element with name +utf8+ enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name +authenticity_token+ is a security feature of Rails called *cross-site request forgery protection*, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the "Security Guide":./security.html#_cross_site_reference_forgery_csrf.
+Now, you'll notice that the HTML contains something extra: a +div+ element with two hidden input elements inside. This div is important, because the form cannot be successfully submitted without it. The first input element with name +utf8+ enforces browsers to properly respect your form's character encoding and is generated for all forms whether their actions are "GET" or "POST". The second input element with name +authenticity_token+ is a security feature of Rails called *cross-site request forgery protection*, and form helpers generate it for every non-GET form (provided that this security feature is enabled). You can read more about this in the "Security Guide":./security.html#cross-site-request-forgery-csrf.
NOTE: Throughout this guide, the +div+ with the hidden input elements will be excluded from code samples for brevity.
@@ -89,7 +89,7 @@ form_tag(:controller => "people", :action => "search", :method => "get", :class
# => '<form accept-charset="UTF-8" action="/people/search?method=get&class=nifty_form" method="post">'
</ruby>
-Here, +method+ and +class+ are appended to the query string of the generated URL because you even though you mean to write two hashes, you really only specified one. So you need to tell Ruby which is which by delimiting the first hash (or both) with curly brackets. This will generate the HTML you expect:
+Here, +method+ and +class+ are appended to the query string of the generated URL because even though you mean to write two hashes, you really only specified one. So you need to tell Ruby which is which by delimiting the first hash (or both) with curly brackets. This will generate the HTML you expect:
<ruby>
form_tag({:controller => "people", :action => "search"}, :method => "get", :class => "nifty_form")
@@ -150,7 +150,7 @@ NOTE: Always use labels for checkbox and radio buttons. They associate text with
h4. Other Helpers of Interest
-Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, URL fields and email fields:
+Other form controls worth mentioning are textareas, password fields, hidden fields, search fields, telephone fields, date fields, time fields, URL fields and email fields:
<erb>
<%= text_area_tag(:message, "Hi, nice site", :size => "24x6") %>
@@ -158,8 +158,10 @@ Other form controls worth mentioning are textareas, password fields, hidden fiel
<%= hidden_field_tag(:parent_id, "5") %>
<%= search_field(:user, :name) %>
<%= telephone_field(:user, :phone) %>
+<%= date_field(:user, :born_on) %>
<%= url_field(:user, :homepage) %>
<%= email_field(:user, :address) %>
+<%= time_field(:task, :started_at) %>
</erb>
Output:
@@ -168,15 +170,17 @@ Output:
<textarea id="message" name="message" cols="24" rows="6">Hi, nice site</textarea>
<input id="password" name="password" type="password" />
<input id="parent_id" name="parent_id" type="hidden" value="5" />
-<input id="user_name" name="user[name]" size="30" type="search" />
-<input id="user_phone" name="user[phone]" size="30" type="tel" />
-<input id="user_homepage" size="30" name="user[homepage]" type="url" />
-<input id="user_address" size="30" name="user[address]" type="email" />
+<input id="user_name" name="user[name]" type="search" />
+<input id="user_phone" name="user[phone]" type="tel" />
+<input id="user_born_on" name="user[born_on]" type="date" />
+<input id="user_homepage" name="user[homepage]" type="url" />
+<input id="user_address" name="user[address]" type="email" />
+<input id="task_started_at" name="task[started_at]" type="time" />
</html>
Hidden inputs are not shown to the user but instead hold data like any textual input. Values inside them can be changed with JavaScript.
-IMPORTANT: The search, telephone, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
+IMPORTANT: The search, telephone, date, time, URL, and email inputs are HTML5 controls. If you require your app to have a consistent experience in older browsers, you will need an HTML5 polyfill (provided by CSS and/or JavaScript). There is definitely "no shortage of solutions for this":https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills, although a couple of popular tools at the moment are "Modernizr":http://www.modernizr.com/ and "yepnope":http://yepnopejs.com/, which provide a simple way to add functionality based on the presence of detected HTML5 features.
TIP: If you're using password input fields (for any purpose), you might want to configure your application to prevent those parameters from being logged. You can learn about this in the "Security Guide":security.html#logging.
@@ -229,7 +233,7 @@ The corresponding view +app/views/articles/new.html.erb+ using +form_for+ looks
There are a few things to note here:
# +@article+ is the actual object being edited.
-# There is a single hash of options. Routing options are passed in the +:url+ hash, HTML options are passed in the +:html+ hash.
+# There is a single hash of options. Routing options are passed in the +:url+ hash, HTML options are passed in the +:html+ hash. Also you can provide a +:namespace+ option for your form to ensure uniqueness of id attributes on form elements. The namespace attribute will be prefixed with underscore on the generated HTML id.
# The +form_for+ method yields a *form builder* object (the +f+ variable).
# Methods to create form controls are called *on* the form builder object +f+
@@ -237,7 +241,7 @@ The resulting HTML is:
<html>
<form accept-charset="UTF-8" action="/articles/create" method="post" class="nifty_form">
- <input id="article_title" name="article[title]" size="30" type="text" />
+ <input id="article_title" name="article[title]" type="text" />
<textarea id="article_body" name="article[body]" cols="60" rows="12"></textarea>
<input name="commit" type="submit" value="Create" />
</form>
@@ -262,8 +266,8 @@ which produces the following output:
<html>
<form accept-charset="UTF-8" action="/people/create" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" size="30" type="text" />
- <input id="contact_detail_phone_number" name="contact_detail[phone_number]" size="30" type="text" />
+ <input id="person_name" name="person[name]" type="text" />
+ <input id="contact_detail_phone_number" name="contact_detail[phone_number]" type="text" />
</form>
</html>
@@ -290,7 +294,7 @@ form_for(@article)
## Editing an existing article
# long-style:
-form_for(@article, :url => article_path(@article), :html => { :method => "put" })
+form_for(@article, :url => article_path(@article), :html => { :method => "patch" })
# short-style:
form_for(@article)
</ruby>
@@ -318,14 +322,14 @@ form_for [:admin, :management, @article]
For more information on Rails' routing system and the associated conventions, please see the "routing guide":routing.html.
-h4. How do forms with PUT or DELETE methods work?
+h4. How do forms with PATCH, PUT, or DELETE methods work?
-The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PUT" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
+The Rails framework encourages RESTful design of your applications, which means you'll be making a lot of "PATCH" and "DELETE" requests (besides "GET" and "POST"). However, most browsers _don't support_ methods other than "GET" and "POST" when it comes to submitting forms.
Rails works around this issue by emulating other methods over POST with a hidden input named +"_method"+, which is set to reflect the desired method:
<ruby>
-form_tag(search_path, :method => "put")
+form_tag(search_path, :method => "patch")
</ruby>
output:
@@ -333,14 +337,14 @@ output:
<html>
<form accept-charset="UTF-8" action="/search" method="post">
<div style="margin:0;padding:0">
- <input name="_method" type="hidden" value="put" />
+ <input name="_method" type="hidden" value="patch" />
<input name="utf8" type="hidden" value="&#x2713;" />
<input name="authenticity_token" type="hidden" value="f755bb0ed134b76c432144748a6d4b7a7ddf2b71" />
</div>
...
</html>
-When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PUT" in this example).
+When parsing POSTed data, Rails will take into account the special +_method+ parameter and acts as if the HTTP method was the one specified inside it ("PATCH" in this example).
h3. Making Select Boxes with Ease
@@ -403,6 +407,8 @@ Whenever Rails sees that the internal value of an option being generated matches
TIP: The second argument to +options_for_select+ must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to +options_for_select+ -- you must pass 2. Be aware of values extracted from the +params+ hash as they are all strings.
+WARNING: when +:inlude_blank+ or +:prompt:+ are not present, +:include_blank+ is forced true if the select attribute +required+ is true, display +size+ is one and +multiple+ is not true.
+
h4. Select Boxes for Dealing with Models
In most cases form controls will be tied to a specific database model and as you might expect Rails provides helpers tailored for that purpose. Consistent with other form helpers, when dealing with models you drop the +_tag+ suffix from +select_tag+:
@@ -426,7 +432,7 @@ As with other helpers, if you were to use the +select+ helper on a form builder
<%= f.select(:city_id, ...) %>
</erb>
-WARNING: If you are using +select+ (or similar helpers such as +collection_select+, +select_tag+) to set a +belongs_to+ association you must pass the name of the foreign key (in the example above +city_id+), not the name of association itself. If you specify +city+ instead of +city_id+ Active Record will raise an error along the lines of <tt> ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) </tt> when you pass the +params+ hash to +Person.new+ or +update_attributes+. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of +attr_protected+ and +attr_accessible+. For further details on this, see the "Ruby On Rails Security Guide":security.html#_mass_assignment.
+WARNING: If you are using +select+ (or similar helpers such as +collection_select+, +select_tag+) to set a +belongs_to+ association you must pass the name of the foreign key (in the example above +city_id+), not the name of association itself. If you specify +city+ instead of +city_id+ Active Record will raise an error along the lines of <tt> ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) </tt> when you pass the +params+ hash to +Person.new+ or +update_attributes+. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of +attr_protected+ and +attr_accessible+. For further details on this, see the "Ruby On Rails Security Guide":security.html#mass-assignment.
h4. Option Tags from a Collection of Arbitrary Objects
@@ -467,7 +473,7 @@ Rails _used_ to have a +country_select+ helper for choosing countries, but this
h3. Using Date and Time Form Helpers
-The date and time helpers differ from all the other form helpers in two important respects:
+You can choose not to use the form helpers generating HTML5 date and time input fields and use the alternative date and time helpers. These date and time helpers differ from all the other form helpers in two important respects:
# Dates and times are not representable by a single input element. Instead you have several, one for each component (year, month, day etc.) and so there is no single value in your +params+ hash with your date or time.
# Other helpers use the +_tag+ suffix to indicate whether a helper is a barebones helper or one that operates on model objects. With dates and times, +select_date+, +select_time+ and +select_datetime+ are the barebones helpers, +date_select+, +time_select+ and +datetime_select+ are the equivalent model object helpers.
@@ -630,10 +636,10 @@ action for a Person model, +params[:model]+ would usually be a hash of all the a
Fundamentally HTML forms don't know about any sort of structured data, all they generate is name–value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses.
-TIP: You may find you can try out examples in this section faster by using the console to directly invoke Rails' parameter parser. For example,
+TIP: You may find you can try out examples in this section faster by using the console to directly invoke Racks' parameter parser. For example,
<ruby>
-ActionController::UrlEncodedPairParser.parse_query_parameters "name=fred&phone=0123456789"
+Rack::Utils.parse_query "name=fred&phone=0123456789"
# => {"name"=>"fred", "phone"=>"0123456789"}
</ruby>
@@ -712,9 +718,9 @@ Assuming the person had two addresses, with ids 23 and 45 this would create outp
<html>
<form accept-charset="UTF-8" action="/people/1" class="edit_person" id="edit_person_1" method="post">
- <input id="person_name" name="person[name]" size="30" type="text" />
- <input id="person_address_23_city" name="person[address][23][city]" size="30" type="text" />
- <input id="person_address_45_city" name="person[address][45][city]" size="30" type="text" />
+ <input id="person_name" name="person[name]" type="text" />
+ <input id="person_address_23_city" name="person[address][23][city]" type="text" />
+ <input id="person_address_45_city" name="person[address][45][city]" type="text" />
</form>
</html>
@@ -737,7 +743,7 @@ To create more intricate nestings, you can specify the first part of the input n
will create inputs like
<html>
-<input id="person_address_primary_1_city" name="person[address][primary][1][city]" size="30" type="text" value="bologna" />
+<input id="person_address_primary_1_city" name="person[address][primary][1][city]" type="text" value="bologna" />
</html>
As a general rule the final input name is the concatenation of the name given to +fields_for+/+form_for+, the index value and the name of the attribute. You can also pass an +:index+ option directly to helpers such as +text_field+, but it is usually less repetitive to specify this at the form builder level rather than on individual input controls.
@@ -754,7 +760,7 @@ produces exactly the same output as the previous example.
h3. Forms to external resources
-If you need to post some data to an external resource it is still great to build your from using rails form helpers. But sometimes you need to set an +authenticity_token+ for this resource. You can do it by passing an +:authenticity_token => 'your_external_token'+ parameter to the +form_tag+ options:
+If you need to post some data to an external resource it is still great to build your form using rails form helpers. But sometimes you need to set an +authenticity_token+ for this resource. You can do it by passing an +:authenticity_token => 'your_external_token'+ parameter to the +form_tag+ options:
<erb>
<%= form_tag 'http://farfar.away/form', :authenticity_token => 'external_token') do %>
diff --git a/railties/guides/source/generators.textile b/guides/source/generators.textile
index 7a863ccbc7..2e9ab0526d 100644
--- a/railties/guides/source/generators.textile
+++ b/guides/source/generators.textile
@@ -48,7 +48,7 @@ end
NOTE: +create_file+ is a method provided by +Thor::Actions+. Documentation for +create_file+ and other Thor methods can be found in "Thor's documentation":http://rdoc.info/github/wycats/thor/master/Thor/Actions.html
-Our new generator is quite simple: it inherits from +Rails::Generators::Base+ and has one method definition. Each public method in the generator is executed when a generator is invoked. Finally, we invoke the +create_file+ method that will create a file at the given destination with the given content. If you are familiar with the Rails Application Templates API, you'll feel right at home with the new generators API.
+Our new generator is quite simple: it inherits from +Rails::Generators::Base+ and has one method definition. When a generator is invoked, each public method in the generator is executed sequentially in the order that it is defined. Finally, we invoke the +create_file+ method that will create a file at the given destination with the given content. If you are familiar with the Rails Application Templates API, you'll feel right at home with the new generators API.
To invoke our new generator, we just need to do:
@@ -406,22 +406,6 @@ The following are methods available for both generators and templates for Rails.
NOTE: Methods provided by Thor are not covered this guide and can be found in "Thor's documentation":http://rdoc.info/github/wycats/thor/master/Thor/Actions.html
-h4. +plugin+
-
-+plugin+ will install a plugin into the current application.
-
-<ruby>
-plugin("dynamic-form", :git => "git://github.com/rails/dynamic-form.git")
-</ruby>
-
-Available options are:
-
-* +:git+ - Takes the path to the git repository where this plugin can be found.
-* +:branch+ - The name of the branch of the git repository where the plugin is found.
-* +:submodule+ - Set to +true+ for the plugin to be installed as a submodule. Defaults to +false+.
-* +:svn+ - Takes the path to the svn repository where this plugin can be found.
-* +:revision+ - The revision of the plugin in an SVN repository.
-
h4. +gem+
Specifies a gem dependency of the application.
@@ -467,6 +451,27 @@ Adds a specified source to +Gemfile+:
add_source "http://gems.github.com"
</ruby>
+h4. +inject_into_file+
+
+Injects a block of code into a defined position in your file.
+
+<ruby>
+inject_into_file 'name_of_file.rb', :after => "#The code goes below this line. Don't forget the Line break at the end\n" do <<-'RUBY'
+ puts "Hello World"
+RUBY
+end
+</ruby>
+
+h4. +gsub_file+
+
+Replaces text inside a file.
+
+<ruby>
+gsub_file 'name_of_file.rb', 'method.to_be_replaced', 'method.the_replacing_code'
+</ruby>
+
+Regular Expressions can be used to make this method more precise. You can also use append_file and prepend_file in the same way to place code at the beginning and end of a file respectively.
+
h4. +application+
Adds a line to +config/application.rb+ directly after the application class definition.
diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile
new file mode 100644
index 0000000000..e25dac22da
--- /dev/null
+++ b/guides/source/getting_started.textile
@@ -0,0 +1,1745 @@
+h2. Getting Started with Rails
+
+This guide covers getting up and running with Ruby on Rails. After reading it,
+you should be familiar with:
+
+* Installing Rails, creating a new Rails application, and connecting your application to a database
+* The general layout of a Rails application
+* The basic principles of MVC (Model, View Controller) and RESTful design
+* How to quickly generate the starting pieces of a Rails application
+
+endprologue.
+
+WARNING. This Guide is based on Rails 3.2. Some of the code shown here will not
+work in earlier versions of Rails.
+
+WARNING: The Edge version of this guide is currently being re-worked. Please excuse us while we re-arrange the place.
+
+h3. Guide Assumptions
+
+This guide is designed for beginners who want to get started with a Rails
+application from scratch. It does not assume that you have any prior experience
+with Rails. However, to get the most out of it, you need to have some
+prerequisites installed:
+
+* The "Ruby":http://www.ruby-lang.org/en/downloads language version 1.8.7 or higher
+
+TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails
+3.0. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02
+though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults
+on Rails 3.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 or
+1.9.3 for smooth sailing.
+
+* The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system
+ ** If you want to learn more about RubyGems, please read the "RubyGems User Guide":http://docs.rubygems.org/read/book/1
+* A working installation of the "SQLite3 Database":http://www.sqlite.org
+
+Rails is a web application framework running on the Ruby programming language.
+If you have no prior experience with Ruby, you will find a very steep learning
+curve diving straight into Rails. There are some good free resources on the
+internet for learning Ruby, including:
+
+* "Mr. Neighborly's Humble Little Ruby Book":http://www.humblelittlerubybook.com
+* "Programming Ruby":http://www.ruby-doc.org/docs/ProgrammingRuby/
+* "Why's (Poignant) Guide to Ruby":http://mislav.uniqpath.com/poignant-guide/
+
+h3. What is Rails?
+
+Rails is a web application development framework written in the Ruby language.
+It is designed to make programming web applications easier by making assumptions
+about what every developer needs to get started. It allows you to write less
+code while accomplishing more than many other languages and frameworks.
+Experienced Rails developers also report that it makes web application
+development more fun.
+
+Rails is opinionated software. It makes the assumption that there is a "best"
+way to do things, and it's designed to encourage that way - and in some cases to
+discourage alternatives. If you learn "The Rails Way" you'll probably discover a
+tremendous increase in productivity. If you persist in bringing old habits from
+other languages to your Rails development, and trying to use patterns you
+learned elsewhere, you may have a less happy experience.
+
+The Rails philosophy includes two major guiding principles:
+
+* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and over again is a bad thing.
+* Convention Over Configuration - means that Rails makes assumptions about what you want to do and how you're going to
+do it, rather than requiring you to specify every little thing through endless configuration files.
+
+h3. Creating a New Rails Project
+
+The best way to use this guide is to follow each step as it happens, no code or
+step needed to make this example application has been left out, so you can
+literally follow along step by step. You can get the complete code
+"here":https://github.com/lifo/docrails/tree/master/guides/code/getting_started.
+
+By following along with this guide, you'll create a Rails project called
++blog+, a
+(very) simple weblog. Before you can start building the application, you need to
+make sure that you have Rails itself installed.
+
+TIP: The examples below use # and $ to denote terminal prompts. If you are using Windows, your prompt will look something like c:\source_code>
+
+h4. Installing Rails
+
+To install Rails, use the +gem install+ command provided by RubyGems:
+
+<shell>
+# gem install rails
+</shell>
+
+TIP. A number of tools exist to help you quickly install Ruby and Ruby
+on Rails on your system. Windows users can use "Rails
+Installer":http://railsinstaller.org, while Mac OS X users can use
+"Rails One Click":http://railsoneclick.com.
+
+To verify that you have everything installed correctly, you should be able to run the following:
+
+<shell>
+$ rails --version
+</shell>
+
+If it says something like "Rails 3.2.3" you are ready to continue.
+
+h4. Creating the Blog Application
+
+Rails comes with a number of generators that are designed to make your development life easier. One of these is the new application generator, which will provide you with the foundation of a Rails application so that you don't have to write it yourself.
+
+To use this generator, open a terminal, navigate to a directory where you have rights to create files, and type:
+
+<shell>
+$ rails new blog
+</shell>
+
+This will create a Rails application called Blog in a directory called blog.
+
+TIP: You can see all of the command line options that the Rails
+application builder accepts by running +rails new -h+.
+
+After you create the blog application, switch to its folder to continue work directly in that application:
+
+<shell>
+$ cd blog
+</shell>
+
+The +rails new blog+ command we ran above created a folder in your
+working directory called +blog+. The +blog+ directory has a number of
+auto-generated files and folders that make up the structure of a Rails
+application. Most of the work in this tutorial will happen in the +app/+ folder, but here's a basic rundown on the function of each of the files and folders that Rails created by default:
+
+|_.File/Folder|_.Purpose|
+|app/|Contains the controllers, models, views, helpers, mailers and assets for your application. You'll focus on this folder for the remainder of this guide.|
+|config/|Configure your application's runtime rules, routes, database, and more. This is covered in more detail in "Configuring Rails Applications":configuring.html|
+|config.ru|Rack configuration for Rack based servers used to start the application.|
+|db/|Contains your current database schema, as well as the database migrations.|
+|doc/|In-depth documentation for your application.|
+|Gemfile<BR />Gemfile.lock|These files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see "the Bundler website":http://gembundler.com |
+|lib/|Extended modules for your application.|
+|log/|Application log files.|
+|public/|The only folder seen to the world as-is. Contains the static files and compiled assets.|
+|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.|
+|README.rdoc|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
+|script/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
+|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html|
+|tmp/|Temporary files|
+|vendor/|A place for all third-party code. In a typical Rails application, this includes Ruby Gems and the Rails source code (if you optionally install it into your project).|
+
+h3. Hello, Rails!
+
+To begin with, let's get some text up on screen quickly. To do this, you need to get your Rails application server running.
+
+h4. Starting up the Web Server
+
+You actually have a functional Rails application already. To see it, you need to start a web server on your development machine. You can do this by running:
+
+<shell>
+$ rails server
+</shell>
+
+TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the absence of a runtime will give you an +execjs+ error. Usually Mac OS X and Windows come with a JavaScript runtime installed. Rails adds the +therubyracer+ gem to Gemfile in a commented line for new apps and you can uncomment if you need it. +therubyrhino+ is the recommended runtime for JRuby users and is added by default to Gemfile in apps generated under JRuby. You can investigate about all the supported runtimes at "ExecJS":https://github.com/sstephenson/execjs#readme.
+
+This will fire up WEBrick, a webserver built into Ruby by default. To see your application in action, open a browser window and navigate to "http://localhost:3000":http://localhost:3000. You should see the Rails default information page:
+
+!images/rails_welcome.png(Welcome Aboard screenshot)!
+
+TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. In development mode, Rails does not generally require you to restart the server; changes you make in files will be automatically picked up by the server.
+
+The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your application's environment.
+
+h4. Say "Hello", Rails
+
+To get Rails saying "Hello", you need to create at minimum a _controller_ and a _view_.
+
+A controller's purpose is to receive specific requests for the application. _Routing_ decides which controller receives which requests. Often, there is more than one route to each controller, and different routes can be served by different _actions_. Each action's purpose is to collect information to provide it to a view.
+
+A view's purpose is to display this information in a human readable format. An important distinction to make is that it is the _controller_, not the view, where information is collected. The view should just display that information. By default, view templates are written in a language called ERB (Embedded Ruby) which is converted by the request cycle in Rails before being sent to the user.
+
+To create a new controller, you will need to run the "controller" generator and tell it you want a controller called "welcome" with an action called "index", just like this:
+
+<shell>
+$ rails generate controller welcome index
+</shell>
+
+Rails will create several files for you. Most important of these are of course the controller, located at +app/controllers/welcome_controller.rb+ and the view, located at +app/views/welcome/index.html.erb+.
+
+Open the +app/views/welcome/index.html.erb+ file in your text editor and edit it to contain a single line of code:
+
+<html>
+<h1>Hello, Rails!</h1>
+</html>
+
+h4. Setting the Application Home Page
+
+Now that we have made the controller and view, we need to tell Rails when we want "Hello Rails!" to show up. In our case, we want it to show up when we navigate to the root URL of our site, "http://localhost:3000":http://localhost:3000. At the moment, however, the "Welcome Aboard" smoke test is occupying that spot.
+
+To fix this, delete the +index.html+ file located inside the +public+ directory of the application.
+
+You need to do this because Rails will serve any static file in the +public+ directory that matches a route in preference to any dynamic content you generate from the controllers. The +index.html+ file is special: it will be served if a request comes in at the root route, e.g. http://localhost:3000. If another request such as http://localhost:3000/welcome happened, a static file at <tt>public/welcome.html</tt> would be served first, but only if it existed.
+
+Next, you have to tell Rails where your actual home page is located.
+
+Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +root :to+ and uncomment it. It should look something like the following:
+
+<ruby>
+Blog::Application.routes.draw do
+
+ #...
+ # You can have the root of your site routed with "root"
+ # just remember to delete public/index.html.
+ root :to => "welcome#index"
+</ruby>
+
+The +root :to => "welcome#index"+ tells Rails to map requests to the root of the application to the welcome controller's index action. This was created earlier when you ran the controller generator (+rails generate controller welcome index+).
+
+If you navigate to "http://localhost:3000":http://localhost:3000 in your browser, you'll see the +Hello, Rails!+ message you put into +app/views/welcome/index.html.erb+, indicating that this new route is indeed going to +WelcomeController+'s +index+ action and is rendering the view correctly.
+
+NOTE. For more information about routing, refer to "Rails Routing from the Outside In":routing.html.
+
+h3. Getting Up and Running
+
+Now that you've seen how to create a controller, an action and a view, let's create something with a bit more substance.
+
+In the Blog application, you will now create a new _resource_. A resource is the term used for a collection of similar objects, such as posts, people or animals. You can create, read, update and destroy items for a resource and these operations are referred to as _CRUD_ operations.
+
+In the next section, you will add the ability to create new posts in your application and be able to view them. This is the "C" and the "R" from CRUD: creation and reading. The form for doing this will look like this:
+
+!images/getting_started/new_post.png(The new post form)!
+
+It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards.
+
+h4. Laying down the ground work
+
+The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at +/posts/new+. If you attempt to navigate to that now -- by visiting "http://localhost:3000/posts/new":http://localhost:3000/posts/new -- Rails will give you a routing error:
+
+!images/getting_started/routing_error_no_route_matches.png(A routing error, no route matches /posts/new)!
+
+This is because there is nowhere inside the routes for the application -- defined inside +config/routes.rb+ -- that defines this route. By default, Rails has no routes configured at all, besides the root route you defined earlier, and so you must define your routes as you need them.
+
+ To do this, you're going to need to create a route inside +config/routes.rb+ file, on a new line between the +do+ and the +end+ for the +draw+ method:
+
+<ruby>
+get "posts/new"
+</ruby>
+
+This route is a super-simple route: it defines a new route that only responds to +GET+ requests, and that the route is at +posts/new+. But how does it know where to go without the use of the +:to+ option? Well, Rails uses a sensible default here: Rails will assume that you want this route to go to the new action inside the posts controller.
+
+With the route defined, requests can now be made to +/posts/new+ in the application. Navigate to "http://localhost:3000/posts/new":http://localhost:3000/posts/new and you'll see another routing error:
+
+!images/getting_started/routing_error_no_controller.png(Another routing error, uninitialized constant PostsController)!
+
+This error is happening because this route need a controller to be defined. The route is attempting to find that controller so it can serve the request, but with the controller undefined, it just can't do that. The solution to this particular problem is simple: you need to create a controller called +PostsController+. You can do this by running this command:
+
+<shell>
+$ rails g controller posts
+</shell>
+
+If you open up the newly generated +app/controllers/posts_controller.rb+ you'll see a fairly empty controller:
+
+<ruby>
+class PostsController < ApplicationController
+end
+</ruby>
+
+A controller is simply a class that is defined to inherit from +ApplicationController+. It's inside this class that you'll define methods that will become the actions for this controller. These actions will perform CRUD operations on the posts within our system.
+
+If you refresh "http://localhost:3000/posts/new":http://localhost:3000/posts/new now, you'll get a new error:
+
+!images/getting_started/unknown_action_new_for_posts.png(Unknown action new for PostsController!)!
+
+This error indicates that Rails cannot find the +new+ action inside the +PostsController+ that you just generated. This is because when controllers are generated in Rails they are empty by default, unless you tell it you wanted actions during the generation process.
+
+To manually define an action inside a controller, all you need to do is to define a new method inside the controller. Open +app/controllers/posts_controller.rb+ and inside the +PostsController+ class, define a +new+ method like this:
+
+<ruby>
+def new
+end
+</ruby>
+
+With the +new+ method defined in +PostsController+, if you refresh "http://localhost:3000/posts/new":http://localhost:3000/posts/new you'll see another error:
+
+!images/getting_started/template_is_missing_posts_new.png(Template is missing for posts/new)!
+
+You're getting this error now because Rails expects plain actions like this one to have views associated with them to display their information. With no view available, Rails errors out.
+
+In the above image, the bottom line has been truncated. Let's see what the full thing looks like:
+
+<blockquote>
+Missing template posts/new, application/new with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views"
+</blockquote>
+
+That's quite a lot of text! Let's quickly go through and understand what each part of it does.
+
+The first part identifies what template is missing. In this case, it's the +posts/new+ template. Rails will first look for this template. If not found, then it will attempt to load a template called +application/new+. It looks for one here because the +PostsController+ inherits from +ApplicationController+.
+
+The next part of the message contains a hash. The +:locale+ key in this hash simply indicates what spoken language template should be retrieved. By default, this is the English -- or "en" -- template. The next key, +:formats+ specifies the format of template to be served in response . The default format is +:html+, and so Rails is looking for an HTML template. The final key, +:handlers+, is telling us what _template handlers_ could be used to render our template. +:erb+ is most commonly used for HTML templates, +:builder+ is used for XML templates, and +:coffee+ uses CoffeeScript to build JavaScript templates.
+
+The final part of this message tells us where Rails has looked for the templates. Templates within a basic Rails application like this are kept in a single location, but in more complex applications it could be many different paths.
+
+The simplest template that would work in this case would be one located at +app/views/posts/new.html.erb+. The extension of this file name is key: the first extension is the _format_ of the template, and the second extension is the _handler_ that will be used. Rails is attempting to find a template called +posts/new+ within +app/views+ for the application. The format for this template can only be +html+ and the handler must be one of +erb+, +builder+ or +coffee+. Because you want to create a new HTML form, you will be using the +ERB+ language. Therefore the file should be called +posts/new.html.erb+ and needs to be located inside the +app/views+ directory of the application.
+
+Go ahead now and create a new file at +app/views/posts/new.html.erb+ and write this content in it:
+
+<erb>
+<h1>New Post</h1>
+</erb>
+
+When you refresh "http://localhost:3000/posts/new":http://localhost:3000/posts/new you'll now see that the page has a title. The route, controller, action and view are now working harmoniously! It's time to create the form for a new post.
+
+h4. The first form
+
+To create a form within this template, you will use a <em>form
+builder</em>. The primary form builder for Rails is provided by a helper
+method called +form_for+. To use this method, add this code into +app/views/posts/new.html.erb+:
+
+<erb>
+<%= form_for :post do |f| %>
+ <p>
+ <%= f.label :title %><br>
+ <%= f.text_field :title %>
+ </p>
+
+ <p>
+ <%= f.label :text %><br>
+ <%= f.text_area :text %>
+ </p>
+
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+</erb>
+
+If you refresh the page now, you'll see the exact same form as in the example. Building forms in Rails is really just that easy!
+
+When you call +form_for+, you pass it an identifying object for this
+form. In this case, it's the symbol +:post+. This tells the +form_for+
+helper what this form is for. Inside the block for this method, the
++FormBuilder+ object -- represented by +f+ -- is used to build two labels and two text fields, one each for the title and text of a post. Finally, a call to +submit+ on the +f+ object will create a submit button for the form.
+
+There's one problem with this form though. If you inspect the HTML that is generated, by viewing the source of the page, you will see that the +action+ attribute for the form is pointing at +/posts/new+. This is a problem because this route goes to the very page that you're on right at the moment, and that route should only be used to display the form for a new post.
+
+The form needs to use a different URL in order to go somewhere else.
+This can be done quite simply with the +:url+ option of +form_for+.
+Typically in Rails, the action that is used for new form submissions
+like this is called "create", and so the form should be pointed to that action.
+
+Edit the +form_for+ line inside +app/views/posts/new.html.erb+ to look like this:
+
+<erb>
+<%= form_for :post, :url => { :action => :create } do |f| %>
+</erb>
+
+In this example, a +Hash+ object is passed to the +:url+ option. What Rails will do with this is that it will point the form to the +create+ action of the current controller, the +PostsController+, and will send a +POST+ request to that route. For this to work, you will need to add a route to +config/routes.rb+, right underneath the one for "posts/new":
+
+<ruby>
+post "posts/create"
+</ruby>
+
+By using the +post+ method rather than the +get+ method, Rails will define a route that will only respond to POST methods. The POST method is the typical method used by forms all over the web.
+
+With the form and its associated route defined, you will be able to fill in the form and then click the submit button to begin the process of creating a new post, so go ahead and do that. When you submit the form, you should see a familiar error:
+
+!images/getting_started/unknown_action_create_for_posts.png(Unknown action create for PostsController)!
+
+You now need to create the +create+ action within the +PostsController+ for this to work.
+
+h4. Creating posts
+
+To make the "Unknown action" go away, you can define a +create+ action within the +PostsController+ class in +app/controllers/posts_controller.rb+, underneath the +new+ action:
+
+<ruby>
+class PostsController < ApplicationController
+ def new
+ end
+
+ def create
+ end
+
+end
+</ruby>
+
+If you re-submit the form now, you'll see another familiar error: a template is missing. That's ok, we can ignore that for now. What the +create+ action should be doing is saving our new post to a database.
+
+When a form is submitted, the fields of the form are sent to Rails as _parameters_. These parameters can then be referenced inside the controller actions, typically to perform a particular task. To see what these parameters look like, change the +create+ action to this:
+
+<ruby>
+def create
+ render :text => params[:post].inspect
+end
+</ruby>
+
+The +render+ method here is taking a very simple hash with a key of +text+ and value of +params[:post].inspect+. The +params+ method is the object which represents the parameters (or fields) coming in from the form. The +params+ method returns a +HashWithIndifferentAccess+ object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form.
+
+If you re-submit the form one more time you'll now no longer get the missing template error. Instead, you'll see something that looks like the following:
+
+<ruby>
+{"title"=>"First post!", "text"=>"This is my first post."}
+</ruby>
+
+This action is now displaying the parameters for the post that are coming in from the form. However, this isn't really all that helpful. Yes, you can see the parameters but nothing in particular is being done with them.
+
+h4. Creating the Post model
+
+Models in Rails use a singular name, and their corresponding database tables use
+a plural name. Rails provides a generator for creating models, which
+most Rails developers tend to use when creating new models.
+To create the new model, run this command in your terminal:
+
+<shell>
+$ rails generate model Post title:string text:text
+</shell>
+
+With that command we told Rails that we want a +Post+ model, together
+with a _title_ attribute of type string, and a _text_ attribute
+of type text. Those attributes are automatically added to the +posts+
+table in the database and mapped to the +Post+ model.
+
+Rails responded by creating a bunch of files. For
+now, we're only interested in +app/models/post.rb+ and
++db/migrate/20120419084633_create_posts.rb+ (your name could be a bit
+different). The latter is responsible
+for creating the database structure, which is what we'll look at next.
+
+TIP: Active Record is smart enough to automatically map column names to
+model attributes, which means you don't have to declare attributes
+inside Rails models, as that will be done automatically by Active
+Record.
+
+h4. Running a Migration
+
+As we've just seen, +rails generate model+ created a _database
+migration_ file inside the +db/migrate+ directory.
+Migrations are Ruby classes that are designed to make it simple to
+create and modify database tables. Rails uses rake commands to run migrations,
+and it's possible to undo a migration after it's been applied to your database.
+Migration filenames include a timestamp to ensure that they're processed in the
+order that they were created.
+
+If you look in the +db/migrate/20120419084633_create_posts.rb+ file (remember,
+yours will have a slightly different name), here's what you'll find:
+
+<ruby>
+class CreatePosts < ActiveRecord::Migration
+ def change
+ create_table :posts do |t|
+ t.string :title
+ t.text :text
+
+ t.timestamps
+ end
+ end
+end
+</ruby>
+
+The above migration creates a method named +change+ which will be called when you
+run this migration. The action defined in this method is also reversible, which
+means Rails knows how to reverse the change made by this migration, in case you
+want to reverse it later. When you run this migration it will create a
++posts+ table with one string column and a text column. It also creates two
+timestamp fields to allow Rails to track post creation and update times. More
+information about Rails migrations can be found in the "Rails Database
+Migrations":migrations.html guide.
+
+At this point, you can use a rake command to run the migration:
+
+<shell>
+$ rake db:migrate
+</shell>
+
+Rails will execute this migration command and tell you it created the Posts
+table.
+
+<shell>
+== CreatePosts: migrating ====================================================
+-- create_table(:posts)
+ -> 0.0019s
+== CreatePosts: migrated (0.0020s) ===========================================
+</shell>
+
+NOTE. Because you're working in the development environment by default, this
+command will apply to the database defined in the +development+ section of your
++config/database.yml+ file. If you would like to execute migrations in another
+environment, for instance in production, you must explicitly pass it when
+invoking the command: +rake db:migrate RAILS_ENV=production+.
+
+h4. Saving data in the controller
+
+Back in +posts_controller+, we need to change the +create+ action
+to use the new +Post+ model to save the data in the database. Open that file
+and change the +create+ action to look like this:
+
+<ruby>
+def create
+ @post = Post.new(params[:post])
+
+ @post.save
+ redirect_to :action => :show, :id => @post.id
+end
+</ruby>
+
+Here's what's going on: every Rails model can be initialized with its
+respective attributes, which are automatically mapped to the respective
+database columns. In the first line we do just that (remember that
++params[:post]+ contains the attributes we're interested in). Then,
++@post.save+ is responsible for saving the model in the database.
+Finally, we redirect the user to the +show+ action,
+wich we'll define later.
+
+TIP: As we'll see later, +@post.save+ returns a boolean indicating
+wherever the model was saved or not.
+
+h4. Showing Posts
+
+If you submit the form again now, Rails will complain about not finding
+the +show+ action. That's not very useful though, so let's add the
++show+ action before proceeding. Open +config/routes.rb+ and add the following route:
+
+<ruby>
+get "posts/:id" => "posts#show"
+</ruby>
+
+The special syntax +:id+ tells rails that this route expects an +:id+
+parameter, which in our case will be the id of the post. Note that this
+time we had to specify the actual mapping, +posts#show+ because
+otherwise Rails would not know which action to render.
+
+As we did before, we need to add the +show+ action in the
++posts_controller+ and its respective view.
+
+<ruby>
+def show
+ @post = Post.find(params[:id])
+end
+</ruby>
+
+A couple of things to note. We use +Post.find+ to find the post we're
+interested in. We also use an instance variable (prefixed by +@+) to
+hold a reference to the post object. We do this because Rails will pass all instance
+variables to the view.
+
+Now, create a new file +app/view/posts/show.html.erb+ with the following
+content:
+
+<erb>
+<p>
+ <strong>Title:</strong>
+ <%= @post.title %>
+</p>
+
+<p>
+ <strong>Text:</strong>
+ <%= @post.text %>
+</p>
+</erb>
+
+Finally, if you now go to
+"http://localhost:3000/posts/new":http://localhost:3000/posts/new you'll
+be able to create a post. Try it!
+
+!images/getting_started/show_action_for_posts.png(Show action for posts)!
+
+h4. Listing all posts
+
+We still need a way to list all our posts, so let's do that. As usual,
+we'll need a route placed into +config/routes.rb+:
+
+<ruby>
+get "posts" => "posts#index"
+</ruby>
+
+And an action for that route inside the +PostsController+ in the +app/controllers/posts_controller.rb+ file:
+
+<ruby>
+def index
+ @posts = Post.all
+end
+</ruby>
+
+And then finally a view for this action, located at +app/views/posts/index.html.erb+:
+
+<erb>
+<h1>Listing posts</h1>
+
+<table>
+ <tr>
+ <th>Title</th>
+ <th>Text</th>
+ </tr>
+
+ <% @posts.each do |post| %>
+ <tr>
+ <td><%= post.title %></td>
+ <td><%= post.text %></td>
+ </tr>
+ <% end %>
+</table>
+</erb>
+
+Now if you go to +http://localhost:3000/posts+ you will see a list of all the posts that you have created.
+
+h4. Adding links
+
+You can now create, show, and list posts. Now let's add some links to
+navigate through pages.
+
+Open +app/views/welcome/index.html.erb+ and modify it as follows:
+
+<ruby>
+<h1>Hello, Rails!</h1>
+<%= link_to "My Blog", :controller => "posts" %>
+</ruby>
+
+The +link_to+ method is one of Rails' built-in view helpers. It creates a
+hyperlink based on text to display and where to go - in this case, to the path
+for posts.
+
+Let's add links to the other views as well, starting with adding this "New Post" link to +app/views/posts/index.html.erb+, placing it above the +<table>+ tag:
+
+<erb>
+<%= link_to 'New post', :action => :new %>
+</erb>
+
+This link will allow you to bring up the form that lets you create a new post. You should also add a link to this template -- +app/views/posts/new.html.erb+ -- to go back to the +index+ action. Do this by adding this underneath the form in this template:
+
+<erb>
+<%= form_for :post do |f| %>
+ ...
+<% end %>
+
+<%= link_to 'Back', :action => :index %>
+</erb>
+
+Finally, add another link to the +app/views/posts/show.html.erb+ template to go back to the +index+ action as well, so that people who are viewing a single post can go back and view the whole list again:
+
+<erb>
+<p>
+ <strong>Title:</strong>
+ <%= @post.title %>
+</p>
+
+<p>
+ <strong>Text:</strong>
+ <%= @post.text %>
+</p>
+
+<%= link_to 'Back', :action => :index %>
+</erb>
+
+TIP: If you want to link to an action in the same controller, you don't
+need to specify the +:controller+ option, as Rails will use the current
+controller by default.
+
+TIP: In development mode (which is what you're working in by default), Rails
+reloads your application with every browser request, so there's no need to stop
+and restart the web server when a change is made.
+
+h4. Allowing the update of fields
+
+The model file, +app/models/post.rb+ is about as simple as it can get:
+
+<ruby>
+class Post < ActiveRecord::Base
+end
+</ruby>
+
+There isn't much to this file - but note that the +Post+ class inherits from
++ActiveRecord::Base+. Active Record supplies a great deal of functionality to
+your Rails models for free, including basic database CRUD (Create, Read, Update,
+Destroy) operations, data validation, as well as sophisticated search support
+and the ability to relate multiple models to one another.
+
+Rails includes methods to help you secure some of your model fields.
+Open the +app/models/post.rb+ file and edit it:
+
+<ruby>
+class Post < ActiveRecord::Base
+ attr_accessible :text, :title
+end
+</ruby>
+
+This change will ensure that all changes made through HTML forms can edit the content of the text and title fields.
+It will not be possible to define any other field value through forms. You can still define them by calling the `field=` method of course.
+Accessible attributes and the mass assignment problem is covered in details in the "Security guide":security.html#mass-assignment
+
+h4. Adding Some Validation
+
+Rails includes methods to help you validate the data that you send to models.
+Open the +app/models/post.rb+ file and edit it:
+
+<ruby>
+class Post < ActiveRecord::Base
+ attr_accessible :text, :title
+
+ validates :title, :presence => true,
+ :length => { :minimum => 5 }
+end
+</ruby>
+
+These changes will ensure that all posts have a title that is at least five characters long.
+Rails can validate a variety of conditions in a model, including the presence or uniqueness of columns, their
+format, and the existence of associated objects. Validations are covered in detail
+in "Active Record Validations and
+Callbacks":active_record_validations_callbacks.html#validations-overview
+
+With the validation now in place, when you call +@post.save+ on an invalid
+post, it will return +false+. If you open +app/controllers/posts_controller.rb+
+again, you'll notice that we don't check the result of calling +@post.save+
+inside the +create+ action. If +@post.save+ fails in this situation, we need to
+show the form back to the user. To do this, change the +new+ and +create+
+actions inside +app/controllers/posts_controller.rb+ to these:
+
+<ruby>
+def new
+ @post = Post.new
+end
+
+def create
+ @post = Post.new(params[:post])
+
+ if @post.save
+ redirect_to :action => :show, :id => @post.id
+ else
+ render 'new'
+ end
+end
+</ruby>
+
+The +new+ action is now creating a new instance variable called +@post+, and
+you'll see why that is in just a few moments.
+
+Notice that inside the +create+ action we use +render+ instead of +redirect_to+ when +save+
+returns +false+. The +render+ method is used so that the +@post+ object is passed back to the +new+ template when it is rendered. This rendering is done within the same request as the form submission, whereas the +redirect_to+ will tell the browser to issue another request.
+
+If you reload
+"http://localhost:3000/posts/new":http://localhost:3000/posts/new and
+try to save a post without a title, Rails will send you back to the
+form, but that's not very useful. You need to tell the user that
+something went wrong. To do that, you'll modify
++app/views/posts/new.html.erb+ to check for error messages:
+
+<erb>
+<%= form_for :post, :url => { :action => :create } do |f| %>
+ <% if @post.errors.any? %>
+ <div id="errorExplanation">
+ <h2><%= pluralize(@post.errors.count, "error") %> prohibited
+ this post from being saved:</h2>
+ <ul>
+ <% @post.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+ <p>
+ <%= f.label :title %><br>
+ <%= f.text_field :title %>
+ </p>
+
+ <p>
+ <%= f.label :text %><br>
+ <%= f.text_area :text %>
+ </p>
+
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', :action => :index %>
+</erb>
+
+A few things are going on. We check if there are any errors with
++@post.errors.any?+, and in that case we show a list of all
+errors with +@post.errors.full_messages+.
+
++pluralize+ is a rails helper that takes a number and a string as its
+arguments. If the number is greater than one, the string will be automatically pluralized.
+
+The reason why we added +@post = Post.new+ in +posts_controller+ is that
+otherwise +@post+ would be +nil+ in our view, and calling
++@post.errors.any?+ would throw an error.
+
+TIP: Rails automatically wraps fields that contain an error with a div
+with class +field_with_errors+. You can define a css rule to make them
+standout.
+
+Now you'll get a nice error message when saving a post without title when you
+attempt to do just that on the "new post form(http://localhost:3000/posts/new)":http://localhost:3000/posts/new.
+
+!images/getting_started/form_with_errors.png(Form With Errors)!
+
+h4. Updating Posts
+
+We've covered the "CR" part of CRUD. Now let's focus on the "U" part,
+updating posts.
+
+The first step we'll take is adding a +edit+ action to
++posts_controller+.
+
+Start by adding a route to +config/routes.rb+:
+
+<ruby>
+get "posts/:id/edit" => "posts#edit"
+</ruby>
+
+And then add the controller action:
+
+<ruby>
+def edit
+ @post = Post.find(params[:id])
+end
+</ruby>
+
+The view will contain a form similar to the one we used when creating
+new posts. Create a file called +app/views/posts/edit.html.erb+ and make
+it look as follows:
+
+<erb>
+<h1>Editing post</h1>
+
+<%= form_for :post, :url => { :action => :update, :id => @post.id },
+:method => :put do |f| %>
+ <% if @post.errors.any? %>
+ <div id="errorExplanation">
+ <h2><%= pluralize(@post.errors.count, "error") %> prohibited
+ this post from being saved:</h2>
+ <ul>
+ <% @post.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+ <p>
+ <%= f.label :title %><br>
+ <%= f.text_field :title %>
+ </p>
+
+ <p>
+ <%= f.label :text %><br>
+ <%= f.text_area :text %>
+ </p>
+
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', :action => :index %>
+</erb>
+
+This time we point the form to the +update+ action, which is not defined yet
+but will be very soon.
+
+The +:method => :put+ option tells Rails that we want this form to be
+submitted via the +PUT+, HTTP method which is the HTTP method you're expected to use to
+*update* resources according to the REST protocol.
+
+TIP: By default forms built with the +form_for_ helper are sent via +POST+.
+
+Next, we need to add the +update+ action. The file
++config/routes.rb+ will need just one more line:
+
+<ruby>
+put "posts/:id" => "posts#update"
+</ruby>
+
+And then create the +update+ action in +app/controllers/posts_controller.rb+:
+
+<ruby>
+def update
+ @post = Post.find(params[:id])
+
+ if @post.update_attributes(params[:post])
+ redirect_to :action => :show, :id => @post.id
+ else
+ render 'edit'
+ end
+end
+</ruby>
+
+The new method, +update_attributes+, is used when you want to update a record
+that already exists, and it accepts an hash containing the attributes
+that you want to update. As before, if there was an error updating the
+post we want to show the form back to the user.
+
+TIP: you don't need to pass all attributes to +update_attributes+. For
+example, if you'd call +@post.update_attributes(:title => 'A new title')+
+Rails would only update the +title+ attribute, leaving all other
+attributes untouched.
+
+Finally, we want to show a link to the +edit+ action in the list of all the
+posts, so let's add that now to +app/views/posts/index.html.erb+ to make it
+appear next to the "Show" link:
+
+<erb>
+
+<table>
+ <tr>
+ <th>Title</th>
+ <th>Text</th>
+ <th></th>
+ <th></th>
+ </tr>
+
+<% @posts.each do |post| %>
+ <tr>
+ <td><%= post.title %></td>
+ <td><%= post.text %></td>
+ <td><%= link_to 'Show', :action => :show, :id => post.id %></td>
+ <td><%= link_to 'Edit', :action => :edit, :id => post.id %></td>
+ </tr>
+<% end %>
+</table>
+</erb>
+
+And we'll also add one to the +app/views/posts/show.html.erb+ template as well,
+so that there's also an "Edit" link on a post's page. Add this at the bottom of
+the template:
+
+<erb>
+...
+
+
+<%= link_to 'Back', :action => :index %>
+| <%= link_to 'Edit', :action => :edit, :id => @post.id %>
+</erb>
+
+And here's how our app looks so far:
+
+!images/getting_started/index_action_with_edit_link.png(Index action
+with edit link)!
+
+h4. Using partials to clean up duplication in views
+
++partials+ are what Rails uses to remove duplication in views. Here's a
+simple example:
+
+<erb>
+# app/views/user/show.html.erb
+
+<h1><%= @user.name %></h1>
+
+<%= render 'user_details' %>
+
+# app/views/user/_user_details.html.erb
+
+<%= @user.location %>
+
+<%= @user.about_me %>
+</erb>
+
+The +users/show+ template will automatically include the content of the
++users/_user_details+ template. Note that partials are prefixed by an underscore,
+as to not be confused with regular views. However, you don't include the
+underscore when including them with the +helper+ method.
+
+TIP: You can read more about partials in the "Layouts and Rendering in
+Rails":layouts_and_rendering.html guide.
+
+Our +edit+ action looks very similar to the +new+ action, in fact they
+both share the same code for displaying the form. Lets clean them up by
+using a partial.
+
+Create a new file +app/views/posts/_form.html.erb+ with the following
+content:
+
+<erb>
+<%= form_for @post do |f| %>
+ <% if @post.errors.any? %>
+ <div id="errorExplanation">
+ <h2><%= pluralize(@post.errors.count, "error") %> prohibited
+ this post from being saved:</h2>
+ <ul>
+ <% @post.errors.full_messages.each do |msg| %>
+ <li><%= msg %></li>
+ <% end %>
+ </ul>
+ </div>
+ <% end %>
+ <p>
+ <%= f.label :title %><br>
+ <%= f.text_field :title %>
+ </p>
+
+ <p>
+ <%= f.label :text %><br>
+ <%= f.text_area :text %>
+ </p>
+
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+</erb>
+
+Everything except for the +form_for+ declaration remained the same.
+How +form_for+ can figure out the right +action+ and +method+ attributes
+when building the form will be explained in just a moment. For now, let's update the
++app/views/posts/new.html.erb+ view to use this new partial, rewriting it
+completely:
+
+<erb>
+<h1>New post</h1>
+
+<%= render 'form' %>
+
+<%= link_to 'Back', :action => :index %>
+</erb>
+
+Then do the same for the +app/views/posts/edit.html.erb+ view:
+
+<erb>
+<h1>Edit post</h1>
+
+<%= render 'form' %>
+
+<%= link_to 'Back', :action => :index %>
+</erb>
+
+Point your browser to "http://localhost:3000/posts/new":http://localhost:3000/posts/new and
+try creating a new post. Everything still works. Now try editing the
+post and you'll receive the following error:
+
+!images/getting_started/undefined_method_post_path.png(Undefined method
+post_path)!
+
+To understand this error, you need to understand how +form_for+ works.
+When you pass an object to +form_for+ and you don't specify a +:url+
+option, Rails will try to guess the +action+ and +method+ options by
+checking if the passed object is a new record or not. Rails follows the
+REST convention, so to create a new +Post+ object it will look for a
+route named +posts_path+, and to update a +Post+ object it will look for
+a route named +post_path+ and pass the current object. Similarly, rails
+knows that it should create new objects via POST and update them via
+PUT.
+
+If you run +rake routes+ from the console you'll see that we already
+have a +posts_path+ route, which was created automatically by Rails when we
+defined the route for the index action.
+However, we don't have a +post_path+ yet, which is the reason why we
+received an error before.
+
+<shell>
+# rake routes
+
+ posts GET /posts(.:format) posts#index
+ posts_new GET /posts/new(.:format) posts#new
+posts_create POST /posts/create(.:format) posts#create
+ GET /posts/:id(.:format) posts#show
+ GET /posts/:id/edit(.:format) posts#edit
+ PUT /posts/:id(.:format) posts#update
+ root / welcome#index
+</shell>
+
+To fix this, open +config/routes.rb+ and modify the +get "posts/:id"+
+line like this:
+
+<ruby>
+get "posts/:id" => "posts#show", :as => :post
+</ruby>
+
+The +:as+ option tells the +get+ method that we want to make routing helpers
+called +post_url+ and +post_path+ available to our application. These are
+precisely the methods that the +form_for+ needs when editing a post, and so now
+you'll be able to update posts again.
+
+NOTE: The +:as+ option is available on the +post+, +put+, +delete+ and +match+
+routing methods also.
+
+h4. Deleting Posts
+
+We're now ready to cover the "D" part of CRUD, deleting posts from the
+database. Following the REST convention, we're going to add a route for
+deleting posts to +config/routes.rb+:
+
+<ruby>
+delete "posts/:id" => "posts#destroy"
+</ruby>
+
+The +delete+ routing method should be used for routes that destroy
+resources. If this was left as a typical +get+ route, it could be possible for
+people to craft malicious URLs like this:
+
+<html>
+<a href='http://yoursite.com/posts/1/destroy'>look at this cat!</a>
+</html>
+
+We use the +delete+ method for destroying resources, and this route is mapped to
+the +destroy+ action inside +app/controllers/posts_controller.rb+, which doesn't exist yet, but is
+provided below:
+
+<ruby>
+def destroy
+ @post = Post.find(params[:id])
+ @post.destroy
+
+ redirect_to :action => :index
+end
+</ruby>
+
+You can call +destroy+ on Active Record objects when you want to delete
+them from the database. Note that we don't need to add a view for this
+action since we're redirecting to the +index+ action.
+
+Finally, add a 'destroy' link to your +index+ action template
+(+app/views/posts/index.html.erb) to wrap everything
+together.
+
+<erb>
+<h1>Listing Posts</h1>
+<table>
+ <tr>
+ <th>Title</th>
+ <th>Text</th>
+ <th></th>
+ <th></th>
+ <th></th>
+ </tr>
+
+<% @posts.each do |post| %>
+ <tr>
+ <td><%= post.title %></td>
+ <td><%= post.text %></td>
+ <td><%= link_to 'Show', :action => :show, :id => post.id %></td>
+ <td><%= link_to 'Edit', :action => :edit, :id => post.id %></td>
+ <td><%= link_to 'Destroy', { :action => :destroy, :id => post.id }, :method => :delete, :confirm => 'Are you sure?' %></td>
+ </tr>
+<% end %>
+</table>
+</erb>
+
+Here we're using +link_to+ in a different way. We wrap the
++:action+ and +:id+ attributes in a hash so that we can pass those two keys in
+first as one argument, and then the final two keys as another argument. The +:method+ and +:confirm+
+options are used as html5 attributes so that when the click is linked,
+Rails will first show a confirm dialog to the user, and then submit the
+link with method +delete+. This is done via the JavaScript file +jquery_ujs+
+which is automatically included into your application's layout
+(+app/views/layouts/application.html.erb+) when you generated the application.
+Without this file, the confirmation dialog box wouldn't appear.
+
+!images/getting_started/confirm_dialog.png(Confirm Dialog)!
+
+Congratulations, you can now create, show, list, update and destroy
+posts. In the next section will see how Rails can aid us when creating
+REST applications, and how we can refactor our Blog app to take
+advantage of it.
+
+h4. Going Deeper into REST
+
+We've now covered all the CRUD actions of a REST app. We did so by
+declaring separate routes with the appropriate verbs into
++config/routes.rb+. Here's how that file looks so far:
+
+<ruby>
+get "posts" => "posts#index"
+get "posts/new"
+post "posts/create"
+get "posts/:id" => "posts#show", :as => :post
+get "posts/:id/edit" => "posts#edit"
+put "posts/:id" => "posts#update"
+delete "posts/:id" => "posts#destroy"
+</ruby>
+
+That's a lot to type for covering a single *resource*. Fortunately,
+Rails provides a +resources+ method which can be used to declare a
+standard REST resource. Here's how +config/routes/rb+ looks after the
+cleanup:
+
+<ruby>
+Blog::Application.routes.draw do
+
+ resources :posts
+
+ root :to => "welcome#index"
+end
+</ruby>
+
+If you run +rake routes+, you'll see that all the routes that we
+declared before are still available:
+
+<shell>
+# rake routes
+ posts GET /posts(.:format) posts#index
+ POST /posts(.:format) posts#create
+ new_post GET /posts/new(.:format) posts#new
+edit_post GET /posts/:id/edit(.:format) posts#edit
+ post GET /posts/:id(.:format) posts#show
+ PUT /posts/:id(.:format) posts#update
+ DELETE /posts/:id(.:format) posts#destroy
+ root / welcome#index
+</shell>
+
+Also, if you go through the motions of creating, updating and deleting
+posts the app still works as before.
+
+TIP: In general, Rails encourages the use of resources objects in place
+of declaring routes manually. It was only done in this guide as a learning
+exercise. For more information about routing, see
+"Rails Routing from the Outside In":routing.html.
+
+h3. Adding a Second Model
+
+It's time to add a second model to the application. The second model will handle comments on
+posts.
+
+h4. Generating a Model
+
+We're going to see the same generator that we used before when creating
+the +Post+ model. This time we'll create a +Comment+ model to hold
+reference of post comments. Run this command in your terminal:
+
+<shell>
+$ rails generate model Comment commenter:string body:text post:references
+</shell>
+
+This command will generate four files:
+
+|_.File |_.Purpose|
+|db/migrate/20100207235629_create_comments.rb | Migration to create the comments table in your database (your name will include a different timestamp) |
+| app/models/comment.rb | The Comment model |
+| test/unit/comment_test.rb | Unit testing harness for the comments model |
+| test/fixtures/comments.yml | Sample comments for use in testing |
+
+First, take a look at +comment.rb+:
+
+<ruby>
+class Comment < ActiveRecord::Base
+ belongs_to :post
+end
+</ruby>
+
+This is very similar to the +post.rb+ model that you saw earlier. The difference
+is the line +belongs_to :post+, which sets up an Active Record _association_.
+You'll learn a little about associations in the next section of this guide.
+
+In addition to the model, Rails has also made a migration to create the
+corresponding database table:
+
+<ruby>
+class CreateComments < ActiveRecord::Migration
+ def change
+ create_table :comments do |t|
+ t.string :commenter
+ t.text :body
+ t.references :post
+
+ t.timestamps
+ end
+
+ add_index :comments, :post_id
+ end
+end
+</ruby>
+
+The +t.references+ line sets up a foreign key column for the association between
+the two models. And the +add_index+ line sets up an index for this association
+column. Go ahead and run the migration:
+
+<shell>
+$ rake db:migrate
+</shell>
+
+Rails is smart enough to only execute the migrations that have not already been
+run against the current database, so in this case you will just see:
+
+<shell>
+== CreateComments: migrating =================================================
+-- create_table(:comments)
+ -> 0.0008s
+-- add_index(:comments, :post_id)
+ -> 0.0003s
+== CreateComments: migrated (0.0012s) ========================================
+</shell>
+
+h4. Associating Models
+
+Active Record associations let you easily declare the relationship between two
+models. In the case of comments and posts, you could write out the relationships
+this way:
+
+* Each comment belongs to one post.
+* One post can have many comments.
+
+In fact, this is very close to the syntax that Rails uses to declare this
+association. You've already seen the line of code inside the Comment model that
+makes each comment belong to a Post:
+
+<ruby>
+class Comment < ActiveRecord::Base
+ belongs_to :post
+end
+</ruby>
+
+You'll need to edit the +post.rb+ file to add the other side of the association:
+
+<ruby>
+class Post < ActiveRecord::Base
+ validates :title, :presence => true,
+ :length => { :minimum => 5 }
+
+ has_many :comments
+end
+</ruby>
+
+These two declarations enable a good bit of automatic behavior. For example, if
+you have an instance variable +@post+ containing a post, you can retrieve all
+the comments belonging to that post as an array using +@post.comments+.
+
+TIP: For more information on Active Record associations, see the "Active Record
+Associations":association_basics.html guide.
+
+h4. Adding a Route for Comments
+
+As with the +welcome+ controller, we will need to add a route so that Rails knows
+where we would like to navigate to see +comments+. Open up the
++config/routes.rb+ file again, and edit it as follows:
+
+<ruby>
+resources :posts do
+ resources :comments
+end
+</ruby>
+
+This creates +comments+ as a _nested resource_ within +posts+. This is another
+part of capturing the hierarchical relationship that exists between posts and
+comments.
+
+TIP: For more information on routing, see the "Rails Routing from the Outside
+In":routing.html guide.
+
+h4. Generating a Controller
+
+With the model in hand, you can turn your attention to creating a matching
+controller. Again, we'll use the same generator we used before:
+
+<shell>
+$ rails generate controller Comments
+</shell>
+
+This creates six files and one empty directory:
+
+|_.File/Directory |_.Purpose |
+| app/controllers/comments_controller.rb | The Comments controller |
+| app/views/comments/ | Views of the controller are stored here |
+| test/functional/comments_controller_test.rb | The functional tests for the controller |
+| app/helpers/comments_helper.rb | A view helper file |
+| test/unit/helpers/comments_helper_test.rb | The unit tests for the helper |
+| app/assets/javascripts/comment.js.coffee | CoffeeScript for the controller |
+| app/assets/stylesheets/comment.css.scss | Cascading style sheet for the controller |
+
+Like with any blog, our readers will create their comments directly after
+reading the post, and once they have added their comment, will be sent back to
+the post show page to see their comment now listed. Due to this, our
++CommentsController+ is there to provide a method to create comments and delete
+spam comments when they arrive.
+
+So first, we'll wire up the Post show template
+(+/app/views/posts/show.html.erb+) to let us make a new comment:
+
+<erb>
+<p>
+ <strong>Title:</strong>
+ <%= @post.title %>
+</p>
+
+<p>
+ <strong>Text:</strong>
+ <%= @post.text %>
+</p>
+
+<h2>Add a comment:</h2>
+<%= form_for([@post, @post.comments.build]) do |f| %>
+ <p>
+ <%= f.label :commenter %><br />
+ <%= f.text_field :commenter %>
+ </p>
+ <p>
+ <%= f.label :body %><br />
+ <%= f.text_area :body %>
+ </p>
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+
+<%= link_to 'Edit Post', edit_post_path(@post) %> |
+<%= link_to 'Back to Posts', posts_path %>
+</erb>
+
+This adds a form on the +Post+ show page that creates a new comment by
+calling the +CommentsController+ +create+ action. The +form_for+ call here uses
+an array, which will build a nested route, such as +/posts/1/comments+.
+
+Let's wire up the +create+:
+
+<ruby>
+class CommentsController < ApplicationController
+ def create
+ @post = Post.find(params[:post_id])
+ @comment = @post.comments.create(params[:comment])
+ redirect_to post_path(@post)
+ end
+end
+</ruby>
+
+You'll see a bit more complexity here than you did in the controller for posts.
+That's a side-effect of the nesting that you've set up. Each request for a
+comment has to keep track of the post to which the comment is attached, thus the
+initial call to the +find+ method of the +Post+ model to get the post in question.
+
+In addition, the code takes advantage of some of the methods available for an
+association. We use the +create+ method on +@post.comments+ to create and save
+the comment. This will automatically link the comment so that it belongs to that
+particular post.
+
+Once we have made the new comment, we send the user back to the original post
+using the +post_path(@post)+ helper. As we have already seen, this calls the
++show+ action of the +PostsController+ which in turn renders the +show.html.erb+
+template. This is where we want the comment to show, so let's add that to the
++app/views/posts/show.html.erb+.
+
+<erb>
+<p>
+ <strong>Title:</strong>
+ <%= @post.title %>
+</p>
+
+<p>
+ <strong>Text:</strong>
+ <%= @post.text %>
+</p>
+
+<h2>Comments</h2>
+<% @post.comments.each do |comment| %>
+ <p>
+ <strong>Commenter:</strong>
+ <%= comment.commenter %>
+ </p>
+
+ <p>
+ <strong>Comment:</strong>
+ <%= comment.body %>
+ </p>
+<% end %>
+
+<h2>Add a comment:</h2>
+<%= form_for([@post, @post.comments.build]) do |f| %>
+ <p>
+ <%= f.label :commenter %><br />
+ <%= f.text_field :commenter %>
+ </p>
+ <p>
+ <%= f.label :body %><br />
+ <%= f.text_area :body %>
+ </p>
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+
+<%= link_to 'Edit Post', edit_post_path(@post) %> |
+<%= link_to 'Back to Posts', posts_path %>
+</erb>
+
+Now you can add posts and comments to your blog and have them show up in the
+right places.
+
+!images/getting_started/post_with_comments.png(Post with Comments)!
+
+h3. Refactoring
+
+Now that we have posts and comments working, take a look at the
++app/views/posts/show.html.erb+ template. It is getting long and awkward. We can
+use partials to clean it up.
+
+h4. Rendering Partial Collections
+
+First, we will make a comment partial to extract showing all the comments for the
+post. Create the file +app/views/comments/_comment.html.erb+ and put the
+following into it:
+
+<erb>
+<p>
+ <strong>Commenter:</strong>
+ <%= comment.commenter %>
+</p>
+
+<p>
+ <strong>Comment:</strong>
+ <%= comment.body %>
+</p>
+</erb>
+
+Then you can change +app/views/posts/show.html.erb+ to look like the
+following:
+
+<erb>
+<p>
+ <strong>Title:</strong>
+ <%= @post.title %>
+</p>
+
+<p>
+ <strong>Text:</strong>
+ <%= @post.text %>
+</p>
+
+<h2>Comments</h2>
+<%= render @post.comments %>
+
+<h2>Add a comment:</h2>
+<%= form_for([@post, @post.comments.build]) do |f| %>
+ <p>
+ <%= f.label :commenter %><br />
+ <%= f.text_field :commenter %>
+ </p>
+ <p>
+ <%= f.label :body %><br />
+ <%= f.text_area :body %>
+ </p>
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+
+<%= link_to 'Edit Post', edit_post_path(@post) %> |
+<%= link_to 'Back to Posts', posts_path %>
+</erb>
+
+This will now render the partial in +app/views/comments/_comment.html.erb+ once
+for each comment that is in the +@post.comments+ collection. As the +render+
+method iterates over the <tt>@post.comments</tt> collection, it assigns each
+comment to a local variable named the same as the partial, in this case
++comment+ which is then available in the partial for us to show.
+
+h4. Rendering a Partial Form
+
+Let us also move that new comment section out to its own partial. Again, you
+create a file +app/views/comments/_form.html.erb+ containing:
+
+<erb>
+<%= form_for([@post, @post.comments.build]) do |f| %>
+ <p>
+ <%= f.label :commenter %><br />
+ <%= f.text_field :commenter %>
+ </p>
+ <p>
+ <%= f.label :body %><br />
+ <%= f.text_area :body %>
+ </p>
+ <p>
+ <%= f.submit %>
+ </p>
+<% end %>
+</erb>
+
+Then you make the +app/views/posts/show.html.erb+ look like the following:
+
+<erb>
+<p>
+ <strong>Title:</strong>
+ <%= @post.title %>
+</p>
+
+<p>
+ <strong>Text:</strong>
+ <%= @post.text %>
+</p>
+
+<h2>Add a comment:</h2>
+<%= render "comments/form" %>
+
+<%= link_to 'Edit Post', edit_post_path(@post) %> |
+<%= link_to 'Back to Posts', posts_path %>
+</erb>
+
+The second render just defines the partial template we want to render,
+<tt>comments/form</tt>. Rails is smart enough to spot the forward slash in that
+string and realize that you want to render the <tt>_form.html.erb</tt> file in
+the <tt>app/views/comments</tt> directory.
+
+The +@post+ object is available to any partials rendered in the view because we
+defined it as an instance variable.
+
+h3. Deleting Comments
+
+Another important feature of a blog is being able to delete spam comments. To do
+this, we need to implement a link of some sort in the view and a +DELETE+ action
+in the +CommentsController+.
+
+So first, let's add the delete link in the
++app/views/comments/_comment.html.erb+ partial:
+
+<erb>
+<p>
+ <strong>Commenter:</strong>
+ <%= comment.commenter %>
+</p>
+
+<p>
+ <strong>Comment:</strong>
+ <%= comment.body %>
+</p>
+
+<p>
+ <%= link_to 'Destroy Comment', [comment.post, comment],
+ :confirm => 'Are you sure?',
+ :method => :delete %>
+</p>
+</erb>
+
+Clicking this new "Destroy Comment" link will fire off a <tt>DELETE
+/posts/:id/comments/:id</tt> to our +CommentsController+, which can then use
+this to find the comment we want to delete, so let's add a destroy action to our
+controller:
+
+<ruby>
+class CommentsController < ApplicationController
+
+ def create
+ @post = Post.find(params[:post_id])
+ @comment = @post.comments.create(params[:comment])
+ redirect_to post_path(@post)
+ end
+
+ def destroy
+ @post = Post.find(params[:post_id])
+ @comment = @post.comments.find(params[:id])
+ @comment.destroy
+ redirect_to post_path(@post)
+ end
+
+end
+</ruby>
+
+The +destroy+ action will find the post we are looking at, locate the comment
+within the <tt>@post.comments</tt> collection, and then remove it from the
+database and send us back to the show action for the post.
+
+
+h4. Deleting Associated Objects
+
+If you delete a post then its associated comments will also need to be deleted.
+Otherwise they would simply occupy space in the database. Rails allows you to
+use the +dependent+ option of an association to achieve this. Modify the Post
+model, +app/models/post.rb+, as follows:
+
+<ruby>
+class Post < ActiveRecord::Base
+ validates :title, :presence => true,
+ :length => { :minimum => 5 }
+ has_many :comments, :dependent => :destroy
+end
+</ruby>
+
+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.
+
+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 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
+
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :except => [:index, :show]
+
+ def index
+ @posts = Post.all
+# snipped for brevity
+</ruby>
+
+We also only want to allow authenticated users to delete comments, so in the
++CommentsController+ we write:
+
+<ruby>
+class CommentsController < ApplicationController
+
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :only => :destroy
+
+ def create
+ @post = Post.find(params[:post_id])
+# snipped for brevity
+</ruby>
+
+Now if you try to create a new post, you will be greeted with a basic HTTP
+Authentication challenge
+
+!images/challenge.png(Basic HTTP Authentication Challenge)!
+
+h3. What's Next?
+
+Now that you've seen your first Rails application, you should feel free to
+update it and experiment on your own. But you don't have to do everything
+without help. As you need assistance getting up and running with Rails, feel
+free to consult these support resources:
+
+* The "Ruby on Rails guides":index.html
+* The "Ruby on Rails Tutorial":http://railstutorial.org/book
+* The "Ruby on Rails mailing list":http://groups.google.com/group/rubyonrails-talk
+* The "#rubyonrails":irc://irc.freenode.net/#rubyonrails channel on irc.freenode.net
+
+Rails also comes with built-in help that you can generate using the rake command-line utility:
+
+* Running +rake doc:guides+ will put a full copy of the Rails Guides in the +doc/guides+ folder of your application. Open +doc/guides/index.html+ in your web browser to explore the Guides.
+* Running +rake doc:rails+ will put a full copy of the API documentation for Rails in the +doc/api+ folder of your application. Open +doc/api/index.html+ in your web browser to explore the API documentation.
+
+h3. Configuration Gotchas
+
+The easiest way to work with Rails is to store all external data as UTF-8. If
+you don't, Ruby libraries and Rails will often be able to convert your native
+data into UTF-8, but this doesn't always work reliably, so you're better off
+ensuring that all external data is UTF-8.
+
+If you have made a mistake in this area, the most common symptom is a black
+diamond with a question mark inside appearing in the browser. Another common
+symptom is characters like "ü" appearing instead of "ü". Rails takes a number
+of internal steps to mitigate common causes of these problems that can be
+automatically detected and corrected. However, if you have external data that is
+not stored as UTF-8, it can occasionally result in these kinds of issues that
+cannot be automatically detected by Rails and corrected.
+
+Two very common sources of data that are not UTF-8:
+* Your text editor: Most text editors (such as Textmate), default to saving files as
+ UTF-8. If your text editor does not, this can result in special characters that you
+ enter in your templates (such as é) to appear as a diamond with a question mark inside
+ in the browser. This also applies to your I18N translation files.
+ Most editors that do not already default to UTF-8 (such as some versions of
+ Dreamweaver) offer a way to change the default to UTF-8. Do so.
+* Your database. Rails defaults to converting data from your database into UTF-8 at
+ the boundary. However, if your database is not using UTF-8 internally, it may not
+ be able to store all characters that your users enter. For instance, if your database
+ is using Latin-1 internally, and your user enters a Russian, Hebrew, or Japanese
+ character, the data will be lost forever once it enters the database. If possible,
+ use UTF-8 as the internal storage of your database.
diff --git a/railties/guides/source/i18n.textile b/guides/source/i18n.textile
index 2d4cc13571..ee7176a6c8 100644
--- a/railties/guides/source/i18n.textile
+++ b/guides/source/i18n.textile
@@ -127,7 +127,7 @@ If you want to translate your Rails application to a *single language other than
However, you would probably like to *provide support for more locales* in your application. In such case, you need to set and pass the locale between requests.
-WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>. *Do not do so*. The locale should be transparent and a part of the URL. This way you don't break people's basic assumptions about the web itself: if you send a URL of some page to a friend, she should see the same page, same content. A fancy word for this would be that you're being "<em>RESTful</em>":http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about the RESTful approach in "Stefan Tilkov's articles":http://www.infoq.com/articles/rest-introduction. There may be some exceptions to this rule, which are discussed below.
+WARNING: You may be tempted to store the chosen locale in a _session_ or a <em>cookie</em>, however *do not do this*. The locale should be transparent and a part of the URL. This way you won't break people's basic assumptions about the web itself: if you send a URL to a friend, they should see the same page and content as you. A fancy word for this would be that you're being "<em>RESTful</em>":http://en.wikipedia.org/wiki/Representational_State_Transfer. Read more about the RESTful approach in "Stefan Tilkov's articles":http://www.infoq.com/articles/rest-introduction. Sometimes there are exceptions to this rule and those are discussed below.
The _setting part_ is easy. You can set the locale in a +before_filter+ in the +ApplicationController+ like this:
@@ -220,7 +220,7 @@ Every helper method dependent on +url_for+ (e.g. helpers for named routes like +
You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of the application domain: and URLs should reflect this.
-You probably want URLs to look like this: +www.example.com/en/books+ (which loads the English locale) and +www.example.com/nl/books+ (which loads the Netherlands locale). This is achievable with the "over-riding +default_url_options+" strategy from above: you just have to set up your routes with "+path_prefix+":http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354 option in this way:
+You probably want URLs to look like this: +www.example.com/en/books+ (which loads the English locale) and +www.example.com/nl/books+ (which loads the Dutch locale). This is achievable with the "over-riding +default_url_options+" strategy from above: you just have to set up your routes with "+path_prefix+":http://api.rubyonrails.org/classes/ActionController/Resources.html#M000354 option in this way:
<ruby>
# config/routes.rb
@@ -229,9 +229,9 @@ scope "/:locale" do
end
</ruby>
-Now, when you call the +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Netherlands locale, then, and following calls to +books_path+ should return +"/nl/books"+ (because the locale changed).
+Now, when you call the +books_path+ method you should get +"/en/books"+ (for the default locale). An URL like +http://localhost:3001/nl/books+ should load the Dutch locale, then, and following calls to +books_path+ should return +"/nl/books"+ (because the locale changed).
-If you don't want to force the use of a locale in your routes you can use an optional path scope (donated by the use brackets) like so:
+If you don't want to force the use of a locale in your routes you can use an optional path scope (denoted by the parentheses) like so:
<ruby>
# config/routes.rb
@@ -521,7 +521,7 @@ h5. Bulk and Namespace Lookup
To look up multiple translations at once, an array of keys can be passed:
<ruby>
-I18n.t [:odd, :even], :scope => 'activerecord.errors.messages'
+I18n.t [:odd, :even], :scope => 'errors.messages'
# => ["must be odd", "must be even"]
</ruby>
@@ -819,13 +819,13 @@ h5. Action View Helper Methods
* The +number_to_currency+, +number_with_precision+, +number_to_percentage+, +number_with_delimiter+, and +number_to_human_size+ helpers use the number format settings located in the "number":https://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L2 scope.
-h5. Active Record Methods
+h5. Active Model Methods
* +model_name.human+ and +human_attribute_name+ use translations for model names and attribute names if available in the "activerecord.models":https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L29 scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes".
-* +ActiveRecord::Errors#generate_message+ (which is used by Active Record validations but may also be used manually) uses +model_name.human+ and +human_attribute_name+ (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes".
+* +ActiveModel::Errors#generate_message+ (which is used by Active Model validations but may also be used manually) uses +model_name.human+ and +human_attribute_name+ (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes".
-*+ ActiveRecord::Errors#full_messages+ prepends the attribute name to the error message using a separator that will be looked up from "activerecord.errors.format.separator":https://github.com/rails/rails/blob/master/actionpack/lib/action_view/locale/en.yml#L91 (and which defaults to +'&nbsp;'+).
+* +ActiveModel::Errors#full_messages+ prepends the attribute name to the error message using a separator that will be looked up from "errors.format":https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml#L4 (and which defaults to +"%{attribute} %{message}"+).
h5. Active Support Methods
@@ -866,19 +866,35 @@ The I18n API will catch all of these exceptions when they are thrown in the back
The reason for this is that during development you'd usually want your views to still render even though a translation is missing.
-In other contexts you might want to change this behaviour, though. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module:
+In other contexts you might want to change this behaviour, though. E.g. the default exception handling does not allow to catch missing translations during automated tests easily. For this purpose a different exception handler can be specified. The specified exception handler must be a method on the I18n module or a class with +#call+ method:
<ruby>
module I18n
- def self.just_raise_that_exception(*args)
- raise args.first
+ class JustRaiseExceptionHandler < ExceptionHandler
+ def call(exception, locale, key, options)
+ if exception.is_a?(MissingTranslation)
+ raise exception.to_exception
+ else
+ super
+ end
+ end
end
end
-I18n.exception_handler = :just_raise_that_exception
+I18n.exception_handler = I18n::JustRaiseExceptionHandler.new
</ruby>
-This would re-raise all caught exceptions including +MissingTranslationData+.
+This would re-raise only the +MissingTranslationData+ exception, passing all other input to the default exception handler.
+
+However, if you are using +I18n::Backend::Pluralization+ this handler will also raise +I18n::MissingTranslationData: translation missing: en.i18n.plural.rule+ exception that should normally be ignored to fall back to the default pluralization rule for English locale. To avoid this you may use additional check for translation key:
+
+<ruby>
+if exception.is_a?(MissingTranslation) && key.to_s != 'i18n.plural.rule'
+ raise exception.to_exception
+else
+ super
+end
+</ruby>
Another example where the default behaviour is less desirable is the Rails TranslationHelper which provides the method +#t+ (as well as +#translate+). When a +MissingTranslationData+ exception occurs in this context, the helper wraps the message into a span with the CSS class +translation_missing+.
diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb
new file mode 100644
index 0000000000..74805b2754
--- /dev/null
+++ b/guides/source/index.html.erb
@@ -0,0 +1,30 @@
+<% content_for :page_title do %>
+Ruby on Rails Guides
+<% end %>
+
+<% content_for :header_section do %>
+<%= render 'welcome' %>
+<% end %>
+
+<% content_for :index_section do %>
+<div id="subCol">
+ <dl>
+ <dd class="kindle">Rails Guides are also available for the <%= link_to 'Kindle', 'https://kindle.amazon.com' %>
+and <%= link_to 'Free Kindle Reading Apps', 'http://www.amazon.com/gp/kindle/kcp' %> for the iPad,
+iPhone, Mac, Android, etc. Download them from <%= link_to 'here', @mobi %>.
+ </dd>
+ <dd class="work-in-progress">Guides marked with this icon are currently being worked on and will not be available in the Guides Index menu. While still useful, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections.</dd>
+ </dl>
+</div>
+<% end %>
+
+<% documents_by_section.each do |section| %>
+ <h3><%= section['name'] %></h3>
+ <dl>
+ <% section['documents'].each do |document| %>
+ <%= guide(document['name'], document['url'], :work_in_progress => document['work_in_progress']) do %>
+ <p><%= document['description'] %></p>
+ <% end %>
+ <% end %>
+ </dl>
+<% end %>
diff --git a/railties/guides/source/initialization.textile b/guides/source/initialization.textile
index 32b41fdd2c..155a439e64 100644
--- a/railties/guides/source/initialization.textile
+++ b/guides/source/initialization.textile
@@ -7,7 +7,7 @@ This guide explains the internals of the initialization process in Rails as of R
endprologue.
-This guide goes through every single file, class and method call that is required to boot up the Ruby on Rails stack for a default Rails 3.1 application, explaining each part in detail a long the way. For this guide, we will be focusing on how the two most common methods (+rails server+ and Passenger) boot a Rails application.
+This guide goes through every single file, class and method call that is required to boot up the Ruby on Rails stack for a default Rails 3.1 application, explaining each part in detail along the way. For this guide, we will be focusing on how the two most common methods (+rails server+ and Passenger) boot a Rails application.
NOTE: Paths in this guide are relative to Rails or a Rails application unless otherwise specified.
@@ -17,7 +17,7 @@ As of Rails 3, +script/server+ has become +rails server+. This was done to centr
h4. +bin/rails+
-The actual +rails+ command is kept in _bin/rails_ at the and goes like this:
+The actual +rails+ command is kept in _bin/rails_:
<ruby>
#!/usr/bin/env ruby
@@ -31,7 +31,7 @@ rescue LoadError
end
</ruby>
-This file will attempt to load +rails/cli+ and if it cannot find it then add the +railties/lib+ path to the load path (+$:+) and will then try to require it again.
+This file will attempt to load +rails/cli+. If it cannot find it then +railties/lib+ is added to the load path (+$:+) before retrying.
h4. +railties/lib/rails/cli.rb+
@@ -56,7 +56,7 @@ else
end
</ruby>
-The +rbconfig+ file here is out of Ruby's standard library and provides us with the +RbConfig+ class which contains useful information dependent on how Ruby was compiled. We'll see this in use in +railties/lib/rails/script_rails_loader+.
+The +rbconfig+ file from the Ruby standard library provides us with the +RbConfig+ class which contains detailed information about the Ruby environment, including how Ruby was compiled. We can see this in use in +railties/lib/rails/script_rails_loader+.
<ruby>
require 'pathname'
@@ -71,7 +71,7 @@ module Rails
end
</ruby>
-The +rails/script_rails_loader+ file uses +RbConfig::Config+ to gather up the +bin_dir+ and +ruby_install_name+ values for the configuration which will result in a path such as +/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby+, which is the default path on Mac OS X. If you're running Windows the path may be something such as +C:/Ruby192/bin/ruby+. Anyway, the path on your system may be different, but the point of this is that it will point at the known ruby executable location for your install. The +RbConfig::CONFIG["EXEEXT"]+ will suffix this path with ".exe" if the script is running on Windows. This constant is used later on in +exec_script_rails!+. As for the +SCRIPT_RAILS+ constant, we'll see that when we get to the +in_rails_application?+ method.
+The +rails/script_rails_loader+ file uses +RbConfig::Config+ to obtain the +bin_dir+ and +ruby_install_name+ values for the configuration which together form the path to the Ruby interpreter. The +RbConfig::CONFIG["EXEEXT"]+ will suffix this path with ".exe" if the script is running on Windows. This constant is used later on in +exec_script_rails!+. As for the +SCRIPT_RAILS+ constant, we'll see that when we get to the +in_rails_application?+ method.
Back in +rails/cli+, the next line is this:
@@ -79,7 +79,7 @@ Back in +rails/cli+, the next line is this:
Rails::ScriptRailsLoader.exec_script_rails!
</ruby>
-This method is defined in +rails/script_rails_loader+ like this:
+This method is defined in +rails/script_rails_loader+:
<ruby>
def self.exec_script_rails!
@@ -96,7 +96,7 @@ rescue SystemCallError
end
</ruby>
-This method will first check if the current working directory (+cwd+) is a Rails application or is a subdirectory of one. The way to determine this is defined in the +in_rails_application?+ method like this:
+This method will first check if the current working directory (+cwd+) is a Rails application or a subdirectory of one. This is determined by the +in_rails_application?+ method:
<ruby>
def self.in_rails_application?
@@ -104,7 +104,7 @@ def self.in_rails_application?
end
</ruby>
-The +SCRIPT_RAILS+ constant defined earlier is used here, with +File.exists?+ checking for its presence in the current directory. If this method returns +false+, then +in_rails_application_subdirectory?+ will be used:
+The +SCRIPT_RAILS+ constant defined earlier is used here, with +File.exists?+ checking for its presence in the current directory. If this method returns +false+ then +in_rails_application_subdirectory?+ will be used:
<ruby>
def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd))
@@ -112,17 +112,17 @@ def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd))
end
</ruby>
-This climbs the directory tree until it reaches a path which contains a +script/rails+ file. If a directory is reached which contains this file then this line will run:
+This climbs the directory tree until it reaches a path which contains a +script/rails+ file. If a directory containing this file is reached then this line will run:
<ruby>
exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application?
</ruby>
-This is effectively the same as doing +ruby script/rails [arguments]+. Where +[arguments]+ at this point in time is simply "server".
+This is effectively the same as running +ruby script/rails [arguments]+, where +[arguments]+ at this point in time is simply "server".
h4. +script/rails+
-This file looks like this:
+This file is as follows:
<ruby>
APP_PATH = File.expand_path('../../config/application', __FILE__)
@@ -130,15 +130,13 @@ require File.expand_path('../../config/boot', __FILE__)
require 'rails/commands'
</ruby>
-The +APP_PATH+ constant here will be used later in +rails/commands+. The +config/boot+ file that +script/rails+ references is the +config/boot.rb+ file in our application which is responsible for loading Bundler and setting it up.
+The +APP_PATH+ constant will be used later in +rails/commands+. The +config/boot+ file referenced here is the +config/boot.rb+ file in our application which is responsible for loading Bundler and setting it up.
h4. +config/boot.rb+
+config/boot.rb+ contains this:
<ruby>
-require 'rubygems'
-
# Set up gems listed in the Gemfile.
gemfile = File.expand_path('../../Gemfile', __FILE__)
begin
@@ -159,7 +157,6 @@ In a standard Rails application, there's a +Gemfile+ which declares all dependen
* actionpack (3.1.0.beta)
* activemodel (3.1.0.beta)
* activerecord (3.1.0.beta)
-* activeresource (3.1.0.beta)
* activesupport (3.1.0.beta)
* arel (2.0.7)
* builder (3.0.0)
@@ -243,7 +240,7 @@ In this file there are a lot of lines such as this inside the +ActiveSupport+ mo
autoload :Inflector
</ruby>
-Due to the overriding of the +autoload+ method, Ruby will know to look for this file at +activesupport/lib/active_support/inflector.rb+ when the +Inflector+ class is first referenced.
+Due to the overriding of the +autoload+ method, Ruby will know how to look for this file at +activesupport/lib/active_support/inflector.rb+ when the +Inflector+ class is first referenced.
The +active_support/lib/active_support/version.rb+ that is also required here simply defines an +ActiveSupport::VERSION+ constant which defines a couple of constants inside this module, the main constant of this is +ActiveSupport::VERSION::STRING+ which returns the current version of ActiveSupport.
@@ -491,7 +488,6 @@ require "rails"
active_record
action_controller
action_mailer
- active_resource
rails/test_unit
).each do |framework|
begin
@@ -525,19 +521,19 @@ silence_warnings do
end
</ruby>
-These methods can be used to silence STDERR responses and the +silence_stream+ allows you to also silence other streams. Additionally, this mixin allows you to suppress exceptions and capture streams. For more information see the "Silencing Warnings, Streams, and Exceptions":http://guides.rubyonrails.org/active_support_core_extensions.html#silencing-warnings-streams-and-exceptions section from the Active Support Core Extensions Guide.
+These methods can be used to silence STDERR responses and the +silence_stream+ allows you to also silence other streams. Additionally, this mixin allows you to suppress exceptions and capture streams. For more information see the "Silencing Warnings, Streams, and Exceptions":active_support_core_extensions.html#silencing-warnings-streams-and-exceptions section from the Active Support Core Extensions Guide.
h4. +active_support/core_ext/logger.rb+
The next file that is required is another Active Support core extension, this time to the +Logger+ class. This begins by defining the +around_[level]+ helpers for the +Logger+ class as well as other methods such as a +datetime_format+ getter and setter for the +formatter+ object tied to a +Logger+ object.
-For more information see the "Extensions to Logger":http://guides.rubyonrails.org/active_support_core_extensions.html#extensions-to-logger section from the Active Support Core Extensions Guide.
+For more information see the "Extensions to Logger":active_support_core_extensions.html#extensions-to-logger section from the Active Support Core Extensions Guide.
h4. +railties/lib/rails/application.rb+
The next file required by +railties/lib/rails.rb+ is +application.rb+. This file defines the +Rails::Application+ constant which the application's class defined in +config/application.rb+ in a standard Rails application depends on. Before the +Rails::Application+ class is defined however, there's some other files that get required first.
-The first of these is +active_support/core_ext/hash/reverse_merge+ which can be "read about in the Active Support Core Extensions guide":http://guides.rubyonrails.org/active_support_core_extensions.html#merging under the "Merging" section.
+The first of these is +active_support/core_ext/hash/reverse_merge+ which can be "read about in the Active Support Core Extensions guide":active_support_core_extensions.html#merging under the "Merging" section.
h4. +active_support/file_update_checker.rb+
@@ -575,7 +571,7 @@ Now that +rails/initializable.rb+ has finished being required from +rails/railti
h4. +railties/lib/rails/configuration.rb+
-This file defines the +Rails::Configuration+ module, containing the +MiddlewareStackProxy+ class as well as the +Generators+ class. The +MiddlewareStackProxy+ class is used for managing the middleware stack for an application, which we'll see later on. The +Generators+ class provides the functionality used for configuring what generators an application uses through the "+config.generators+ option":http://guides.rubyonrails.org/configuring.html#configuring-generators.
+This file defines the +Rails::Configuration+ module, containing the +MiddlewareStackProxy+ class as well as the +Generators+ class. The +MiddlewareStackProxy+ class is used for managing the middleware stack for an application, which we'll see later on. The +Generators+ class provides the functionality used for configuring what generators an application uses through the "+config.generators+ option":configuring.html#configuring-generators.
The first file required in this file is +activesupport/deprecation+.
@@ -598,11 +594,11 @@ This file defines the +ActiveSupport::Notifications+ module. Notifications provi
The "API documentation":http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html for +ActiveSupport::Notifications+ explains the usage of this module, including the methods that it defines.
-The file required in +active_support/notifications.rb+ is +active_support/core_ext/module/delegation+ which is documented in the "Active Support Core Extensions Guide":http://guides.rubyonrails.org/active_support_core_extensions.html#method-delegation.
+The file required in +active_support/notifications.rb+ is +active_support/core_ext/module/delegation+ which is documented in the "Active Support Core Extensions Guide":active_support_core_extensions.html#method-delegation.
h4. +activesupport/core_ext/array/wrap+
-As this file comprises of a core extension, it is covered exclusively in "the Active Support Core Extensions guide":http://guides.rubyonrails.org/active_support_core_extensions.html#wrapping
+As this file comprises of a core extension, it is covered exclusively in "the Active Support Core Extensions guide":active_support_core_extensions.html#wrapping
h4. +activesupport/lib/active_support/deprecation/reporting.rb+
@@ -624,7 +620,7 @@ h4. +active_support/ordered_options+
This file is the next file required from +rails/configuration.rb+ is the file that defines +ActiveSupport::OrderedOptions+ which is used for configuration options such as +config.active_support+ and the like.
-The next file required is +active_support/core_ext/hash/deep_dup+ which is covered in "Active Support Core Extensions guide":http://guides.rubyonrails.org/active_support_core_extensions.html#deep_dup
+The next file required is +active_support/core_ext/hash/deep_dup+ which is covered in "Active Support Core Extensions guide":active_support_core_extensions.html#deep_dup
The file that is required next from is +rails/paths+
@@ -682,7 +678,7 @@ When the module from this file (+Rails::Initializable+) is included, it extends
h4. +railties/lib/rails/engine.rb+
-The next file required in +rails/engine.rb+ is +active_support/core_ext/module/delegation+ which is documented in the "Active Support Core Extensions Guide":http://guides.rubyonrails.org/active_support_core_extensions.html#method-delegation.
+The next file required in +rails/engine.rb+ is +active_support/core_ext/module/delegation+ which is documented in the "Active Support Core Extensions Guide":active_support_core_extensions.html#method-delegation.
The next two files after this are Ruby standard library files: +pathname+ and +rbconfig+. The file after these is +rails/engine/railties+.
@@ -698,7 +694,7 @@ Once this file has finished loading we jump back to +railties/lib/rails/plugin.r
h4. Back to +railties/lib/rails/plugin.rb+
-The next file required in this is a core extension from Active Support called +array/conversions+ which is covered in "this section":http://guides.rubyonrails.org/active_support_core_extensions.html#array-conversions of the Active Support Core Extensions Guide.
+The next file required in this is a core extension from Active Support called +array/conversions+ which is covered in "this section":active_support_core_extensions.html#array-conversions of the Active Support Core Extensions Guide.
Once that file has finished loading, the +Rails::Plugin+ class is defined.
@@ -817,7 +813,7 @@ def initializer(name, opts = {}, &blk)
end
</ruby>
-An initializer can be configured to run before or after another initializer, which we'll see a couple of times throughout this initialization process. Anything that inherits from +Rails::Railtie+ may also make use of the +initializer+ method, something which is covered in the "Configuration guide":[http://ryanbigg.com/guides/configuring.html#rails-railtie-initializer].
+An initializer can be configured to run before or after another initializer, which we'll see a couple of times throughout this initialization process. Anything that inherits from +Rails::Railtie+ may also make use of the +initializer+ method, something which is covered in the "Configuration guide":configuring.html#rails-railtie-initializer.
The +Initializer+ class here is defined within the +Rails::Initializable+ module and its +initialize+ method is defined to just set up a couple of variables:
diff --git a/guides/source/kindle/KINDLE.md b/guides/source/kindle/KINDLE.md
new file mode 100644
index 0000000000..a7d9a4e4cf
--- /dev/null
+++ b/guides/source/kindle/KINDLE.md
@@ -0,0 +1,26 @@
+# Rails Guides on the Kindle
+
+
+## Synopsis
+
+ 1. Obtain `kindlegen` from the link below and put the binary in your path
+ 2. Run `KINDLE=1 rake generate_guides` to generate the guides and compile the `.mobi` file
+ 3. Copy `output/kindle/rails_guides.mobi` to your Kindle
+
+## Resources
+
+ * [StackOverflow: Kindle Periodical Format](http://stackoverflow.com/questions/5379565/kindle-periodical-format)
+ * Example Periodical [.ncx](https://gist.github.com/808c971ed087b839d462) and [.opf](https://gist.github.com/d6349aa8488eca2ee6d0)
+ * [Kindle Publishing guidelines](http://kindlegen.s3.amazonaws.com/AmazonKindlePublishingGuidelines.pdf)
+ * [KindleGen & Kindle Previewer](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000234621)
+
+## TODO
+
+### Post release
+
+ * Integrate generated Kindle document in to published HTML guides
+ * Tweak heading styles (most docs use h3/h4/h5, which end up being smaller than the text under it)
+ * Tweak table styles (smaller text? Many of the tables are unusable on a Kindle in portrait mode)
+ * Have the HTML/XML TOC 'drill down' into the TOCs of the individual guides
+ * `.epub` generation.
+
diff --git a/guides/source/kindle/copyright.html.erb b/guides/source/kindle/copyright.html.erb
new file mode 100644
index 0000000000..bd51d87383
--- /dev/null
+++ b/guides/source/kindle/copyright.html.erb
@@ -0,0 +1 @@
+<%= render 'license' %> \ No newline at end of file
diff --git a/guides/source/kindle/layout.html.erb b/guides/source/kindle/layout.html.erb
new file mode 100644
index 0000000000..f0a286210b
--- /dev/null
+++ b/guides/source/kindle/layout.html.erb
@@ -0,0 +1,27 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+
+<title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title>
+
+<link rel="stylesheet" type="text/css" href="stylesheets/kindle.css" />
+
+</head>
+<body class="guide">
+
+ <% if content_for? :header_section %>
+ <%= yield :header_section %>
+ <div class="pagebreak">
+ <% end %>
+
+ <% if content_for? :index_section %>
+ <%= yield :index_section %>
+ <div class="pagebreak">
+ <% end %>
+
+ <%= yield.html_safe %>
+</body>
+</html>
diff --git a/guides/source/kindle/rails_guides.opf.erb b/guides/source/kindle/rails_guides.opf.erb
new file mode 100644
index 0000000000..4e07664fd0
--- /dev/null
+++ b/guides/source/kindle/rails_guides.opf.erb
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="RailsGuides">
+<metadata>
+ <meta name="cover" content="cover" />
+ <dc-metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
+
+ <dc:title>Ruby on Rails Guides (<%= @version %>)</dc:title>
+
+ <dc:language>en-us</dc:language>
+ <dc:creator>Ruby on Rails</dc:creator>
+ <dc:publisher>Ruby on Rails</dc:publisher>
+ <dc:subject>Reference</dc:subject>
+ <dc:date><%= Time.now.strftime('%Y-%m-%d') %></dc:date>
+
+ <dc:description>These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.</dc:description>
+ </dc-metadata>
+ <x-metadata>
+ <output content-type="application/x-mobipocket-subscription-magazine" encoding="utf-8"/>
+ </x-metadata>
+</metadata>
+
+<manifest>
+ <!-- HTML content files [mandatory] -->
+ <% documents_flat.each do |document| %>
+ <item id="<%= document['url'] %>" media-type="text/html" href="<%= document['url'] %>" />
+ <% end %>
+
+ <% %w{toc.html credits.html welcome.html copyright.html}.each do |url| %>
+ <item id="<%= url %>" media-type="text/html" href="<%= url %>" />
+ <% end %>
+
+ <item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx" />
+
+ <item id="cover" media-type="image/jpeg" href="images/rails_guides_kindle_cover.jpg"/>
+</manifest>
+
+<spine toc="toc">
+ <itemref idref="toc.html" />
+ <itemref idref="welcome.html" />
+ <itemref idref="credits.html" />
+ <itemref idref="copyright.html" />
+ <% documents_flat.each do |document| %>
+ <itemref idref="<%= document['url'] %>" />
+ <% end %>
+</spine>
+
+<guide>
+ <reference type="toc" title="Table of Contents" href="toc.html"></reference>
+</guide>
+
+</package>
diff --git a/guides/source/kindle/toc.html.erb b/guides/source/kindle/toc.html.erb
new file mode 100644
index 0000000000..e013797dee
--- /dev/null
+++ b/guides/source/kindle/toc.html.erb
@@ -0,0 +1,24 @@
+<% content_for :page_title do %>
+Ruby on Rails Guides
+<% end %>
+
+<h1>Table of Contents</h1>
+<div id="toc">
+ <ul><li><a href="welcome.html">Welcome</a></li></ul>
+<% documents_by_section.each_with_index do |section, i| %>
+ <h3><%= "#{i + 1}." %> <%= section['name'] %></h3>
+ <ul>
+ <% section['documents'].each do |document| %>
+ <li>
+ <a href="<%= document['url'] %>"><%= document['name'] %></a>
+ <% if document['work_in_progress']%>(WIP)<% end %>
+ </li>
+ <% end %>
+ </ul>
+<% end %>
+<hr />
+<ul>
+ <li><a href="credits.html">Credits</a></li>
+ <li><a href="copyright.html">Copyright &amp; License</a></li>
+<ul>
+</div>
diff --git a/guides/source/kindle/toc.ncx.erb b/guides/source/kindle/toc.ncx.erb
new file mode 100644
index 0000000000..2c6d8e3bdf
--- /dev/null
+++ b/guides/source/kindle/toc.ncx.erb
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN"
+ "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
+
+<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="en-US">
+<head>
+ <meta name="dtb:uid" content="RailsGuides"/>
+ <meta name="dtb:depth" content="2"/>
+ <meta name="dtb:totalPageCount" content="0"/>
+ <meta name="dtb:maxPageNumber" content="0"/>
+</head>
+<docTitle><text>Ruby on Rails Guides</text></docTitle>
+<docAuthor><text>docrails</text></docAuthor>
+<navMap>
+ <navPoint playOrder="0" class="periodical" id="periodical">
+ <navLabel>
+ <text>Table of Contents</text>
+ </navLabel>
+ <content src="toc.html"/>
+
+ <navPoint class="section" id="welcome" playOrder="1">
+ <navLabel>
+ <text>Introduction</text>
+ </navLabel>
+ <content src="welcome.html"/>
+
+ <navPoint class="article" id="welcome" playOrder="2">
+ <navLabel>
+ <text>Welcome</text>
+ </navLabel>
+ <content src="welcome.html"/>
+ </navPoint>
+ <navPoint class="article" id="credits" playOrder="3">
+ <navLabel><text>Credits</text></navLabel>
+ <content src="credits.html">
+ </navPoint>
+ <navPoint class="article" id="copyright" playOrder="4">
+ <navLabel><text>Copyright &amp; License</text></navLabel>
+ <content src="copyright.html">
+ </navPoint>
+ </navPoint>
+
+ <% play_order = 4 %>
+ <% documents_by_section.each_with_index do |section, section_no| %>
+ <navPoint class="section" id="chapter_<%= section_no + 1 %>" playOrder="<% play_order +=1 %>">
+ <navLabel>
+ <text><%= section['name'] %></text>
+ </navLabel>
+ <content src="<%=section['documents'].first['url'] %>"/>
+
+ <% section['documents'].each_with_index do |document, document_no| %>
+ <navPoint class="article" id="_<%=section_no+1%>.<%=document_no+1%>" playOrder="<%=play_order +=1 %>">
+ <navLabel>
+ <text><%= document['name'] %></text>
+ </navLabel>
+ <content src="<%=document['url'] %>"/>
+ </navPoint>
+ <% end %>
+ </navPoint>
+ <% end %>
+
+ </navPoint>
+</navMap>
+</ncx>
diff --git a/guides/source/kindle/welcome.html.erb b/guides/source/kindle/welcome.html.erb
new file mode 100644
index 0000000000..e30704c4e6
--- /dev/null
+++ b/guides/source/kindle/welcome.html.erb
@@ -0,0 +1,5 @@
+<%= render 'welcome' %>
+
+<h3>Kindle Edition</h3>
+
+The Kindle Edition of the Rails Guides should be considered a work in progress. Feedback is really welcome, please see the "Feedback" section at the end of each guide for instructions.
diff --git a/railties/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 4c979888b7..0a8daf7ae5 100644
--- a/railties/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -14,6 +14,8 @@
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />
+
+<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
</head>
<body class="guide">
<% if @edge %>
@@ -38,57 +40,21 @@
<div id="header">
<div class="wrapper clearfix">
<h1><a href="index.html" title="Return to home page">Guides.rubyonrails.org</a></h1>
- <p class="hide"><a href="#mainCol">Skip navigation</a>.</p>
<ul class="nav">
<li><a href="index.html">Home</a></li>
<li class="index"><a href="index.html" onclick="guideMenu(); return false;" id="guidesMenu">Guides Index</a>
<div id="guides" class="clearfix" style="display: none;">
<hr />
- <dl class="L">
- <dt>Start Here</dt>
- <dd><a href="getting_started.html">Getting Started with Rails</a></dd>
- <dt>Models</dt>
- <dd><a href="migrations.html">Rails Database Migrations</a></dd>
- <dd><a href="active_record_validations_callbacks.html">Active Record Validations and Callbacks</a></dd>
- <dd><a href="association_basics.html">Active Record Associations</a></dd>
- <dd><a href="active_record_querying.html">Active Record Query Interface</a></dd>
- <dt>Views</dt>
- <dd><a href="layouts_and_rendering.html">Layouts and Rendering in Rails</a></dd>
- <dd><a href="form_helpers.html">Action View Form Helpers</a></dd>
- <dt>Controllers</dt>
- <dd><a href="action_controller_overview.html">Action Controller Overview</a></dd>
- <dd><a href="routing.html">Rails Routing from the Outside In</a></dd>
- </dl>
- <dl class="R">
- <dt>Digging Deeper</dt>
- <dd><a href="active_support_core_extensions.html">Active Support Core Extensions</a></dd>
- <dd><a href="i18n.html">Rails Internationalization API</a></dd>
- <dd><a href="action_mailer_basics.html">Action Mailer Basics</a></dd>
- <dd><a href="testing.html">Testing Rails Applications</a></dd>
- <dd><a href="security.html">Securing Rails Applications</a></dd>
- <dd><a href="debugging_rails_applications.html">Debugging Rails Applications</a></dd>
- <dd><a href="performance_testing.html">Performance Testing Rails Applications</a></dd>
- <dd><a href="configuring.html">Configuring Rails Applications</a></dd>
- <dd><a href="command_line.html">Rails Command Line Tools and Rake Tasks</a></dd>
- <dd><a href="caching_with_rails.html">Caching with Rails</a></dd>
- <dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
-
- <dt>Extending Rails</dt>
- <dd><a href="plugins.html">The Basics of Creating Rails Plugins</a></dd>
- <dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
- <dd><a href="generators.html">Creating and Customizing Rails Generators</a></dd>
-
- <dt>Contributing to Ruby on Rails</dt>
- <dd><a href="contributing_to_ruby_on_rails.html">Contributing to Ruby on Rails</a></dd>
- <dd><a href="api_documentation_guidelines.html">API Documentation Guidelines</a></dd>
- <dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails Guides Guidelines</a></dd>
-
- <dt>Release Notes</dt>
- <dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 Release Notes</a></dd>
- <dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 Release Notes</a></dd>
- <dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 Release Notes</a></dd>
- <dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 Release Notes</a></dd>
- </dl>
+ <% ['L', 'R'].each do |position| %>
+ <dl class="<%= position %>">
+ <% docs_for_menu(position).each do |section| %>
+ <dt><%= section['name'] %></dt>
+ <% finished_documents(section['documents']).each do |document| %>
+ <dd><a href="<%= document['url'] %>"><%= document['name'] %></a></dd>
+ <% end %>
+ <% end %>
+ </dl>
+ <% end %>
</div>
</li>
<li><a href="contributing_to_ruby_on_rails.html">Contribute</a></li>
@@ -143,8 +109,7 @@
<hr class="hide" />
<div id="footer">
<div class="wrapper">
- <p>This work is licensed under a <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution-Share Alike 3.0</a> License</p>
- <p>"Rails", "Ruby on Rails", and the Rails logo are trademarks of David Heinemeier Hansson. All rights reserved.</p>
+ <%= render 'license' %>
</div>
</div>
diff --git a/railties/guides/source/layouts_and_rendering.textile b/guides/source/layouts_and_rendering.textile
index 69ef05104c..b0a87a5981 100644
--- a/railties/guides/source/layouts_and_rendering.textile
+++ b/guides/source/layouts_and_rendering.textile
@@ -78,16 +78,16 @@ If we want to display the properties of all the books in our view, we can do so
<tr>
<td><%= book.title %></td>
<td><%= book.content %></td>
- <td><%= link_to 'Show', book %></td>
- <td><%= link_to 'Edit', edit_book_path(book) %></td>
- <td><%= link_to 'Remove', book, :confirm => 'Are you sure?', :method => :delete %></td>
+ <td><%= link_to "Show", book %></td>
+ <td><%= link_to "Edit", edit_book_path(book) %></td>
+ <td><%= link_to "Remove", book, :confirm => "Are you sure?", :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
-<%= link_to 'New book', new_book_path %>
+<%= 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. Beginning with Rails 2, the standard extensions are +.erb+ for ERB (HTML with embedded Ruby), and +.builder+ for Builder (XML generator).
@@ -177,13 +177,13 @@ h5. Rendering an Action's Template from Another Controller
What if you want to render a template from an entirely different controller from the one that contains the action code? You can also do that with +render+, which accepts the full path (relative to +app/views+) of the template to render. For example, if you're running code in an +AdminProductsController+ that lives in +app/controllers/admin+, you can render the results of an action to a template in +app/views/products+ this way:
<ruby>
-render 'products/show'
+render "products/show"
</ruby>
Rails knows that this view belongs to a different controller because of the embedded slash character in the string. If you want to be explicit, you can use the +:template+ option (which was required on Rails 2.2 and earlier):
<ruby>
-render :template => 'products/show'
+render :template => "products/show"
</ruby>
h5. Rendering an Arbitrary File
@@ -216,18 +216,18 @@ In fact, in the BooksController class, inside of the update action where we want
<ruby>
render :edit
render :action => :edit
-render 'edit'
-render 'edit.html.erb'
-render :action => 'edit'
-render :action => 'edit.html.erb'
-render 'books/edit'
-render 'books/edit.html.erb'
-render :template => 'books/edit'
-render :template => 'books/edit.html.erb'
-render '/path/to/rails/app/views/books/edit'
-render '/path/to/rails/app/views/books/edit.html.erb'
-render :file => '/path/to/rails/app/views/books/edit'
-render :file => '/path/to/rails/app/views/books/edit.html.erb'
+render "edit"
+render "edit.html.erb"
+render :action => "edit"
+render :action => "edit.html.erb"
+render "books/edit"
+render "books/edit.html.erb"
+render :template => "books/edit"
+render :template => "books/edit.html.erb"
+render "/path/to/rails/app/views/books/edit"
+render "/path/to/rails/app/views/books/edit.html.erb"
+render :file => "/path/to/rails/app/views/books/edit"
+render :file => "/path/to/rails/app/views/books/edit.html.erb"
</ruby>
Which one you use is really a matter of style and convention, but the rule of thumb is to use the simplest one that makes sense for the code you are writing.
@@ -306,7 +306,7 @@ h6. The +:content_type+ Option
By default, Rails will serve the results of a rendering operation with the MIME content-type of +text/html+ (or +application/json+ if you use the +:json+ option, or +application/xml+ for the +:xml+ option.). There are times when you might like to change this, and you can do so by setting the +:content_type+ option:
<ruby>
-render :file => filename, :content_type => 'application/rss'
+render :file => filename, :content_type => "application/rss"
</ruby>
h6. The +:layout+ Option
@@ -316,7 +316,7 @@ With most of the options to +render+, the rendered content is displayed as part
You can use the +:layout+ option to tell Rails to use a specific file as the layout for the current action:
<ruby>
-render :layout => 'special_layout'
+render :layout => "special_layout"
</ruby>
You can also tell Rails to render with no layout at all:
@@ -334,7 +334,7 @@ render :status => 500
render :status => :forbidden
</ruby>
-Rails understands both numeric status codes and symbols for status codes.
+Rails understands both numeric and symbolic status codes.
h6. The +:location+ Option
@@ -348,9 +348,9 @@ h5. Finding Layouts
To find the current layout, Rails first looks for a file in +app/views/layouts+ with the same base name as the controller. For example, rendering actions from the +PhotosController+ class will use +app/views/layouts/photos.html.erb+ (or +app/views/layouts/photos.builder+). If there is no such controller-specific layout, Rails will use +app/views/layouts/application.html.erb+ or +app/views/layouts/application.builder+. If there is no +.erb+ layout, Rails will use a +.builder+ layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions.
-h6. Specifying Layouts on a per-Controller Basis
+h6. Specifying Layouts for Controllers
-You can override the automatic layout conventions in your controllers by using the +layout+ declaration in the controller. For example:
+You can override the default layout conventions in your controllers by using the +layout+ declaration. For example:
<ruby>
class ProductsController < ApplicationController
@@ -359,9 +359,9 @@ class ProductsController < ApplicationController
end
</ruby>
-With this declaration, all methods within +ProductsController+ will use +app/views/layouts/inventory.html.erb+ for their layout.
+With this declaration, all of the views rendered by the products controller will use +app/views/layouts/inventory.html.erb+ as their layout.
-To assign a specific layout for the entire application, use a declaration in your +ApplicationController+ class:
+To assign a specific layout for the entire application, use a +layout+ declaration in your +ApplicationController+ class:
<ruby>
class ApplicationController < ActionController::Base
@@ -370,7 +370,7 @@ class ApplicationController < ActionController::Base
end
</ruby>
-With this declaration, all views in the entire application will use +app/views/layouts/main.html.erb+ for their layout.
+With this declaration, all of the views in the entire application will use +app/views/layouts/main.html.erb+ for their layout.
h6. Choosing Layouts at Runtime
@@ -378,7 +378,7 @@ You can use a symbol to defer the choice of layout until a request is processed:
<ruby>
class ProductsController < ApplicationController
- layout :products_layout
+ layout "products_layout"
def show
@product = Product.find(params[:id])
@@ -392,19 +392,19 @@ class ProductsController < ApplicationController
end
</ruby>
-Now, if the current user is a special user, they'll get a special layout when viewing a product. You can even use an inline method to determine the layout:
+Now, if the current user is a special user, they'll get a special layout when viewing a product.
-You can also decide the layout by passing a Proc object, the block you give the Proc will be given the +controller+ instance, so you can make decisions based on the current request. For example:
+You can even use an inline method, such as a Proc, to determine the layout. For example, if you pass a Proc object, the block you give the Proc will be given the +controller+ instance, so the layout can be determined based on the current request:
<ruby>
class ProductsController < ApplicationController
- layout Proc.new { |controller| controller.request.xhr? ? 'popup' : 'application' }
+ layout Proc.new { |controller| controller.request.xhr? ? "popup" : "application" }
end
</ruby>
h6. Conditional Layouts
-Layouts specified at the controller level support +:only+ and +:except+ options that take either a method name or an array of method names which correspond to method names within the controller:
+Layouts specified at the controller level support the +:only+ and +:except+ options. These options take either a method name, or an array of method names, corresponding to method names within the controller:
<ruby>
class ProductsController < ApplicationController
@@ -416,7 +416,7 @@ With this declaration, the +product+ layout would be used for everything but the
h6. Layout Inheritance
-Layouts are shared downwards in the hierarchy, and more specific layouts always override more general ones. For example:
+Layout declarations cascade downward in the hierarchy, and more specific layout declarations always override more general ones. For example:
* +application_controller.rb+
@@ -445,7 +445,7 @@ end
<ruby>
class OldPostsController < SpecialPostsController
- layout nil
+ layout false
def show
@post = Post.find(params[:id])
@@ -495,9 +495,9 @@ def show
end
</ruby>
-Make sure you use +and return+ and not +&amp;&amp; return+ because while the former will work, the latter will not due to operator precedence in the Ruby Language.
+Make sure to use +and return+ instead of +&amp;&amp; return+ because +&amp;&amp; return+ will not work due to the operator precedence in the Ruby Language.
-Note that the implicit render done by ActionController detects if +render+ has been called, and thus avoids this error. Therefore, the following will work without errors:
+Note that the implicit render done by ActionController detects if +render+ has been called, so the following will work without errors:
<ruby>
def show
@@ -518,7 +518,7 @@ Another way to handle returning responses to an HTTP request is with +redirect_t
redirect_to photos_url
</ruby>
-You can use +redirect_to+ with any arguments that you could use with +link_to+ or +url_for+. In addition, there's a special redirect that sends the user back to the page they just came from:
+You can use +redirect_to+ with any arguments that you could use with +link_to+ or +url_for+. There's also a special redirect that sends the user back to the page they just came from:
<ruby>
redirect_to :back
@@ -526,7 +526,7 @@ redirect_to :back
h5. Getting a Different Redirect Status Code
-Rails uses HTTP status code 302 (temporary redirect) when you call +redirect_to+. If you'd like to use a different status code (perhaps 301, permanent redirect), you can do so by using the +:status+ option:
+Rails uses HTTP status code 302, a temporary redirect, when you call +redirect_to+. If you'd like to use a different status code, perhaps 301, a permanent redirect, you can use the +:status+ option:
<ruby>
redirect_to photos_path, :status => 301
@@ -536,7 +536,7 @@ Just like the +:status+ option for +render+, +:status+ for +redirect_to+ accepts
h5. The Difference Between +render+ and +redirect_to+
-Sometimes inexperienced developers conceive of +redirect_to+ as a sort of +goto+ command, moving execution from one place to another in your Rails code. This is _not_ correct. Your code stops running and waits for a new request for the browser. It just happens that you've told the browser what request it should make next, by sending back an HTTP 302 status code.
+Sometimes inexperienced developers think of +redirect_to+ as a sort of +goto+ command, moving execution from one place to another in your Rails code. This is _not_ correct. Your code stops running and waits for a new request for the browser. It just happens that you've told the browser what request it should make next, by sending back an HTTP 302 status code.
Consider these actions to see the difference:
@@ -553,7 +553,7 @@ def show
end
</ruby>
-With the code in this form, there will likely be a problem if the +@book+ variable is +nil+. Remember, a +render :action+ doesn't run any code in the target action, so nothing will set up the +@books+ variable that the +index+ view is presumably depending on. One way to fix this is to redirect instead of rendering:
+With the code in this form, there will likely be a problem if the +@book+ variable is +nil+. Remember, a +render :action+ doesn't run any code in the target action, so nothing will set up the +@books+ variable that the +index+ view will probably require. One way to fix this is to redirect instead of rendering:
<ruby>
def index
@@ -570,9 +570,9 @@ end
With this code, the browser will make a fresh request for the index page, the code in the +index+ method will run, and all will be well.
-The only downside to this code, is that it requires a round trip to the browser, the browser requested the show action with +/books/1+ and the controller finds that there are no books, so the controller sends out a 302 redirect response to the browser telling it to go to +/books/+, the browser complies and sends a new request back to the controller asking now for the +index+ action, the controller then gets all the books in the database and renders the index template, sending it back down to the browser which then shows it on your screen.
+The only downside to this code is that it requires a round trip to the browser: the browser requested the show action with +/books/1+ and the controller finds that there are no books, so the controller sends out a 302 redirect response to the browser telling it to go to +/books/+, the browser complies and sends a new request back to the controller asking now for the +index+ action, the controller then gets all the books in the database and renders the index template, sending it back down to the browser which then shows it on your screen.
-While in a small app, this added latency might not be a problem, it is something to think about when speed of response is of the essence. One way to handle this double request (though a contrived example) could be:
+While in a small application, this added latency might not be a problem, it is something to think about if response time is a concern. We can demonstrate one way to handle this with a contrived example:
<ruby>
def index
@@ -583,12 +583,12 @@ def show
@book = Book.find_by_id(params[:id])
if @book.nil?
@books = Book.all
- render "index", :alert => 'Your book was not found!'
+ render "index", :alert => "Your book was not found!"
end
end
</ruby>
-Which would detect that there are no books, populate the +@books+ instance variable with all the books in the database and then directly render the +index.html.erb+ template returning it to the browser with a flash alert message telling the user what happened.
+This would detect that there are no books with the specified ID, populate the +@books+ instance variable with all the books in the model, and then directly render the +index.html.erb+ template, returning it to the browser with a flash alert message to tell the user what happened.
h4. Using +head+ To Build Header-Only Responses
@@ -598,7 +598,7 @@ The +head+ method can be used to send responses with only headers to the browser
head :bad_request
</ruby>
-Which would produce the following header:
+This would produce the following header:
<shell>
HTTP/1.1 400 Bad Request
@@ -611,7 +611,7 @@ Set-Cookie: _blog_session=...snip...; path=/; HttpOnly
Cache-Control: no-cache
</shell>
-Or you can use other HTTP headers to convey additional information:
+Or you can use other HTTP headers to convey other information:
<ruby>
head :created, :location => photo_path(@photo)
@@ -633,15 +633,15 @@ Cache-Control: no-cache
h3. Structuring Layouts
-When Rails renders a view as a response, it does so by combining the view with the current layout (using the rules for finding the current layout that were covered earlier in this guide). Within a layout, you have access to three tools for combining different bits of output to form the overall response:
+When Rails renders a view as a response, it does so by combining the view with the current layout, using the rules for finding the current layout that were covered earlier in this guide. Within a layout, you have access to three tools for combining different bits of output to form the overall response:
* Asset tags
* +yield+ and +content_for+
* Partials
-h4. Asset Tags
+h4. Asset Tag Helpers
-Asset tags provide methods for generating HTML that links views to feeds, JavaScript, stylesheets, images, videos and audios. These are the six asset tags available in Rails:
+Asset tag helpers provide methods for generating HTML that link views to feeds, JavaScript, stylesheets, images, videos and audios. There are six asset tag helpers available in Rails:
* +auto_discovery_link_tag+
* +javascript_include_tag+
@@ -650,40 +650,54 @@ Asset tags provide methods for generating HTML that links views to feeds, JavaSc
* +video_tag+
* +audio_tag+
-You can use these tags in layouts or other views, although the tags other than +image_tag+ are most commonly used in the +&lt;head&gt;+ section of a layout.
+You can use these tags in layouts or other views, although the +auto_discovery_link_tag+, +javascript_include_tag+, and +stylesheet_link_tag+, are most commonly used in the +&lt;head&gt;+ section of a layout.
-WARNING: The asset tags do _not_ verify the existence of the assets at the specified locations; they simply assume that you know what you're doing and generate the link.
+WARNING: The asset tag helpers do _not_ verify the existence of the assets at the specified locations; they simply assume that you know what you're doing and generate the link.
-h5. Linking to Feeds with +auto_discovery_link_tag+
+h5. Linking to Feeds with the +auto_discovery_link_tag+
-The +auto_discovery_link_tag+ helper builds HTML that most browsers and newsreaders can use to detect the presences of RSS or ATOM feeds. It takes the type of the link (+:rss+ or +:atom+), a hash of options that are passed through to url_for, and a hash of options for the tag:
+The +auto_discovery_link_tag+ helper builds HTML that most browsers and newsreaders can use to detect the presence of RSS or Atom feeds. It takes the type of the link (+:rss+ or +:atom+), a hash of options that are passed through to url_for, and a hash of options for the tag:
<erb>
<%= auto_discovery_link_tag(:rss, {:action => "feed"},
{:title => "RSS Feed"}) %>
</erb>
-There are three tag options available for +auto_discovery_link_tag+:
+There are three tag options available for the +auto_discovery_link_tag+:
-* +:rel+ specifies the +rel+ value in the link (defaults to "alternate")
+* +:rel+ specifies the +rel+ value in the link. The default value is "alternate".
* +:type+ specifies an explicit MIME type. Rails will generate an appropriate MIME type automatically.
-* +:title+ specifies the title of the link
+* +:title+ specifies the title of the link. The default value is the uppercased +:type+ value, for example, "ATOM" or "RSS".
-h5. Linking to JavaScript Files with +javascript_include_tag+
+h5. Linking to JavaScript Files with the +javascript_include_tag+
-The +javascript_include_tag+ helper returns an HTML +script+ tag for each source provided. Rails looks in +public/javascripts+ for these files by default, but you can specify a full path relative to the document root, or a URL, if you prefer. For example, to include +public/javascripts/main.js+:
+The +javascript_include_tag+ helper returns an HTML +script+ tag for each source provided.
+
+If you are using Rails with the "Asset Pipeline":asset_pipeline.html enabled, this helper will generate a link to +/assets/javascripts/+ rather than +public/javascripts+ which was used in earlier versions of Rails. This link is then served by the Sprockets gem, which was introduced in Rails 3.1.
+
+A JavaScript file within a Rails application or Rails engine goes in one of three locations: +app/assets+, +lib/assets+ or +vendor/assets+. These locations are explained in detail in the "Asset Organization section in the Asset Pipeline Guide":asset_pipeline.html#asset-organization
+
+You can specify a full path relative to the document root, or a URL, if you prefer. For example, to link to a JavaScript file that is inside a directory called +javascripts+ inside of one of +app/assets+, +lib/assets+ or +vendor/assets+, you would do this:
<erb>
<%= javascript_include_tag "main" %>
</erb>
-To include +public/javascripts/main.js+ and +public/javascripts/columns.js+:
+Rails will then output a +script+ tag such as this:
+
+<html>
+<script src='/assets/main.js'></script>
+</html>
+
+The request to this asset is then served by the Sprockets gem.
+
+To include multiple files such as +app/assets/javascripts/main.js+ and +app/assets/javascripts/columns.js+ at the same time:
<erb>
<%= javascript_include_tag "main", "columns" %>
</erb>
-To include +public/javascripts/main.js+ and +public/photos/columns.js+:
+To include +app/assets/javascripts/main.js+ and +app/assets/javascripts/photos/columns.js+:
<erb>
<%= javascript_include_tag "main", "/photos/columns" %>
@@ -701,15 +715,38 @@ If the application does not use the asset pipeline, the +:defaults+ option loads
<%= javascript_include_tag :defaults %>
</erb>
-And you can in any case override the expansion in <tt>config/application.rb</tt>:
+Outputting +script+ tags such as this:
+
+<html>
+<script src="/javascripts/jquery.js"></script>
+<script src="/javascripts/jquery_ujs.js"></script>
+</html>
+
+These two files for jQuery, +jquery.js+ and +jquery_ujs.js+ must be placed inside +public/javascripts+ if the application doesn't use the asset pipeline. These files can be downloaded from the "jquery-rails repository on GitHub":https://github.com/indirect/jquery-rails/tree/master/vendor/assets/javascripts
+
+WARNING: If you are using the asset pipeline, this tag will render a +script+ tag for an asset called +defaults.js+, which would not exist in your application unless you've explicitly created it.
+
+And you can in any case override the +:defaults+ 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.
+You can also define new defaults:
+
+<ruby>
+config.action_view.javascript_expansions[:projects] = %w(projects.js tickets.js)
+</ruby>
+
+And use them by referencing them exactly like +:defaults+:
-Also, the +:all+ option loads every JavaScript file in +public/javascripts+:
+<erb>
+<%= javascript_include_tag :projects %>
+</erb>
+
+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.
+
+Also, if the asset pipeline is disabled, the +:all+ expansion loads every JavaScript file in +public/javascripts+:
<erb>
<%= javascript_include_tag :all %>
@@ -733,26 +770,30 @@ By default, the combined file will be delivered as +javascripts/all.js+. You can
<erb>
<%= javascript_include_tag "main", "columns",
- :cache => 'cache/main/display' %>
+ :cache => "cache/main/display" %>
</erb>
You can even use dynamic paths such as +cache/#{current_site}/main/display+.
-h5. Linking to CSS Files with +stylesheet_link_tag+
+h5. Linking to CSS Files with the +stylesheet_link_tag+
+
+The +stylesheet_link_tag+ helper returns an HTML +&lt;link&gt;+ tag for each source provided.
+
+If you are using Rails with the "Asset Pipeline" enabled, this helper will generate a link to +/assets/stylesheets/+. This link is then processed by the Sprockets gem. A stylesheet file can be stored in one of three locations: +app/assets+, +lib/assets+ or +vendor/assets+.
-The +stylesheet_link_tag+ helper returns an HTML +&lt;link&gt;+ tag for each source provided. Rails looks in +public/stylesheets+ for these files by default, but you can specify a full path relative to the document root, or a URL, if you prefer. For example, to include +public/stylesheets/main.css+:
+You can specify a full path relative to the document root, or a URL. For example, to link to a stylesheet file that is inside a directory called +stylesheets+ inside of one of +app/assets+, +lib/assets+ or +vendor/assets+, you would do this:
<erb>
<%= stylesheet_link_tag "main" %>
</erb>
-To include +public/stylesheets/main.css+ and +public/stylesheets/columns.css+:
+To include +app/assets/stylesheets/main.css+ and +app/assets/stylesheets/columns.css+:
<erb>
<%= stylesheet_link_tag "main", "columns" %>
</erb>
-To include +public/stylesheets/main.css+ and +public/photos/columns.css+:
+To include +app/assets/stylesheets/main.css+ and +app/assets/stylesheets/photos/columns.css+:
<erb>
<%= stylesheet_link_tag "main", "/photos/columns" %>
@@ -764,13 +805,13 @@ To include +http://example.com/main.css+:
<%= stylesheet_link_tag "http://example.com/main.css" %>
</erb>
-By default, +stylesheet_link_tag+ creates links with +media="screen" rel="stylesheet" type="text/css"+. You can override any of these defaults by specifying an appropriate option (+:media+, +:rel+, or +:type+):
+By default, the +stylesheet_link_tag+ creates links with +media="screen" rel="stylesheet"+. You can override any of these defaults by specifying an appropriate option (+:media+, +:rel+):
<erb>
<%= stylesheet_link_tag "main_print", :media => "print" %>
</erb>
-The +all+ option links every CSS file in +public/stylesheets+:
+If the asset pipeline is disabled, the +all+ option links every CSS file in +public/stylesheets+:
<erb>
<%= stylesheet_link_tag :all %>
@@ -792,12 +833,12 @@ By default, the combined file will be delivered as +stylesheets/all.css+. You ca
<erb>
<%= stylesheet_link_tag "main", "columns",
- :cache => 'cache/main/display' %>
+ :cache => "cache/main/display" %>
</erb>
You can even use dynamic paths such as +cache/#{current_site}/main/display+.
-h5. Linking to Images with +image_tag+
+h5. Linking to Images with the +image_tag+
The +image_tag+ helper builds an HTML +&lt;img /&gt;+ tag to the specified file. By default, files are loaded from +public/images+.
@@ -819,12 +860,6 @@ You can supply a hash of additional HTML options:
<%= image_tag "icons/delete.gif", {:height => 45} %>
</erb>
-You can also supply an alternate image to show on mouseover:
-
-<erb>
-<%= image_tag "home.gif", :onmouseover => "menu/home_highlight.gif" %>
-</erb>
-
You can supply alternate text for the image which will be used if the user has images turned off in their browser. If you do not specify an alt text explicitly, it defaults to the file name of the file, capitalized and with no extension. For example, these two image tags would return the same code:
<erb>
@@ -843,10 +878,10 @@ In addition to the above special tags, you can supply a final hash of standard H
<erb>
<%= image_tag "home.gif", :alt => "Go Home",
:id => "HomeImage",
- :class => 'nav_bar' %>
+ :class => "nav_bar" %>
</erb>
-h5. Linking to Videos with +video_tag+
+h5. Linking to Videos with the +video_tag+
The +video_tag+ helper builds an HTML 5 +&lt;video&gt;+ tag to the specified file. By default, files are loaded from +public/videos+.
@@ -864,7 +899,7 @@ Like an +image_tag+ you can supply a path, either absolute, or relative to the +
The video tag also supports all of the +&lt;video&gt;+ HTML options through the HTML options hash, including:
-* +:poster => 'image_name.png'+, provides an image to put in place of the video before it starts playing.
+* +:poster => "image_name.png"+, provides an image to put in place of the video before it starts playing.
* +:autoplay => true+, starts playing the video on page load.
* +:loop => true+, loops the video once it gets to the end.
* +:controls => true+, provides browser supplied controls for the user to interact with the video.
@@ -882,7 +917,7 @@ This will produce:
<video><source src="trailer.ogg" /><source src="movie.ogg" /></video>
</erb>
-h5. Linking to Audio files with +audio_tag+
+h5. Linking to Audio Files with the +audio_tag+
The +audio_tag+ helper builds an HTML 5 +&lt;audio&gt;+ tag to the specified file. By default, files are loaded from +public/audios+.
@@ -933,7 +968,7 @@ You can also create a layout with multiple yielding regions:
The main body of the view will always render into the unnamed +yield+. To render content into a named +yield+, you use the +content_for+ method.
-h4. Using +content_for+
+h4. Using the +content_for+ Method
The +content_for+ method allows you to insert content into a named +yield+ block in your layout. For example, this view would work with the layout that you just saw:
@@ -1093,13 +1128,6 @@ In Rails 3.0, there is also a shorthand for this. Assuming +@products+ is a coll
Rails determines the name of the partial to use by looking at the model name in the collection. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection:
-In the event that the collection is empty, +render+ will return nil, so it should be fairly simple to provide alternative content.
-
-<erb>
-<h1>Products</h1>
-<%= render(@products) || 'There are no products available.' %>
-</erb>
-
* +index.html.erb+
<erb>
@@ -1121,6 +1149,13 @@ In the event that the collection is empty, +render+ will return nil, so it shoul
In this case, Rails will use the customer or employee partials as appropriate for each member of the collection.
+In the event that the collection is empty, +render+ will return nil, so it should be fairly simple to provide alternative content.
+
+<erb>
+<h1>Products</h1>
+<%= render(@products) || "There are no products available." %>
+</erb>
+
h5. Local Variables
To use a custom local variable name within the partial, specify the +:as+ option in the call to the partial:
@@ -1134,7 +1169,7 @@ With this change, you can access an instance of the +@products+ collection as th
You can also pass in arbitrary local variables to any partial you are rendering with the +:locals => {}+ option:
<erb>
-<%= render :partial => 'products', :collection => @products,
+<%= render :partial => "products", :collection => @products,
:as => :item, :locals => {:title => "Products Page"} %>
</erb>
@@ -1147,11 +1182,21 @@ You can also specify a second partial to be rendered between instances of the ma
h5. Spacer Templates
<erb>
-<%= render @products, :spacer_template => "product_ruler" %>
+<%= render :partial => @products, :spacer_template => "product_ruler" %>
</erb>
Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials.
+h5. Partial Layouts
+
+When rendering collections it is also possible to use the +:layout+ option:
+
+<erb>
+<%= render :partial => "product", :collection => @products, :layout => "special_layout" %>
+</erb>
+
+The layout will be rendered together with the partial for each item in the collection. The current object and object_counter variables will be available in the layout as well, the same way they do within the partial.
+
h4. Using Nested Layouts
You may find that your application requires a layout that differs slightly from your regular application layout to support one particular controller. Rather than repeating the main layout and editing it, you can accomplish this by using nested layouts (sometimes called sub-templates). Here's an example:
@@ -1163,9 +1208,9 @@ Suppose you have the following +ApplicationController+ layout:
<erb>
<html>
<head>
- <title><%= @page_title or 'Page Title' %></title>
- <%= stylesheet_link_tag 'layout' %>
- <style type="text/css"><%= yield :stylesheets %></style>
+ <title><%= @page_title or "Page Title" %></title>
+ <%= stylesheet_link_tag "layout" %>
+ <style><%= yield :stylesheets %></style>
</head>
<body>
<div id="top_menu">Top menu items here</div>
@@ -1188,7 +1233,7 @@ On pages generated by +NewsController+, you want to hide the top menu and add a
<div id="right_menu">Right menu items here</div>
<%= content_for?(:news_content) ? yield(:news_content) : yield %>
<% end %>
-<%= render :template => 'layouts/application' %>
+<%= render :template => "layouts/application" %>
</erb>
That's it. The News views will use the new layout, hiding the top menu and adding a new right menu inside the "content" div.
diff --git a/guides/source/migrations.textile b/guides/source/migrations.textile
new file mode 100644
index 0000000000..52dba76e68
--- /dev/null
+++ b/guides/source/migrations.textile
@@ -0,0 +1,932 @@
+h2. Migrations
+
+Migrations are a convenient way for you to alter your database in a structured
+and organized manner. You could edit fragments of SQL by hand but you would then
+be responsible for telling other developers that they need to go and run them.
+You'd also have to keep track of which changes need to be run against the
+production machines next time you deploy.
+
+Active Record tracks which migrations have already been run so all you have to
+do is update your source and run +rake db:migrate+. Active Record will work out
+which migrations should be run. It will also update your +db/schema.rb+ file to
+match the structure of your database.
+
+Migrations also allow you to describe these transformations using Ruby. The
+great thing about this is that (like most of Active Record's functionality) it
+is database independent: you don't need to worry about the precise syntax of
++CREATE TABLE+ any more than you worry about variations on +SELECT *+ (you can
+drop down to raw SQL for database specific features). For example you could use
+SQLite3 in development, but MySQL in production.
+
+In this guide, you'll learn all about migrations including:
+
+* The generators you can use to create them
+* The methods Active Record provides to manipulate your database
+* The Rake tasks that manipulate them
+* How they relate to +schema.rb+
+
+endprologue.
+
+h3. Anatomy of a Migration
+
+Before we dive into the details of a migration, here are a few examples of the
+sorts of things you can do:
+
+<ruby>
+class CreateProducts < ActiveRecord::Migration
+ def up
+ create_table :products do |t|
+ t.string :name
+ t.text :description
+
+ t.timestamps
+ end
+ end
+
+ def down
+ drop_table :products
+ end
+end
+</ruby>
+
+This migration adds a table called +products+ with a string column called +name+
+and a text column called +description+. A primary key column called +id+ will
+also be added, however since this is the default we do not need to explicitly specify it.
+The timestamp columns +created_at+ and +updated_at+ which Active Record
+populates automatically will also be added. Reversing this migration is as
+simple as dropping the table.
+
+Migrations are not limited to changing the schema. You can also use them to fix
+bad data in the database or populate new fields:
+
+<ruby>
+class AddReceiveNewsletterToUsers < ActiveRecord::Migration
+ def up
+ change_table :users do |t|
+ t.boolean :receive_newsletter, :default => false
+ end
+ User.update_all :receive_newsletter => true
+ end
+
+ def down
+ remove_column :users, :receive_newsletter
+ end
+end
+</ruby>
+
+NOTE: Some "caveats":#using-models-in-your-migrations apply to using models in
+your migrations.
+
+This migration adds a +receive_newsletter+ column to the +users+ table. We want
+it to default to +false+ for new users, but existing users are considered to
+have already opted in, so we use the User model to set the flag to +true+ for
+existing users.
+
+h4. Using the change method
+
+Rails 3.1 makes migrations smarter by providing a new <tt>change</tt> method.
+This method is preferred for writing constructive migrations (adding columns or
+tables). The migration knows how to migrate your database and reverse it when
+the migration is rolled back without the need to write a separate +down+ method.
+
+<ruby>
+class CreateProducts < ActiveRecord::Migration
+ def change
+ create_table :products do |t|
+ t.string :name
+ t.text :description
+
+ t.timestamps
+ end
+ end
+end
+</ruby>
+
+h4. Migrations are Classes
+
+A migration is a subclass of <tt>ActiveRecord::Migration</tt> that implements
+two methods: +up+ (perform the required transformations) and +down+ (revert
+them).
+
+Active Record provides methods that perform common data definition tasks in a
+database independent way (you'll read about them in detail later):
+
+* +add_column+
+* +add_index+
+* +change_column+
+* +change_table+
+* +create_table+
+* +create_join_table+
+* +drop_table+
+* +remove_column+
+* +remove_index+
+* +rename_column+
+
+If you need to perform tasks specific to your database (for example create a
+"foreign key":#active-record-and-referential-integrity constraint) then the
++execute+ method allows you to execute arbitrary SQL. A migration is just a
+regular Ruby class so you're not limited to these functions. For example after
+adding a column you could write code to set the value of that column for
+existing records (if necessary using your models).
+
+On databases that support transactions with statements that change the schema
+(such as PostgreSQL or SQLite3), migrations are wrapped in a transaction. If the
+database does not support this (for example MySQL) then when a migration fails
+the parts of it that succeeded will not be rolled back. You will have to rollback
+the changes that were made by hand.
+
+h4. What's in a Name
+
+Migrations are stored as files in the +db/migrate+ directory, one for each
+migration class. The name of the file is of the form
++YYYYMMDDHHMMSS_create_products.rb+, that is to say a UTC timestamp
+identifying the migration followed by an underscore followed by the name
+of the migration. The name of the migration class (CamelCased version)
+should match the latter part of the file name. For example
++20080906120000_create_products.rb+ should define class +CreateProducts+ and
++20080906120001_add_details_to_products.rb+ should define
++AddDetailsToProducts+. If you do feel the need to change the file name then you
+<em>have to</em> update the name of the class inside or Rails will complain
+about a missing class.
+
+Internally Rails only uses the migration's number (the timestamp) to identify
+them. Prior to Rails 2.1 the migration number started at 1 and was incremented
+each time a migration was generated. With multiple developers it was easy for
+these to clash requiring you to rollback migrations and renumber them. With
+Rails 2.1+ this is largely avoided by using the creation time of the migration
+to identify them. You can revert to the old numbering scheme by adding the
+following line to +config/application.rb+.
+
+<ruby>
+config.active_record.timestamped_migrations = false
+</ruby>
+
+The combination of timestamps and recording which migrations have been run
+allows Rails to handle common situations that occur with multiple developers.
+
+For example Alice adds migrations +20080906120000+ and +20080906123000+ and Bob
+adds +20080906124500+ and runs it. Alice finishes her changes and checks in her
+migrations and Bob pulls down the latest changes. When Bob runs +rake db:migrate+,
+Rails knows that it has not run Alice's two migrations so it executes the +up+ method for each migration.
+
+Of course this is no substitution for communication within the team. For
+example, if Alice's migration removed a table that Bob's migration assumed to
+exist, then trouble would certainly strike.
+
+h4. Changing Migrations
+
+Occasionally you will make a mistake when writing a migration. If you have
+already run the migration then you cannot just edit the migration and run the
+migration again: Rails thinks it has already run the migration and so will do
+nothing when you run +rake db:migrate+. You must rollback the migration (for
+example with +rake db:rollback+), edit your migration and then run +rake db:migrate+ to run the corrected version.
+
+In general editing existing migrations is not a good idea: you will be creating
+extra work for yourself and your co-workers and cause major headaches if the
+existing version of the migration has already been run on production machines.
+Instead, you should write a new migration that performs the changes you require.
+Editing a freshly generated migration that has not yet been committed to source
+control (or, more generally, which has not been propagated beyond your
+development machine) is relatively harmless.
+
+h4. Supported Types
+
+Active Record supports the following database column types:
+
+* +:binary+
+* +:boolean+
+* +:date+
+* +:datetime+
+* +:decimal+
+* +:float+
+* +:integer+
+* +:primary_key+
+* +:string+
+* +:text+
+* +:time+
+* +:timestamp+
+
+These will be mapped onto an appropriate underlying database type. For example,
+with MySQL the type +:string+ is mapped to +VARCHAR(255)+. You can create
+columns of types not supported by Active Record when using the non-sexy syntax,
+for example
+
+<ruby>
+create_table :products do |t|
+ t.column :name, 'polygon', :null => false
+end
+</ruby>
+
+This may however hinder portability to other databases.
+
+h3. Creating a Migration
+
+h4. Creating a Model
+
+The model and scaffold generators will create migrations appropriate for adding
+a new model. This migration will already contain instructions for creating the
+relevant table. If you tell Rails what columns you want, then statements for
+adding these columns will also be created. For example, running
+
+<shell>
+$ rails generate model Product name:string description:text
+</shell>
+
+will create a migration that looks like this
+
+<ruby>
+class CreateProducts < ActiveRecord::Migration
+ def change
+ create_table :products do |t|
+ t.string :name
+ t.text :description
+
+ t.timestamps
+ end
+ end
+end
+</ruby>
+
+You can append as many column name/type pairs as you want. By default, the
+generated migration will include +t.timestamps+ (which creates the
++updated_at+ and +created_at+ columns that are automatically populated
+by Active Record).
+
+h4. Creating a Standalone Migration
+
+If you are creating migrations for other purposes (for example to add a column
+to an existing table) then you can also use the migration generator:
+
+<shell>
+$ rails generate migration AddPartNumberToProducts
+</shell>
+
+This will create an empty but appropriately named migration:
+
+<ruby>
+class AddPartNumberToProducts < ActiveRecord::Migration
+ def change
+ end
+end
+</ruby>
+
+If the migration name is of the form "AddXXXToYYY" or "RemoveXXXFromYYY" and is
+followed by a list of column names and types then a migration containing the
+appropriate +add_column+ and +remove_column+ statements will be created.
+
+<shell>
+$ rails generate migration AddPartNumberToProducts part_number:string
+</shell>
+
+will generate
+
+<ruby>
+class AddPartNumberToProducts < ActiveRecord::Migration
+ def change
+ add_column :products, :part_number, :string
+ end
+end
+</ruby>
+
+Similarly,
+
+<shell>
+$ rails generate migration RemovePartNumberFromProducts part_number:string
+</shell>
+
+generates
+
+<ruby>
+class RemovePartNumberFromProducts < ActiveRecord::Migration
+ def up
+ remove_column :products, :part_number
+ end
+
+ def down
+ add_column :products, :part_number, :string
+ end
+end
+</ruby>
+
+You are not limited to one magically generated column, for example
+
+<shell>
+$ rails generate migration AddDetailsToProducts part_number:string price:decimal
+</shell>
+
+generates
+
+<ruby>
+class AddDetailsToProducts < ActiveRecord::Migration
+ def change
+ add_column :products, :part_number, :string
+ add_column :products, :price, :decimal
+ end
+end
+</ruby>
+
+As always, what has been generated for you is just a starting point. You can add
+or remove from it as you see fit by editing the
+db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb file.
+
+NOTE: The generated migration file for destructive migrations will still be
+old-style using the +up+ and +down+ methods. This is because Rails needs to know
+the original data types defined when you made the original changes.
+
+h3. Writing a Migration
+
+Once you have created your migration using one of the generators it's time to
+get to work!
+
+h4. Creating a Table
+
+Migration method +create_table+ will be one of your workhorses. A typical use
+would be
+
+<ruby>
+create_table :products do |t|
+ t.string :name
+end
+</ruby>
+
+which creates a +products+ table with a column called +name+ (and as discussed
+below, an implicit +id+ column).
+
+The object yielded to the block allows you to create columns on the table. There
+are two ways of doing it. The first (traditional) form looks like
+
+<ruby>
+create_table :products do |t|
+ t.column :name, :string, :null => false
+end
+</ruby>
+
+The second form, the so called "sexy" migration, drops the somewhat redundant
++column+ method. Instead, the +string+, +integer+, etc. methods create a column
+of that type. Subsequent parameters are the same.
+
+<ruby>
+create_table :products do |t|
+ t.string :name, :null => false
+end
+</ruby>
+
+By default, +create_table+ will create a primary key called +id+. You can change
+the name of the primary key with the +:primary_key+ option (don't forget to
+update the corresponding model) or, if you don't want a primary key at all (for
+example for a HABTM join table), you can pass the option +:id => false+. If you
+need to pass database specific options you can place an SQL fragment in the
++:options+ option. For example,
+
+<ruby>
+create_table :products, :options => "ENGINE=BLACKHOLE" do |t|
+ t.string :name, :null => false
+end
+</ruby>
+
+will append +ENGINE=BLACKHOLE+ to the SQL statement used to create the table
+(when using MySQL, the default is +ENGINE=InnoDB+).
+
+h4. Creating a Join Table
+
+Migration method +create_join_table+ creates a HABTM join table. A typical use
+would be
+
+<ruby>
+create_join_table :products, :categories
+</ruby>
+
+which creates a +categories_products+ table with two columns called +category_id+ and +product_id+.
+These columns have the option +:null+ set to +false+ by default.
+
+You can pass the option +:table_name+ with you want to customize the table name. For example,
+
+<ruby>
+create_join_table :products, :categories, :table_name => :categorization
+</ruby>
+
+will create a +categorization+ table.
+
+By default, +create_join_table+ will create two columns with no options, but you can specify these
+options using the +:column_options+ option. For example,
+
+<ruby>
+create_join_table :products, :categories, :column_options => {:null => true}
+</ruby>
+
+will create the +product_id+ and +category_id+ with the +:null+ option as +true+.
+
+h4. Changing Tables
+
+A close cousin of +create_table+ is +change_table+, used for changing existing
+tables. It is used in a similar fashion to +create_table+ but the object yielded
+to the block knows more tricks. For example
+
+<ruby>
+change_table :products do |t|
+ t.remove :description, :name
+ t.string :part_number
+ t.index :part_number
+ t.rename :upccode, :upc_code
+end
+</ruby>
+
+removes the +description+ and +name+ columns, creates a +part_number+ string
+column and adds an index on it. Finally it renames the +upccode+ column.
+
+h4. Special Helpers
+
+Active Record provides some shortcuts for common functionality. It is for
+example very common to add both the +created_at+ and +updated_at+ columns and so
+there is a method that does exactly that:
+
+<ruby>
+create_table :products do |t|
+ t.timestamps
+end
+</ruby>
+
+will create a new products table with those two columns (plus the +id+ column)
+whereas
+
+<ruby>
+change_table :products do |t|
+ t.timestamps
+end
+</ruby>
+adds those columns to an existing table.
+
+Another helper is called +references+ (also available as +belongs_to+). In its
+simplest form it just adds some readability.
+
+<ruby>
+create_table :products do |t|
+ t.references :category
+end
+</ruby>
+
+will create a +category_id+ column of the appropriate type. Note that you pass
+the model name, not the column name. Active Record adds the +_id+ for you. If
+you have polymorphic +belongs_to+ associations then +references+ will add both
+of the columns required:
+
+<ruby>
+create_table :products do |t|
+ t.references :attachment, :polymorphic => {:default => 'Photo'}
+end
+</ruby>
+
+will add an +attachment_id+ column and a string +attachment_type+ column with
+a default value of 'Photo'. +references+ also allows you to define an
+index directly, instead of using +add_index+ after the +create_table+ call:
+
+<ruby>
+create_table :products do |t|
+ t.references :category, :index => true
+end
+</ruby>
+
+will create an index identical to calling `add_index :products, :category_id`.
+
+NOTE: The +references+ helper does not actually create foreign key constraints
+for you. You will need to use +execute+ or a plugin that adds "foreign key
+support":#active-record-and-referential-integrity.
+
+If the helpers provided by Active Record aren't enough you can use the +execute+
+method to execute arbitrary SQL.
+
+For more details and examples of individual methods, check the API documentation,
+in particular the documentation for
+"<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html
+(which provides the methods available in the +up+ and +down+ methods),
+"<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html
+(which provides the methods available on the object yielded by +create_table+)
+and
+"<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html
+(which provides the methods available on the object yielded by +change_table+).
+
+h4. Using the +change+ Method
+
+The +change+ method removes the need to write both +up+ and +down+ methods in
+those cases that Rails knows how to revert the changes automatically. Currently,
+the +change+ method supports only these migration definitions:
+
+* +add_column+
+* +add_index+
+* +add_timestamps+
+* +create_table+
+* +remove_timestamps+
+* +rename_column+
+* +rename_index+
+* +rename_table+
+
+If you're going to need to use any other methods, you'll have to write the
++up+ and +down+ methods instead of using the +change+ method.
+
+h4. Using the +up+/+down+ Methods
+
+The +down+ method of your migration should revert the transformations done by
+the +up+ method. In other words, the database schema should be unchanged if you
+do an +up+ followed by a +down+. For example, if you create a table in the +up+
+method, you should drop it in the +down+ method. It is wise to reverse the
+transformations in precisely the reverse order they were made in the +up+
+method. For example,
+
+<ruby>
+class ExampleMigration < ActiveRecord::Migration
+ def up
+ create_table :products do |t|
+ t.references :category
+ end
+ #add a foreign key
+ execute <<-SQL
+ ALTER TABLE products
+ ADD CONSTRAINT fk_products_categories
+ FOREIGN KEY (category_id)
+ REFERENCES categories(id)
+ SQL
+ add_column :users, :home_page_url, :string
+ rename_column :users, :email, :email_address
+ end
+
+ def down
+ rename_column :users, :email_address, :email
+ remove_column :users, :home_page_url
+ execute <<-SQL
+ ALTER TABLE products
+ DROP FOREIGN KEY fk_products_categories
+ SQL
+ drop_table :products
+ end
+end
+</ruby>
+
+Sometimes your migration will do something which is just plain irreversible; for
+example, it might destroy some data. In such cases, you can raise
++ActiveRecord::IrreversibleMigration+ from your +down+ method. If someone tries
+to revert your migration, an error message will be displayed saying that it
+can't be done.
+
+h3. Running Migrations
+
+Rails provides a set of rake tasks to work with migrations which boil down to
+running certain sets of migrations.
+
+The very first migration related rake task you will use will probably be
++rake db:migrate+. In its most basic form it just runs the +up+ or +change+
+method for all the migrations that have not yet been run. If there are
+no such migrations, it exits. It will run these migrations in order based
+on the date of the migration.
+
+Note that running the +db:migrate+ also invokes the +db:schema:dump+ task, which
+will update your db/schema.rb file to match the structure of your database.
+
+If you specify a target version, Active Record will run the required migrations
+(up, down or change) until it has reached the specified version. The version
+is the numerical prefix on the migration's filename. For example, to migrate
+to version 20080906120000 run
+
+<shell>
+$ rake db:migrate VERSION=20080906120000
+</shell>
+
+If version 20080906120000 is greater than the current version (i.e., it is
+migrating upwards), this will run the +up+ method on all migrations up to and
+including 20080906120000, and will not execute any later migrations. If
+migrating downwards, this will run the +down+ method on all the migrations
+down to, but not including, 20080906120000.
+
+h4. Rolling Back
+
+A common task is to rollback the last migration, for example if you made a
+mistake in it and wish to correct it. Rather than tracking down the version
+number associated with the previous migration you can run
+
+<shell>
+$ rake db:rollback
+</shell>
+
+This will run the +down+ method from the latest migration. If you need to undo
+several migrations you can provide a +STEP+ parameter:
+
+<shell>
+$ rake db:rollback STEP=3
+</shell>
+
+will run the +down+ method from the last 3 migrations.
+
+The +db:migrate:redo+ task is a shortcut for doing a rollback and then migrating
+back up again. As with the +db:rollback+ task, you can use the +STEP+ parameter
+if you need to go more than one version back, for example
+
+<shell>
+$ rake db:migrate:redo STEP=3
+</shell>
+
+Neither of these Rake tasks do anything you could not do with +db:migrate+. They
+are simply more convenient, since you do not need to explicitly specify the
+version to migrate to.
+
+h4. Resetting the database
+
+The +rake db:reset+ task will drop the database, recreate it and load the
+current schema into it.
+
+NOTE: This is not the same as running all the migrations - see the section on
+"schema.rb":#schema-dumping-and-you.
+
+h4. Running specific migrations
+
+If you need to run a specific migration up or down, the +db:migrate:up+ and
++db:migrate:down+ tasks will do that. Just specify the appropriate version and
+the corresponding migration will have its +up+ or +down+ method invoked, for
+example,
+
+<shell>
+$ rake db:migrate:up VERSION=20080906120000
+</shell>
+
+will run the +up+ method from the 20080906120000 migration. This task will first
+check whether the migration is already performed and will do nothing if Active Record believes
+that it has already been run.
+
+h4. Changing the output of running migrations
+
+By default migrations tell you exactly what they're doing and how long it took.
+A migration creating a table and adding an index might produce output like this
+
+<shell>
+== CreateProducts: migrating =================================================
+-- create_table(:products)
+ -> 0.0028s
+== CreateProducts: migrated (0.0028s) ========================================
+</shell>
+
+Several methods are provided in migrations that allow you to control all this:
+
+|_.Method |_.Purpose|
+|suppress_messages |Takes a block as an argument and suppresses any output
+ generated by the block.|
+|say |Takes a message argument and outputs it as is. A second
+ boolean argument can be passed to specify whether to
+ indent or not.|
+|say_with_time |Outputs text along with how long it took to run its
+ block. If the block returns an integer it assumes it
+ is the number of rows affected.|
+
+For example, this migration
+
+<ruby>
+class CreateProducts < ActiveRecord::Migration
+ def change
+ suppress_messages do
+ create_table :products do |t|
+ t.string :name
+ t.text :description
+ t.timestamps
+ end
+ end
+ say "Created a table"
+ suppress_messages {add_index :products, :name}
+ say "and an index!", true
+ say_with_time 'Waiting for a while' do
+ sleep 10
+ 250
+ end
+ end
+end
+</ruby>
+
+generates the following output
+
+<shell>
+== CreateProducts: migrating =================================================
+-- Created a table
+ -> and an index!
+-- Waiting for a while
+ -> 10.0013s
+ -> 250 rows
+== CreateProducts: migrated (10.0054s) =======================================
+</shell>
+
+If you want Active Record to not output anything, then running +rake db:migrate
+VERBOSE=false+ will suppress all output.
+
+h3. Using Models in Your Migrations
+
+When creating or updating data in a migration it is often tempting to use one of
+your models. After all, they exist to provide easy access to the underlying
+data. This can be done, but some caution should be observed.
+
+For example, problems occur when the model uses database columns which are (1)
+not currently in the database and (2) will be created by this or a subsequent
+migration.
+
+Consider this example, where Alice and Bob are working on the same code base
+which contains a +Product+ model:
+
+Bob goes on vacation.
+
+Alice creates a migration for the +products+ table which adds a new column and
+initializes it. She also adds a validation to the +Product+ model for the new
+column.
+
+<ruby>
+# db/migrate/20100513121110_add_flag_to_product.rb
+
+class AddFlagToProduct < ActiveRecord::Migration
+ def change
+ add_column :products, :flag, :boolean
+ Product.update_all :flag => false
+ end
+end
+</ruby>
+
+<ruby>
+# app/model/product.rb
+
+class Product < ActiveRecord::Base
+ validates :flag, :presence => true
+end
+</ruby>
+
+Alice adds a second migration which adds and initializes another column to the
++products+ table and also adds a validation to the +Product+ model for the new
+column.
+
+<ruby>
+# db/migrate/20100515121110_add_fuzz_to_product.rb
+
+class AddFuzzToProduct < ActiveRecord::Migration
+ def change
+ add_column :products, :fuzz, :string
+ Product.update_all :fuzz => 'fuzzy'
+ end
+end
+</ruby>
+
+<ruby>
+# app/model/product.rb
+
+class Product < ActiveRecord::Base
+ validates :flag, :fuzz, :presence => true
+end
+</ruby>
+
+Both migrations work for Alice.
+
+Bob comes back from vacation and:
+
+# Updates the source - which contains both migrations and the latest version of
+the Product model.
+# Runs outstanding migrations with +rake db:migrate+, which
+includes the one that updates the +Product+ model.
+
+The migration crashes because when the model attempts to save, it tries to
+validate the second added column, which is not in the database when the _first_
+migration runs:
+
+<plain>
+rake aborted!
+An error has occurred, this and all later migrations canceled:
+
+undefined method `fuzz' for #<Product:0x000001049b14a0>
+</plain>
+
+A fix for this is to create a local model within the migration. This keeps rails
+from running the validations, so that the migrations run to completion.
+
+When using a faux model, it's a good idea to call
++Product.reset_column_information+ to refresh the +ActiveRecord+ cache for the
++Product+ model prior to updating data in the database.
+
+If Alice had done this instead, there would have been no problem:
+
+<ruby>
+# db/migrate/20100513121110_add_flag_to_product.rb
+
+class AddFlagToProduct < ActiveRecord::Migration
+ class Product < ActiveRecord::Base
+ end
+
+ def change
+ add_column :products, :flag, :boolean
+ Product.reset_column_information
+ Product.update_all :flag => false
+ end
+end
+</ruby>
+
+<ruby>
+# db/migrate/20100515121110_add_fuzz_to_product.rb
+
+class AddFuzzToProduct < ActiveRecord::Migration
+ class Product < ActiveRecord::Base
+ end
+
+ def change
+ add_column :products, :fuzz, :string
+ Product.reset_column_information
+ Product.update_all :fuzz => 'fuzzy'
+ end
+end
+</ruby>
+
+h3. Schema Dumping and You
+
+h4. What are Schema Files for?
+
+Migrations, mighty as they may be, are not the authoritative source for your
+database schema. That role falls to either +db/schema.rb+ or an SQL file which
+Active Record generates by examining the database. They are not designed to be
+edited, they just represent the current state of the database.
+
+There is no need (and it is error prone) to deploy a new instance of an app by
+replaying the entire migration history. It is much simpler and faster to just
+load into the database a description of the current schema.
+
+For example, this is how the test database is created: the current development
+database is dumped (either to +db/schema.rb+ or +db/structure.sql+) and then
+loaded into the test database.
+
+Schema files are also useful if you want a quick look at what attributes an
+Active Record object has. This information is not in the model's code and is
+frequently spread across several migrations, but the information is nicely
+summed up in the schema file. The
+"annotate_models":https://github.com/ctran/annotate_models gem automatically
+adds and updates comments at the top of each model summarizing the schema if
+you desire that functionality.
+
+h4. Types of Schema Dumps
+
+There are two ways to dump the schema. This is set in +config/application.rb+ by
+the +config.active_record.schema_format+ setting, which may be either +:sql+ or
++:ruby+.
+
+If +:ruby+ is selected then the schema is stored in +db/schema.rb+. If you look
+at this file you'll find that it looks an awful lot like one very big migration:
+
+<ruby>
+ActiveRecord::Schema.define(:version => 20080906171750) do
+ create_table "authors", :force => true do |t|
+ t.string "name"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "products", :force => true do |t|
+ t.string "name"
+ t.text "description"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "part_number"
+ end
+end
+</ruby>
+
+In many ways this is exactly what it is. This file is created by inspecting the
+database and expressing its structure using +create_table+, +add_index+, and so
+on. Because this is database-independent, it could be loaded into any database
+that Active Record supports. This could be very useful if you were to distribute
+an application that is able to run against multiple databases.
+
+There is however a trade-off: +db/schema.rb+ cannot express database specific
+items such as foreign key constraints, triggers, or stored procedures. While in
+a migration you can execute custom SQL statements, the schema dumper cannot
+reconstitute those statements from the database. If you are using features like
+this, then you should set the schema format to +:sql+.
+
+Instead of using Active Record's schema dumper, the database's structure will be
+dumped using a tool specific to the database (via the +db:structure:dump+ Rake task)
+into +db/structure.sql+. For example, for the PostgreSQL RDBMS, the
++pg_dump+ utility is used. For MySQL, this file will contain the output of +SHOW
+CREATE TABLE+ for the various tables. Loading these schemas is simply a question
+of executing the SQL statements they contain. By definition, this will create a
+perfect copy of the database's structure. Using the +:sql+ schema format will,
+however, prevent loading the schema into a RDBMS other than the one used to
+create it.
+
+h4. Schema Dumps and Source Control
+
+Because schema dumps are the authoritative source for your database schema, it
+is strongly recommended that you check them into source control.
+
+h3. Active Record and Referential Integrity
+
+The Active Record way claims that intelligence belongs in your models, not in
+the database. As such, features such as triggers or foreign key constraints,
+which push some of that intelligence back into the database, are not heavily
+used.
+
+Validations such as +validates :foreign_key, :uniqueness => true+ are one way in
+which models can enforce data integrity. The +:dependent+ option on associations
+allows models to automatically destroy child objects when the parent is
+destroyed. Like anything which operates at the application level, these cannot
+guarantee referential integrity and so some people augment them with foreign key
+constraints in the database.
+
+Although Active Record does not provide any tools for working directly with such
+features, the +execute+ method can be used to execute arbitrary SQL. You could
+also use some plugin like "foreigner":https://github.com/matthuhiggins/foreigner
+which add foreign key support to Active Record (including support for dumping
+foreign keys in +db/schema.rb+).
diff --git a/railties/guides/source/nested_model_forms.textile b/guides/source/nested_model_forms.textile
index 4b1fd2e0ac..82c9ab9d36 100644
--- a/railties/guides/source/nested_model_forms.textile
+++ b/guides/source/nested_model_forms.textile
@@ -131,7 +131,7 @@ This will generate the following html:
<html>
<form action="/people" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" size="30" type="text" />
+ <input id="person_name" name="person[name]" type="text" />
</form>
</html>
@@ -153,9 +153,9 @@ This generates:
<html>
<form action="/people" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" size="30" type="text" />
+ <input id="person_name" name="person[name]" type="text" />
- <input id="person_address_attributes_street" name="person[address_attributes][street]" size="30" type="text" />
+ <input id="person_address_attributes_street" name="person[address_attributes][street]" type="text" />
</form>
</html>
@@ -194,10 +194,10 @@ Which generates:
<html>
<form action="/people" class="new_person" id="new_person" method="post">
- <input id="person_name" name="person[name]" size="30" type="text" />
+ <input id="person_name" name="person[name]" type="text" />
- <input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" size="30" type="text" />
- <input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" size="30" type="text" />
+ <input id="person_projects_attributes_0_name" name="person[projects_attributes][0][name]" type="text" />
+ <input id="person_projects_attributes_1_name" name="person[projects_attributes][1][name]" type="text" />
</form>
</html>
diff --git a/railties/guides/source/performance_testing.textile b/guides/source/performance_testing.textile
index f3ea7e38bc..958b13cd9e 100644
--- a/railties/guides/source/performance_testing.textile
+++ b/guides/source/performance_testing.textile
@@ -151,7 +151,7 @@ Performance tests can be run in two modes: Benchmarking and Profiling.
h5. Benchmarking
-Benchmarking makes it easy to quickly gather a few metrics about each test tun. By default, each test case is run *4 times* in benchmarking mode.
+Benchmarking makes it easy to quickly gather a few metrics about each test run. By default, each test case is run *4 times* in benchmarking mode.
To run performance tests in benchmarking mode:
@@ -447,7 +447,7 @@ h4. Using Ruby-Prof on MRI and REE
Add Ruby-Prof to your applications' Gemfile if you want to benchmark/profile under MRI or REE:
<ruby>
-gem 'ruby-prof', :path => 'git://github.com/wycats/ruby-prof.git'
+gem 'ruby-prof', :git => 'git://github.com/wycats/ruby-prof.git'
</ruby>
Now run +bundle install+ and you're ready to go.
diff --git a/railties/guides/source/plugins.textile b/guides/source/plugins.textile
index 5cfd336d1e..95e38db483 100644
--- a/railties/guides/source/plugins.textile
+++ b/guides/source/plugins.textile
@@ -25,26 +25,14 @@ endprologue.
h3. Setup
-Before you continue, take a moment to decide if your new plugin will be potentially shared across different Rails applications.
+_"vendored plugins"_ were available in previous versions of Rails, but they are deprecated in
+Rails 3.2, and will not be available in the future.
-* If your plugin is specific to your application, your new plugin will be a _vendored plugin_.
-* If you think your plugin may be used across applications, build it as a _gemified plugin_.
+Currently, Rails plugins are built as gems, _gemified plugins_. They can be shared accross
+different rails applications using RubyGems and Bundler if desired.
-h4. Either generate a vendored plugin...
+h4. Generate a gemified plugin.
-Use the +rails generate plugin+ command in your Rails root directory
- to create a new plugin that will live in the +vendor/plugins+
- directory. See usage and options by asking for help:
-
-<shell>
-$ rails generate plugin --help
-</shell>
-
-h4. Or generate a gemified plugin.
-
-Writing your Rails plugin as a gem, rather than as a vendored plugin,
- lets you share your plugin across different rails applications using
- RubyGems and Bundler.
Rails 3.1 ships with a +rails plugin new+ command which creates a
skeleton for developing any kind of Rails extension with the ability
@@ -186,11 +174,11 @@ require 'test_helper'
class ActsAsYaffleTest < Test::Unit::TestCase
def test_a_hickwalls_yaffle_text_field_should_be_last_squawk
- assert_equal "last_squawk", Hickwall.yaffle_text_field
+ assert_equal :last_squawk, Hickwall.yaffle_text_field
end
def test_a_wickwalls_yaffle_text_field_should_be_last_tweet
- assert_equal "last_tweet", Wickwall.yaffle_text_field
+ assert_equal :last_tweet, Wickwall.yaffle_text_field
end
end
@@ -372,13 +360,16 @@ module Yaffle
def acts_as_yaffle(options = {})
cattr_accessor :yaffle_text_field
self.yaffle_text_field = (options[:yaffle_text_field] || :last_squawk).to_s
+
+ include Yaffle::ActsAsYaffle::LocalInstanceMethods
end
end
- def squawk(string)
- write_attribute(self.class.yaffle_text_field, string.to_squawk)
+ module LocalInstanceMethods
+ def squawk(string)
+ write_attribute(self.class.yaffle_text_field, string.to_squawk)
+ end
end
-
end
end
@@ -412,30 +403,6 @@ After running +bundle install+, your gem functionality will be available to the
When the gem is ready to be shared as a formal release, it can be published to "RubyGems":http://www.rubygems.org.
For more information about publishing gems to RubyGems, see: "http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html":http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html
-h3. Non-Gem Plugins
-
-Non-gem plugins are useful for functionality that won't be shared with another project. Keeping your custom functionality in the
-vendor/plugins directory un-clutters the rest of the application.
-
-Move the directory that you created for the gem based plugin into the vendor/plugins directory of a generated Rails application, create a vendor/plugins/yaffle/init.rb file that contains "require 'yaffle'" and everything will still work.
-
-<ruby>
-# yaffle/init.rb
-
-require 'yaffle'
-</ruby>
-
-You can test this by changing to the Rails application that you added the plugin to and starting a rails console. Once in the
-console we can check to see if the String has an instance method to_squawk:
-
-<shell>
-$ cd my_app
-$ rails console
-$ "Rails plugins are easy!".to_squawk
-</shell>
-
-You can also remove the .gemspec, Gemfile and Gemfile.lock files as they will no longer be needed.
-
h3. RDoc Documentation
Once your plugin is stable and you are ready to deploy do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy.
@@ -461,4 +428,3 @@ h4. References
* "Using Gemspecs As Intended":http://yehudakatz.com/2010/04/02/using-gemspecs-as-intended/
* "Gemspec Reference":http://docs.rubygems.org/read/chapter/20
* "GemPlugins":http://www.mbleigh.com/2008/06/11/gemplugins-a-brief-introduction-to-the-future-of-rails-plugins
-* "Keeping init.rb thin":http://daddy.platte.name/2007/05/rails-plugins-keep-initrb-thin.html
diff --git a/railties/guides/source/rails_application_templates.textile b/guides/source/rails_application_templates.textile
index 3b51a52733..f50ced3307 100644
--- a/railties/guides/source/rails_application_templates.textile
+++ b/guides/source/rails_application_templates.textile
@@ -1,6 +1,6 @@
h2. Rails Application Templates
-Application templates are simple Ruby files containing DSL for adding plugins/gems/initializers etc. to your freshly created Rails project or an existing Rails project.
+Application templates are simple Ruby files containing DSL for adding gems/initializers etc. to your freshly created Rails project or an existing Rails project.
By referring to this guide, you will be able to:
@@ -82,31 +82,6 @@ For example, if you need to source a gem from "http://code.whytheluckystiff.net"
add_source "http://code.whytheluckystiff.net"
</ruby>
-h4. plugin(name, options = {})
-
-Installs a plugin to the generated application.
-
-Plugin can be installed from Git:
-
-<ruby>
-plugin 'authentication', :git => 'git://github.com/foor/bar.git'
-</ruby>
-
-You can even install plugins as git submodules:
-
-<ruby>
-plugin 'authentication', :git => 'git://github.com/foor/bar.git',
- :submodule => true
-</ruby>
-
-Please note that you need to +git :init+ before you can install a plugin as a submodule.
-
-Or use plain old SVN:
-
-<ruby>
-plugin 'usingsvn', :svn => 'svn://example.com/usingsvn/trunk'
-</ruby>
-
h4. vendor/lib/file/initializer(filename, data = nil, &block)
Adds an initializer to the generated application’s +config/initializers+ directory.
diff --git a/railties/guides/source/rails_on_rack.textile b/guides/source/rails_on_rack.textile
index 57c03b54dc..d8910cf1d0 100644
--- a/railties/guides/source/rails_on_rack.textile
+++ b/guides/source/rails_on_rack.textile
@@ -91,12 +91,15 @@ For a freshly generated Rails application, this might produce something like:
<ruby>
use ActionDispatch::Static
use Rack::Lock
-use ActiveSupport::Cache::Strategy::LocalCache
+use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838>
use Rack::Runtime
+use Rack::MethodOverride
+use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
+use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
-use Rack::Sendfile
+use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
@@ -104,8 +107,9 @@ use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
-use Rack::MethodOverride
use ActionDispatch::Head
+use Rack::ConditionalGet
+use Rack::ETag
use ActionDispatch::BestStandardsSupport
run Blog::Application.routes
</ruby>
@@ -144,61 +148,104 @@ You can swap an existing middleware in the middleware stack using +config.middle
<ruby>
# config/application.rb
-# Replace ActionController::Failsafe with Lifo::Failsafe
-config.middleware.swap ActionController::Failsafe, Lifo::Failsafe
+# Replace ActionDispatch::ShowExceptions with Lifo::ShowExceptions
+config.middleware.swap ActionDispatch::ShowExceptions, Lifo::ShowExceptions
</ruby>
-h5. Middleware Stack is an Array
+h5. Middleware Stack is an Enumerable
-The middleware stack behaves just like a normal +Array+. You can use any +Array+ methods to insert, reorder, or remove items from the stack. Methods described in the section above are just convenience methods.
+The middleware stack behaves just like a normal +Enumerable+. You can use any +Enumerable+ methods to manipulate or interrogate the stack. The middleware stack also implements some +Array+ methods including <tt>[]</tt>, +unshift+ and +delete+. Methods described in the section above are just convenience methods.
-For example, the following removes the middleware matching the supplied class name:
+Append following lines to your application configuration:
<ruby>
-config.middleware.delete(middleware)
+# config/application.rb
+config.middleware.delete "Rack::Lock"
</ruby>
+And now if you inspect the middleware stack, you'll find that +Rack::Lock+ will not be part of it.
+
+<shell>
+$ rake middleware
+(in /Users/lifo/Rails/blog)
+use ActionDispatch::Static
+use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8>
+use Rack::Runtime
+...
+run Myapp::Application.routes
+</shell>
+
h4. Internal Middleware Stack
-Much of Action Controller's functionality is implemented as Middlewares. The following table explains the purpose of each of them:
+Much of Action Controller's functionality is implemented as Middlewares. The following list explains the purpose of each of them:
-|_.Middleware|_.Purpose|
-|+Rack::Lock+|Sets <tt>env["rack.multithread"]</tt> flag to +true+ and wraps the application within a Mutex.|
-|+ActionController::Failsafe+|Returns HTTP Status +500+ to the client if an exception gets raised while dispatching.|
-|+ActiveRecord::QueryCache+|Enables the Active Record query cache.|
-|+ActionController::Session::CookieStore+|Uses the cookie based session store.|
-|+ActionController::Session::MemCacheStore+|Uses the memcached based session store.|
-|+ActiveRecord::SessionStore+|Uses the database based session store.|
-|+Rack::MethodOverride+|Sets HTTP method based on +_method+ parameter or <tt>env["HTTP_X_HTTP_METHOD_OVERRIDE"]</tt>.|
-|+Rack::Head+|Discards the response body if the client sends a +HEAD+ request.|
+ *+ActionDispatch::Static+*
+* Used to serve static assets. Disabled if <tt>config.serve_static_assets</tt> is true.
-TIP: It's possible to use any of the above middlewares in your custom Rack stack.
+ *+Rack::Lock+*
+* Sets <tt>env["rack.multithread"]</tt> flag to +true+ and wraps the application within a Mutex.
-h4. Customizing Internal Middleware Stack
+ *+ActiveSupport::Cache::Strategy::LocalCache::Middleware+*
+* Used for memory caching. This cache is not thread safe.
-It's possible to replace the entire middleware stack with a custom stack using <tt>ActionController::Dispatcher.middleware=</tt>.
+ *+Rack::Runtime+*
+* Sets an X-Runtime header, containing the time (in seconds) taken to execute the request.
-Put the following in an initializer:
+ *+Rack::MethodOverride+*
+* Allows the method to be overridden if <tt>params[:_method]</tt> is set. This is the middleware which supports the PUT and DELETE HTTP method types.
-<ruby>
-# config/initializers/stack.rb
-ActionController::Dispatcher.middleware = ActionController::MiddlewareStack.new do |m|
- m.use ActionController::Failsafe
- m.use ActiveRecord::QueryCache
- m.use Rack::Head
-end
-</ruby>
+ *+ActionDispatch::RequestId+*
+* Makes a unique +X-Request-Id+ header available to the response and enables the <tt>ActionDispatch::Request#uuid</tt> method.
-And now inspecting the middleware stack:
+ *+Rails::Rack::Logger+*
+* Notifies the logs that the request has began. After request is complete, flushes all the logs.
-<shell>
-$ rake middleware
-(in /Users/lifo/Rails/blog)
-use ActionController::Failsafe
-use ActiveRecord::QueryCache
-use Rack::Head
-run ActionController::Dispatcher.new
-</shell>
+ *+ActionDispatch::ShowExceptions+*
+* Rescues any exception returned by the application and calls an exceptions app that will wrap it in a format for the end user.
+
+ *+ActionDispatch::DebugExceptions+*
+* Responsible for logging exceptions and showing a debugging page in case the request is local.
+
+ *+ActionDispatch::RemoteIp+*
+* Checks for IP spoofing attacks.
+
+ *+ActionDispatch::Reloader+*
+* Provides prepare and cleanup callbacks, intended to assist with code reloading during development.
+
+ *+ActionDispatch::Callbacks+*
+* Runs the prepare callbacks before serving the request.
+
+ *+ActiveRecord::ConnectionAdapters::ConnectionManagement+*
+* Cleans active connections after each request, unless the <tt>rack.test</tt> key in the request environment is set to +true+.
+
+ *+ActiveRecord::QueryCache+*
+* Enables the Active Record query cache.
+
+ *+ActionDispatch::Cookies+*
+* Sets cookies for the request.
+
+ *+ActionDispatch::Session::CookieStore+*
+* Responsible for storing the session in cookies.
+
+ *+ActionDispatch::Flash+*
+* Sets up the flash keys. Only available if <tt>config.action_controller.session_store</tt> is set to a value.
+
+ *+ActionDispatch::ParamsParser+*
+* Parses out parameters from the request into <tt>params</tt>.
+
+ *+ActionDispatch::Head+*
+* Converts HEAD requests to +GET+ requests and serves them as so.
+
+ *+Rack::ConditionalGet+*
+* Adds support for "Conditional +GET+" so that server responds with nothing if page wasn't changed.
+
+ *+Rack::ETag+*
+* Adds ETag header on all String bodies. ETags are used to validate cache.
+
+ *+ActionDispatch::BestStandardsSupport+*
+* Enables “best standards support” so that IE8 renders some elements correctly.
+
+TIP: It's possible to use any of the above middlewares in your custom Rack stack.
h4. Using Rack Builder
diff --git a/railties/guides/source/routing.textile b/guides/source/routing.textile
index f281009fee..4a50edbb15 100644
--- a/railties/guides/source/routing.textile
+++ b/guides/source/routing.textile
@@ -25,7 +25,7 @@ GET /patients/17
it asks the router to match it to a controller action. If the first matching route is
<ruby>
-match "/patients/:id" => "patients#show"
+get "/patients/:id" => "patients#show"
</ruby>
the request is dispatched to the +patients+ controller's +show+ action with <tt>{ :id => "17" }</tt> in +params+.
@@ -50,7 +50,7 @@ Resource routing allows you to quickly declare all of the common routes for a gi
h4. Resources on the Web
-Browsers request pages from Rails by making a request for a URL using a specific HTTP method, such as +GET+, +POST+, +PUT+ and +DELETE+. Each method is a request to perform an operation on the resource. A resource route maps a number of related requests to actions in a single controller.
+Browsers request pages from Rails by making a request for a URL using a specific HTTP method, such as +GET+, +POST+, +PATCH+, +PUT+ and +DELETE+. Each method is a request to perform an operation on the resource. A resource route maps a number of related requests to actions in a single controller.
When your Rails application receives an incoming request for
@@ -82,10 +82,9 @@ creates seven different routes in your application, all mapping to the +Photos+
|POST |/photos |create |create a new photo |
|GET |/photos/:id |show |display a specific photo |
|GET |/photos/:id/edit |edit |return an HTML form for editing a photo |
-|PUT |/photos/:id |update |update a specific photo |
+|PATCH/PUT |/photos/:id |update |update a specific photo |
|DELETE |/photos/:id |destroy |delete a specific photo |
-
NOTE: Rails routes are matched in the order they are specified, so if you have a +resources :photos+ above a +get 'photos/poll'+ the +show+ action's route for the +resources+ line will be matched before the +get+ line. To fix this, move the +get+ line *above* the +resources+ line so that it is matched first.
h4. Paths and URLs
@@ -122,7 +121,7 @@ h4. Singular Resources
Sometimes, you have a resource that clients always look up without referencing an ID. For example, you would like +/profile+ to always show the profile of the currently logged in user. In this case, you can use a singular resource to map +/profile+ (rather than +/profile/:id+) to the +show+ action.
<ruby>
-match "profile" => "users#show"
+get "profile" => "users#show"
</ruby>
This resourceful route
@@ -138,7 +137,7 @@ creates six different routes in your application, all mapping to the +Geocoders+
|POST |/geocoder |create |create the new geocoder |
|GET |/geocoder |show |display the one and only geocoder resource |
|GET |/geocoder/edit |edit |return an HTML form for editing the geocoder |
-|PUT |/geocoder |update |update the one and only geocoder resource |
+|PATCH/PUT |/geocoder |update |update the one and only geocoder resource |
|DELETE |/geocoder |destroy |delete the geocoder resource |
NOTE: Because you might want to use the same controller for a singular route (+/account+) and a plural route (+/accounts/45+), singular resources map to plural controllers.
@@ -169,7 +168,7 @@ This will create a number of routes for each of the +posts+ and +comments+ contr
|POST |/admin/posts |create | admin_posts_path |
|GET |/admin/posts/:id |show | admin_post_path(:id) |
|GET |/admin/posts/:id/edit |edit | edit_admin_post_path(:id) |
-|PUT |/admin/posts/:id |update | admin_post_path(:id) |
+|PATCH/PUT |/admin/posts/:id |update | admin_post_path(:id) |
|DELETE |/admin/posts/:id |destroy | admin_post_path(:id) |
If you want to route +/posts+ (without the prefix +/admin+) to +Admin::PostsController+, you could use
@@ -208,7 +207,7 @@ In each of these cases, the named routes remain the same as if you did not use +
|POST |/admin/posts |create | posts_path |
|GET |/admin/posts/:id |show | post_path(:id) |
|GET |/admin/posts/:id/edit|edit | edit_post_path(:id)|
-|PUT |/admin/posts/:id |update | post_path(:id) |
+|PATCH/PUT |/admin/posts/:id |update | post_path(:id) |
|DELETE |/admin/posts/:id |destroy | post_path(:id) |
h4. Nested Resources
@@ -235,15 +234,14 @@ end
In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine:
-|_.HTTP Verb |_.Path |_.action |_.used for |
-|GET |/magazines/:id/ads |index |display a list of all ads for a specific magazine |
-|GET |/magazines/:id/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine |
-|POST |/magazines/:id/ads |create |create a new ad belonging to a specific magazine |
-|GET |/magazines/:id/ads/:id |show |display a specific ad belonging to a specific magazine |
-|GET |/magazines/:id/ads/:id/edit |edit |return an HTML form for editing an ad belonging to a specific magazine |
-|PUT |/magazines/:id/ads/:id |update |update a specific ad belonging to a specific magazine |
-|DELETE |/magazines/:id/ads/:id |destroy |delete a specific ad belonging to a specific magazine |
-
+|_.HTTP Verb |_.Path |_.action |_.used for |
+|GET |/magazines/:magazine_id/ads |index |display a list of all ads for a specific magazine |
+|GET |/magazines/:magazine_id/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine |
+|POST |/magazines/:magazine_id/ads |create |create a new ad belonging to a specific magazine |
+|GET |/magazines/:magazine_id/ads/:id |show |display a specific ad belonging to a specific magazine |
+|GET |/magazines/:magazine_id/ads/:id/edit |edit |return an HTML form for editing an ad belonging to a specific magazine |
+|PATCH/PUT |/magazines/:magazine_id/ads/:id |update |update a specific ad belonging to a specific magazine |
+|DELETE |/magazines/:magazine_id/ads/:id |destroy |delete a specific ad belonging to a specific magazine |
This will also create routing helpers such as +magazine_ads_url+ and +edit_magazine_ad_path+. These helpers take an instance of Magazine as the first parameter (+magazine_ads_url(@magazine)+).
@@ -297,12 +295,18 @@ In this case, Rails will see that +@magazine+ is a +Magazine+ and +@ad+ is an +A
<%= link_to "Ad details", [@magazine, @ad] %>
</erb>
-If you wanted to link to just a magazine, you could leave out the +Array+:
+If you wanted to link to just a magazine:
<erb>
<%= link_to "Magazine details", @magazine %>
</erb>
+For other actions, you just need to insert the action name as the first element of the array:
+
+<erb>
+<%= link_to "Edit Ad", [:edit, @magazine, @ad] %>
+</erb>
+
This allows you to treat instances of your models as URLs, and is a key advantage to using the resourceful style.
h4. Adding More RESTful Actions
@@ -323,7 +327,7 @@ end
This will recognize +/photos/1/preview+ with GET, and route to the +preview+ action of +PhotosController+. It will also create the +preview_photo_url+ and +preview_photo_path+ helpers.
-Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ routes, you can also pass +:on+ to a route, eliminating the block:
+Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +patch+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ routes, you can also pass +:on+ to a route, eliminating the block:
<ruby>
resources :photos do
@@ -370,7 +374,7 @@ h4. Bound Parameters
When you set up a regular route, you supply a series of symbols that Rails maps to parts of an incoming HTTP request. Two of these symbols are special: +:controller+ maps to the name of a controller in your application, and +:action+ maps to the name of an action within that controller. For example, consider one of the default Rails routes:
<ruby>
-match ':controller(/:action(/:id))'
+get ':controller(/:action(/:id))'
</ruby>
If an incoming request of +/photos/show/1+ is processed by this route (because it hasn't matched any previous route in the file), then the result will be to invoke the +show+ action of the +PhotosController+, and to make the final parameter +"1"+ available as +params[:id]+. This route will also route the incoming request of +/photos+ to +PhotosController#index+, since +:action+ and +:id+ are optional parameters, denoted by parentheses.
@@ -380,15 +384,15 @@ h4. Dynamic Segments
You can set up as many dynamic segments within a regular route as you like. Anything other than +:controller+ or +:action+ will be available to the action as part of +params+. If you set up this route:
<ruby>
-match ':controller/:action/:id/:user_id'
+get ':controller/:action/:id/:user_id'
</ruby>
An incoming path of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
-NOTE: You can't use +namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g:
+NOTE: You can't use +:namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g:
<ruby>
-match ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/
+get ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/
</ruby>
TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id+ => /[^\/]+/ allows anything except a slash.
@@ -398,7 +402,7 @@ h4. Static Segments
You can specify static segments when creating a route:
<ruby>
-match ':controller/:action/:id/with_user/:user_id'
+get ':controller/:action/:id/with_user/:user_id'
</ruby>
This route would respond to paths such as +/photos/show/1/with_user/2+. In this case, +params+ would be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
@@ -408,7 +412,7 @@ h4. The Query String
The +params+ will also include any parameters from the query string. For example, with this route:
<ruby>
-match ':controller/:action/:id'
+get ':controller/:action/:id'
</ruby>
An incoming path of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
@@ -418,7 +422,7 @@ h4. Defining Defaults
You do not need to explicitly use the +:controller+ and +:action+ symbols within a route. You can supply them as defaults:
<ruby>
-match 'photos/:id' => 'photos#show'
+get 'photos/:id' => 'photos#show'
</ruby>
With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of +PhotosController+.
@@ -426,7 +430,7 @@ With this route, Rails will match an incoming path of +/photos/12+ to the +show+
You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that you do not specify as dynamic segments. For example:
<ruby>
-match 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' }
+get 'photos/:id' => 'photos#show', :defaults => { :format => 'jpg' }
</ruby>
Rails would match +photos/12+ to the +show+ action of +PhotosController+, and set +params[:format]+ to +"jpg"+.
@@ -436,49 +440,45 @@ h4. Naming Routes
You can specify a name for any route using the +:as+ option.
<ruby>
-match 'exit' => 'sessions#destroy', :as => :logout
+get 'exit' => 'sessions#destroy', :as => :logout
</ruby>
This will create +logout_path+ and +logout_url+ as named helpers in your application. Calling +logout_path+ will return +/exit+
h4. HTTP Verb Constraints
-You can use the +:via+ option to constrain the request to one or more HTTP methods:
+In general, you should use the +get+, +post+, +put+ and +delete+ methods to constrain a route to a particular verb. You can use the +match+ method with the +:via+ option to match multiple verbs at once:
<ruby>
-match 'photos/show' => 'photos#show', :via => :get
+match 'photos' => 'photos#show', :via => [:get, :post]
</ruby>
-There is a shorthand version of this as well:
+You can match all verbs to a particular route using +:via => :all+:
<ruby>
-get 'photos/show'
+match 'photos' => 'photos#show', :via => :all
</ruby>
-You can also permit more than one verb to a single route:
-
-<ruby>
-match 'photos/show' => 'photos#show', :via => [:get, :post]
-</ruby>
+You should avoid routing all verbs to an action unless you have a good reason to, as routing both +GET+ requests and +POST+ requests to a single action has security implications.
h4. Segment Constraints
You can use the +:constraints+ option to enforce a format for a dynamic segment:
<ruby>
-match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
+get 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
</ruby>
This route would match paths such as +/photos/A12345+. You can more succinctly express the same route this way:
<ruby>
-match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
</ruby>
+:constraints+ takes regular expressions with the restriction that regexp anchors can't be used. For example, the following route will not work:
<ruby>
-match '/:id' => 'posts#show', :constraints => {:id => /^\d/}
+get '/:id' => 'posts#show', :constraints => {:id => /^\d/}
</ruby>
However, note that you don't need to use anchors because all routes are anchored at the start.
@@ -486,8 +486,8 @@ However, note that you don't need to use anchors because all routes are anchored
For example, the following routes would allow for +posts+ with +to_param+ values like +1-hello-world+ that always begin with a number and +users+ with +to_param+ values like +david+ that never begin with a number to share the root namespace:
<ruby>
-match '/:id' => 'posts#show', :constraints => { :id => /\d.+/ }
-match '/:username' => 'users#show'
+get '/:id' => 'posts#show', :constraints => { :id => /\d.+/ }
+get '/:username' => 'users#show'
</ruby>
h4. Request-Based Constraints
@@ -497,7 +497,7 @@ You can also constrain a route based on any method on the <a href="action_contro
You specify a request-based constraint the same way that you specify a segment constraint:
<ruby>
-match "photos", :constraints => {:subdomain => "admin"}
+get "photos", :constraints => {:subdomain => "admin"}
</ruby>
You can also specify constraints in a block form:
@@ -526,17 +526,28 @@ class BlacklistConstraint
end
TwitterClone::Application.routes.draw do
- match "*path" => "blacklist#index",
+ get "*path" => "blacklist#index",
:constraints => BlacklistConstraint.new
end
</ruby>
+You can also specify constraints as a lambda:
+
+<ruby>
+TwitterClone::Application.routes.draw do
+ get "*path" => "blacklist#index",
+ :constraints => lambda { |request| Blacklist.retrieve_ips.include?(request.remote_ip) }
+end
+</ruby>
+
+Both the +matches?+ method and the lambda gets the +request+ object as an argument.
+
h4. Route Globbing
Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example
<ruby>
-match 'photos/*other' => 'photos#unknown'
+get 'photos/*other' => 'photos#unknown'
</ruby>
This route would match +photos/12+ or +/photos/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+.
@@ -544,7 +555,7 @@ This route would match +photos/12+ or +/photos/long/path/to/12+, setting +params
Wildcard segments can occur anywhere in a route. For example,
<ruby>
-match 'books/*section/:title' => 'books#show'
+get 'books/*section/:title' => 'books#show'
</ruby>
would match +books/some/section/last-words-a-memoir+ with +params[:section]+ equals +"some/section"+, and +params[:title]+ equals +"last-words-a-memoir"+.
@@ -552,7 +563,7 @@ would match +books/some/section/last-words-a-memoir+ with +params[:section]+ equ
Technically a route can have even more than one wildcard segment. The matcher assigns segments to parameters in an intuitive way. For example,
<ruby>
-match '*a/foo/*b' => 'test#index'
+get '*a/foo/*b' => 'test#index'
</ruby>
would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +params[:b]+ equals +"bar/baz"+.
@@ -560,19 +571,19 @@ would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +par
NOTE: Starting from Rails 3.1, wildcard routes will always match the optional format segment by default. For example if you have this route:
<ruby>
-match '*pages' => 'pages#show'
+get '*pages' => 'pages#show'
</ruby>
NOTE: By requesting +"/foo/bar.json"+, your +params[:pages]+ will be equals to +"foo/bar"+ with the request format of JSON. If you want the old 3.0.x behavior back, you could supply +:format => false+ like this:
<ruby>
-match '*pages' => 'pages#show', :format => false
+get '*pages' => 'pages#show', :format => false
</ruby>
NOTE: If you want to make the format segment mandatory, so it cannot be omitted, you can supply +:format => true+ like this:
<ruby>
-match '*pages' => 'pages#show', :format => true
+get '*pages' => 'pages#show', :format => true
</ruby>
h4. Redirection
@@ -580,20 +591,20 @@ h4. Redirection
You can redirect any path to another path using the +redirect+ helper in your router:
<ruby>
-match "/stories" => redirect("/posts")
+get "/stories" => redirect("/posts")
</ruby>
You can also reuse dynamic segments from the match in the path to redirect to:
<ruby>
-match "/stories/:name" => redirect("/posts/%{name}")
+get "/stories/:name" => redirect("/posts/%{name}")
</ruby>
You can also provide a block to redirect, which receives the params and (optionally) the request object:
<ruby>
-match "/stories/:name" => redirect {|params| "/posts/#{params[:name].pluralize}" }
-match "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" }
+get "/stories/:name" => redirect {|params| "/posts/#{params[:name].pluralize}" }
+get "/stories" => redirect {|p, req| "/posts/#{req.subdomain}" }
</ruby>
Please note that this redirection is a 301 "Moved Permanently" redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible.
@@ -605,10 +616,10 @@ h4. Routing to Rack Applications
Instead of a String, like +"posts#index"+, which corresponds to the +index+ action in the +PostsController+, you can specify any <a href="rails_on_rack.html">Rack application</a> as the endpoint for a matcher.
<ruby>
-match "/application.js" => Sprockets
+match "/application.js" => Sprockets, :via => :all
</ruby>
-As long as +Sprockets+ responds to +call+ and returns a <tt>[status, headers, body]</tt>, the router won't know the difference between the Rack application and an action.
+As long as +Sprockets+ responds to +call+ and returns a <tt>[status, headers, body]</tt>, the router won't know the difference between the Rack application and an action. This is an appropriate use of +:via => :all+, as you will want to allow your Rack application to handle all verbs as it considers appropriate.
NOTE: For the curious, +"posts#index"+ actually expands out to +PostsController.action(:index)+, which returns a valid Rack application.
@@ -618,9 +629,12 @@ You can specify what Rails should route +"/"+ to with the +root+ method:
<ruby>
root :to => 'pages#main'
+root 'pages#main' # shortcut for the above
</ruby>
-You should put the +root+ route at the end of the file. You also need to delete the +public/index.html+ file for the root route to take effect.
+You should put the +root+ route at the top of the file, because it is the most popular route and should be matched first. You also need to delete the +public/index.html+ file for the root route to take effect.
+
+NOTE: The +root+ route only routes +GET+ requests to the action.
h3. Customizing Resourceful Routes
@@ -642,7 +656,7 @@ will recognize incoming paths beginning with +/photos+ but route to the +Images+
|POST |/photos |create | photos_path |
|GET |/photos/:id |show | photo_path(:id) |
|GET |/photos/:id/edit |edit | edit_photo_path(:id) |
-|PUT |/photos/:id |update | photo_path(:id) |
+|PATCH/PUT |/photos/:id |update | photo_path(:id) |
|DELETE |/photos/:id |destroy | photo_path(:id) |
NOTE: Use +photos_path+, +new_photo_path+, etc. to generate paths for this resource.
@@ -655,7 +669,7 @@ You can use the +:constraints+ option to specify a required format on the implic
resources :photos, :constraints => {:id => /[A-Z][A-Z][0-9]+/}
</ruby>
-This declaration constraints the +:id+ parameter to match the supplied regular expression. So, in this case, the router would no longer match +/photos/1+ to this route. Instead, +/photos/RR27+ would match.
+This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, the router would no longer match +/photos/1+ to this route. Instead, +/photos/RR27+ would match.
You can specify a single constraint to apply to a number of routes by using the block form:
@@ -686,7 +700,7 @@ will recognize incoming paths beginning with +/photos+ and route the requests to
|POST |/photos |create | images_path |
|GET |/photos/:id |show | image_path(:id) |
|GET |/photos/:id/edit |edit | edit_image_path(:id) |
-|PUT |/photos/:id |update | image_path(:id) |
+|PATCH/PUT |/photos/:id |update | image_path(:id) |
|DELETE |/photos/:id |destroy | image_path(:id) |
h4. Overriding the +new+ and +edit+ Segments
@@ -790,7 +804,7 @@ Rails now creates routes to the +CategoriesController+.
|POST |/kategorien |create | categories_path |
|GET |/kategorien/:id |show | category_path(:id) |
|GET |/kategorien/:id/bearbeiten |edit | edit_category_path(:id) |
-|PUT |/kategorien/:id |update | category_path(:id) |
+|PATCH/PUT |/kategorien/:id |update | category_path(:id) |
|DELETE |/kategorien/:id |destroy | category_path(:id) |
h4. Overriding the Singular Form
@@ -815,6 +829,24 @@ end
This will create routing helpers such as +magazine_periodical_ads_url+ and +edit_magazine_periodical_ad_path+.
+h3. Breaking Up a Large Route File
+
+If you have a large route file that you would like to break up into multiple files, you can use the +#draw+ method in your router:
+
+<ruby>
+draw :admin
+</ruby>
+
+Then, create a file called +config/routes/admin.rb+. Name the file the same as the symbol passed to the +draw+ method. You can then use the normal routing DSL inside that file:
+
+<ruby>
+# in config/routes/admin.rb
+
+namespace :admin do
+ resources :posts
+end
+</ruby>
+
h3. Inspecting and Testing Routes
Rails offers facilities for inspecting and testing your routes.
diff --git a/railties/guides/source/ruby_on_rails_guides_guidelines.textile b/guides/source/ruby_on_rails_guides_guidelines.textile
index 29aefd25f8..f3e934d38c 100644
--- a/railties/guides/source/ruby_on_rails_guides_guidelines.textile
+++ b/guides/source/ruby_on_rails_guides_guidelines.textile
@@ -1,20 +1,20 @@
h2. Ruby on Rails Guides Guidelines
-This guide documents guidelines for writing guides. This guide follows itself in a gracile loop.
+This guide documents guidelines for writing Ruby on Rails Guides. This guide follows itself in a graceful loop, serving itself as an example.
endprologue.
h3. Textile
-Guides are written in "Textile":http://www.textism.com/tools/textile/. There's comprehensive documentation "here":http://redcloth.org/hobix.com/textile/ and a cheatsheet for markup "here":http://redcloth.org/hobix.com/textile/quick.html.
+Guides are written in "Textile":http://www.textism.com/tools/textile/. There is comprehensive "documentation":http://redcloth.org/hobix.com/textile/ and a "cheatsheet":http://redcloth.org/hobix.com/textile/quick.html for markup.
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 reader what the guide is about, and what they will 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.
+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:
@@ -31,7 +31,7 @@ h6. The <tt>:content_type</tt> Option
h3. API Documentation Guidelines
-The guides and the API should be coherent where appropriate. Please have a look at these particular sections of the "API Documentation Guidelines":api_documentation_guidelines.html:
+The guides and the API should be coherent and consistent where appropriate. Please have a look at these particular sections of the "API Documentation Guidelines":api_documentation_guidelines.html:
* "Wording":api_documentation_guidelines.html#wording
* "Example Code":api_documentation_guidelines.html#example-code
@@ -40,9 +40,11 @@ The guides and the API should be coherent where appropriate. Please have a look
Those guidelines apply also to guides.
-h3. HTML Generation
+h3. HTML Guides
-To generate all the guides, just +cd+ into the +railties+ directory and execute:
+h4. Generation
+
+To generate all the guides, just +cd+ into the *+guides+* directory and execute:
<plain>
bundle exec rake generate_guides
@@ -53,22 +55,23 @@ bundle exec rake generate_guides
To process +my_guide.textile+ and nothing else use the +ONLY+ environment variable:
<plain>
+touch my_guide.textile
bundle exec rake generate_guides ONLY=my_guide
</plain>
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+.
+To force processing 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.
-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 +GUIDES_LANGUAGE+ environment variable:
+If you want to generate guides in a language other than English, you can keep them in a separate directory under +source+ (eg. <tt>source/es</tt>) and use the +GUIDES_LANGUAGE+ environment variable:
<plain>
bundle exec rake generate_guides GUIDES_LANGUAGE=es
</plain>
-h3. HTML Validation
+h4. Validation
Please validate the generated HTML with:
@@ -76,4 +79,14 @@ Please validate the generated HTML with:
bundle exec rake validate_guides
</plain>
-Particularly, titles get an ID generated from their content and this often leads to duplicates. Please set +WARNINGS=1+ when generating guides to detect them. The warning messages suggest a way to fix them.
+Particularly, titles get an ID generated from their content and this often leads to duplicates. Please set +WARNINGS=1+ when generating guides to detect them. The warning messages suggest a solution.
+
+h3. Kindle Guides
+
+h4(#generation-kindle). Generation
+
+To generate guides for the Kindle, you need to provide +KINDLE=1+ as an environment variable:
+
+<plain>
+KINDLE=1 bundle exec rake generate_guides
+</plain>
diff --git a/railties/guides/source/security.textile b/guides/source/security.textile
index 8837e06de5..ac55d60368 100644
--- a/railties/guides/source/security.textile
+++ b/guides/source/security.textile
@@ -1,7 +1,6 @@
h2. Ruby On Rails Security Guide
-This manual describes common security problems in web applications and how to avoid them with Rails. If you have any questions or suggestions, please
-mail me, Heiko Webers, at 42 {_et_} rorsecurity.info. After reading it, you should be familiar with:
+This manual describes common security problems in web applications and how to avoid them with Rails. After reading it, you should be familiar with:
* All countermeasures _(highlight)that are highlighted_
* The concept of sessions in Rails, what to put in there and popular attack methods
@@ -82,9 +81,9 @@ This will also be a good idea, if you modify the structure of an object and old
h4. Session Storage
--- _Rails provides several storage mechanisms for the session hashes. The most important are SessionStore and CookieStore._
+-- _Rails provides several storage mechanisms for the session hashes. The most important are ActiveRecord::SessionStore and ActionDispatch::Session::CookieStore._
-There are a number of session storages, i.e. where Rails saves the session hash and session id. Most real-live applications choose SessionStore (or one of its derivatives) over file storage due to performance and maintenance reasons. SessionStore keeps the session id and hash in a database table and saves and retrieves the hash on every request.
+There are a number of session storages, i.e. where Rails saves the session hash and session id. Most real-live applications choose ActiveRecord::SessionStore (or one of its derivatives) over file storage due to performance and maintenance reasons. ActiveRecord::SessionStore keeps the session id and hash in a database table and saves and retrieves the hash on every request.
Rails 2 introduced a new default session storage, CookieStore. CookieStore saves the session hash directly in a cookie on the client-side. The server retrieves the session hash from the cookie and eliminates the need for a session id. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications of it:
@@ -374,7 +373,7 @@ end
Mass-assignment saves you much work, because you don't have to set each value individually. Simply pass a hash to the +new+ method, or +assign_attributes=+ a hash value, to set the model's attributes to the values in the hash. The problem is that it is often used in conjunction with the parameters (params) hash available in the controller, which may be manipulated by an attacker. He may do so by changing the URL like this:
<pre>
-"name":http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1
+http://www.example.com/user/signup?user[name]=ow3ned&user[admin]=1
</pre>
This will set the following parameters in the controller:
@@ -385,7 +384,7 @@ params[:user] # => {:name => “ow3ned”, :admin => true}
So if you create a new user using mass-assignment, it may be too easy to become an administrator.
-Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3+. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example:
+Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the <tt>attributes=</tt> method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example:
<ruby>
class Person < ActiveRecord::Base
@@ -474,7 +473,7 @@ h3. User Management
-- _Almost every web application has to deal with authorization and authentication. Instead of rolling your own, it is advisable to use common plug-ins. But keep them up-to-date, too. A few additional precautions can make your application even more secure._
-There are some authorization and authentication plug-ins for Rails available. A good one saves only encrypted passwords, not plain-text passwords. The most popular plug-in is +restful_authentication+ which protects from session fixation, too. However, earlier versions allowed you to login without user name and password in certain circumstances.
+There are a number of authentication plug-ins for Rails available. Good ones, such as the popular "devise":https://github.com/plataformatec/devise and "authlogic":https://github.com/binarylogic/authlogic, store only encrypted passwords, not plain-text passwords. In Rails 3.1 you can use the built-in +has_secure_password+ method which has similar features.
Every new user gets an activation code to activate his account when he gets an e-mail with a link in it. After activating the account, the activation_code columns will be set to NULL in the database. If someone requested an URL like these, he would be logged in as the first activated user found in the database (and chances are that this is the administrator):
@@ -628,7 +627,7 @@ h4. Whitelists versus Blacklists
-- _When sanitizing, protecting or verifying something, whitelists over blacklists._
-A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although, sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _(highlight)prefer to use whitelist approaches_:
+A blacklist can be a list of bad e-mail addresses, non-public actions or bad HTML tags. This is opposed to a whitelist which lists the good e-mail addresses, public actions, good HTML tags and so on. Although sometimes it is not possible to create a whitelist (in a SPAM filter, for example), _(highlight)prefer to use whitelist approaches_:
* Use before_filter :only => [...] instead of :except => [...]. This way you don't forget to turn it off for newly added actions.
* Use attr_accessible instead of attr_protected. See the mass-assignment section for details
diff --git a/railties/guides/source/testing.textile b/guides/source/testing.textile
index 2341a3522c..d35be6a70e 100644
--- a/railties/guides/source/testing.textile
+++ b/guides/source/testing.textile
@@ -114,25 +114,18 @@ Rails by default automatically loads all fixtures from the +test/fixtures+ folde
* Load the fixture data into the table
* Dump the fixture data into a variable in case you want to access it directly
-h5. Hashes with Special Powers
+h5. Fixtures are ActiveRecord objects
-Fixtures are basically Hash objects. As mentioned in point #3 above, you can access the hash object directly because it is automatically setup as a local variable of the test case. For example:
+Fixtures are instances of ActiveRecord. As mentioned in point #3 above, you can access the object directly because it is automatically setup as a local variable of the test case. For example:
<ruby>
-# this will return the Hash for the fixture named david
+# this will return the User object for the fixture named david
users(:david)
# this will return the property for david called id
users(:david).id
-</ruby>
-
-Fixtures can also transform themselves into the form of the original class. Thus, you can get at the methods only available to that class.
-
-<ruby>
-# using the find method, we grab the "real" david as a User
-david = users(:david).find
-# and now we have access to methods only available to a User class
+# one can also access methods available on the User class
email(david.girlfriend.email, david.location_tonight)
</ruby>
@@ -419,7 +412,7 @@ NOTE: +assert_valid(record)+ has been deprecated. Please use +assert(record.vali
|+assert_no_difference(expressions, message = nil, &amp;block)+ |Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.|
|+assert_recognizes(expected_options, path, extras={}, message=nil)+ |Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.|
|+assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)+ |Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.|
-|+assert_response(type, message = nil)+ |Asserts that the response comes with a specific status code. You can specify +:success+ to indicate 200, +:redirect+ to indicate 300-399, +:missing+ to indicate 404, or +:error+ to match the 500-599 range|
+|+assert_response(type, message = nil)+ |Asserts that the response comes with a specific status code. You can specify +:success+ to indicate 200-299, +:redirect+ to indicate 300-399, +:missing+ to indicate 404, or +:error+ to match the 500-599 range|
|+assert_redirected_to(options = {}, message=nil)+ |Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that +assert_redirected_to(:controller => "weblog")+ will also match the redirection of +redirect_to(:controller => "weblog", :action => "show")+ and so on.|
|+assert_template(expected = nil, message=nil)+ |Asserts that the request was rendered with the appropriate template file.|
@@ -490,10 +483,11 @@ Now you can try running all the tests and they should pass.
h4. Available Request Types for Functional Tests
-If you're familiar with the HTTP protocol, you'll know that +get+ is a type of request. There are 5 request types supported in Rails functional tests:
+If you're familiar with the HTTP protocol, you'll know that +get+ is a type of request. There are 6 request types supported in Rails functional tests:
* +get+
* +post+
+* +patch+
* +put+
* +head+
* +delete+
@@ -530,6 +524,44 @@ You also have access to three instance variables in your functional tests:
* +@request+ - The request
* +@response+ - The response
+h4. Testing Templates and Layouts
+
+If you want to make sure that the response rendered the correct template and layout, you can use the +assert_template+
+method:
+
+<ruby>
+test "index should render correct template and layout" do
+ get :index
+ assert_template :index
+ assert_template :layout => "layouts/application"
+end
+</ruby>
+
+Note that you cannot test for template and layout at the same time, with one call to +assert_template+ method.
+Also, for the +layout+ test, you can give a regular expression instead of a string, but using the string, makes
+things clearer. On the other hand, you have to include the "layouts" directory name even if you save your layout
+file in this standard layout directory. Hence,
+
+<ruby>
+assert_template :layout => "application"
+</ruby>
+
+will not work.
+
+If your view renders any partial, when asserting for the layout, you have to assert for the partial at the same time.
+Otherwise, assertion will fail.
+
+Hence:
+
+<ruby>
+test "new should render correct layout" do
+ get :new
+ assert_template :layout => "layouts/application", :partial => "_form"
+end
+</ruby>
+
+is the correct way to assert for the layout when the view renders a partial with name +_form+. Omitting the +:partial+ key in your +assert_template+ call will complain.
+
h4. A Fuller Functional Test Example
Here's another example that uses +flash+, +assert_redirected_to+, and +assert_difference+:
@@ -645,6 +677,7 @@ In addition to the standard testing helpers, there are some additional helpers a
|+request_via_redirect(http_method, path, [parameters], [headers])+ |Allows you to make an HTTP request and follow any subsequent redirects.|
|+post_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP POST request and follow any subsequent redirects.|
|+get_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP GET request and follow any subsequent redirects.|
+|+patch_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP PATCH request and follow any subsequent redirects.|
|+put_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP PUT request and follow any subsequent redirects.|
|+delete_via_redirect(path, [parameters], [headers])+ |Allows you to make an HTTP DELETE request and follow any subsequent redirects.|
|+open_session+ |Opens a new session instance.|
@@ -738,7 +771,6 @@ You don't need to set up and run your tests by hand on a test-by-test basis. Rai
|+rake test:benchmark+ |Benchmark the performance tests|
|+rake test:functionals+ |Runs all the functional tests from +test/functional+|
|+rake test:integration+ |Runs all the integration tests from +test/integration+|
-|+rake test:plugins+ |Run all the plugin tests from +vendor/plugins/*/**/test+ (or specify with +PLUGIN=_name_+)|
|+rake test:profile+ |Profile the performance tests|
|+rake test:recent+ |Tests recent changes|
|+rake test:uncommitted+ |Runs all the tests which are uncommitted. Supports Subversion and Git|
@@ -818,7 +850,7 @@ class PostsControllerTest < ActionController::TestCase
end
test "should update post" do
- put :update, :id => @post.id, :post => { }
+ patch :update, :id => @post.id, :post => { }
assert_redirected_to post_path(assigns(:post))
end
@@ -927,7 +959,7 @@ class UserControllerTest < ActionController::TestCase
assert_difference 'ActionMailer::Base.deliveries.size', +1 do
post :invite_friend, :email => 'friend@example.com'
end
- invite_email = ActionMailer::Base.deliveries.first
+ invite_email = ActionMailer::Base.deliveries.last
assert_equal "You have been invited by me@example.com", invite_email.subject
assert_equal 'friend@example.com', invite_email.to[0]
diff --git a/guides/source/upgrading_ruby_on_rails.textile b/guides/source/upgrading_ruby_on_rails.textile
new file mode 100644
index 0000000000..2b2e65c813
--- /dev/null
+++ b/guides/source/upgrading_ruby_on_rails.textile
@@ -0,0 +1,198 @@
+h2. A Guide for Upgrading Ruby on Rails
+
+This guide provides steps to be followed when you upgrade your applications to a newer version of Ruby on Rails. These steps are also available in individual release guides.
+
+endprologue.
+
+h3. General Advice
+
+Before attempting to upgrade an existing application, you should be sure you have a good reason to upgrade. You need to balance out several factors: the need for new features, the increasing difficulty of finding support for old code, and your available time and skills, to name a few.
+
+h4(#general_testing). Test Coverage
+
+The best way to be sure that your application still works after upgrading is to have good test coverage before you start the process. If you don't have automated tests that exercise the bulk of your application, you'll need to spend time manually exercising all the parts that have changed. In the case of a Rails upgrade, that will mean every single piece of functionality in the application. Do yourself a favor and make sure your test coverage is good _before_ you start an upgrade.
+
+h4(#general_ruby). Ruby Versions
+
+Rails generally stays close to the latest released Ruby version when it's released:
+
+* Rails 3 and above requires Ruby 1.8.7 or higher. Support for all of the previous Ruby versions has been dropped officially and you should upgrade as early as possible.
+* Rails 3.2.x will be the last branch to support Ruby 1.8.7.
+* Rails 4 will support only Ruby 1.9.3.
+
+TIP: Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails. Ruby Enterprise Edition has these fixed since the release of 1.8.7-2010.02. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults, so if you want to use 1.9.x, jump on to 1.9.2 or 1.9.3 for smooth sailing.
+
+h3. Upgrading from Rails 3.2 to Rails 4.0
+
+NOTE: This section is a work in progress.
+
+If your application is currently on any version of Rails older than 3.2.x, you should upgrade to Rails 3.2 before attempting an update to Rails 4.0.
+
+The following changes are meant for upgrading your application to Rails 4.0.
+
+h4(#plugins4_0). vendor/plugins
+
+Rails 4.0 no longer supports loading plugins from <tt>vendor/plugins</tt>. You must replace any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, <tt>lib/my_plugin/*</tt> and add an appropriate initializer in <tt>config/initializers/my_plugin.rb</tt>.
+
+h4(#identity_map4_0). IdentityMap
+
+Rails 4.0 has removed <tt>IdentityMap</tt> from <tt>ActiveRecord</tt>, due to "some inconsistencies with associations":https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. If you have manually enabled it in your application, you will have to remove the following config that has no effect anymore: <tt>config.active_record.identity_map</tt>.
+
+h4(#active_model4_0). ActiveModel
+
+Rails 4.0 has changed how errors attach with the ConfirmationValidator. Now when confirmation validations fail the error will be attached to <tt>:#{attribute}_confirmation</tt> instead of <tt>attribute</tt>.
+
+h3. Upgrading from Rails 3.1 to Rails 3.2
+
+If your application is currently on any version of Rails older than 3.1.x, you should upgrade to Rails 3.1 before attempting an update to Rails 3.2.
+
+The following changes are meant for upgrading your application to Rails 3.2.2, the latest 3.2.x version of Rails.
+
+h4(#gemfile3_2). Gemfile
+
+Make the following changes to your +Gemfile+.
+
+<ruby>
+gem 'rails', '= 3.2.2'
+
+group :assets do
+ gem 'sass-rails', '~> 3.2.3'
+ gem 'coffee-rails', '~> 3.2.1'
+ gem 'uglifier', '>= 1.0.3'
+end
+</ruby>
+
+h4(#config_dev3_2). config/environments/development.rb
+
+There are a couple of new configuration settings that you should add to your development environment:
+
+<ruby>
+# Raise exception on mass assignment protection for Active Record models
+config.active_record.mass_assignment_sanitizer = :strict
+
+# Log the query plan for queries taking more than this (works
+# with SQLite, MySQL, and PostgreSQL)
+config.active_record.auto_explain_threshold_in_seconds = 0.5
+</ruby>
+
+h4(#config_test3_2). config/environments/test.rb
+
+The <tt>mass_assignment_sanitizer</tt> configuration setting should also be be added to <tt>config/environments/test.rb</tt>:
+
+<ruby>
+# Raise exception on mass assignment protection for Active Record models
+config.active_record.mass_assignment_sanitizer = :strict
+</ruby>
+
+h4(#plugins3_2). vendor/plugins
+
+Rails 3.2 deprecates <tt>vendor/plugins</tt> and Rails 4.0 will remove them completely. While it's not strictly necessary as part of a Rails 3.2 upgrade, you can start replacing any plugins by extracting them to gems and adding them to your Gemfile. If you choose not to make them gems, you can move them into, say, <tt>lib/my_plugin/*</tt> and add an appropriate initializer in <tt>config/initializers/my_plugin.rb</tt>.
+
+h3. Upgrading from Rails 3.0 to Rails 3.1
+
+If your application is currently on any version of Rails older than 3.0.x, you should upgrade to Rails 3.0 before attempting an update to Rails 3.1.
+
+The following changes are meant for upgrading your application to Rails 3.1.3, the latest 3.1.x version of Rails.
+
+h4(#gemfile3_1). Gemfile
+
+Make the following changes to your +Gemfile+.
+
+<ruby>
+gem 'rails', '= 3.1.3'
+gem 'mysql2'
+
+# Needed for the new asset pipeline
+group :assets do
+ gem 'sass-rails', "~> 3.1.5"
+ gem 'coffee-rails', "~> 3.1.1"
+ gem 'uglifier', ">= 1.0.3"
+end
+
+# jQuery is the default JavaScript library in Rails 3.1
+gem 'jquery-rails'
+</ruby>
+
+h4(#config_app3_1). config/application.rb
+
+The asset pipeline requires the following additions:
+
+<ruby>
+config.assets.enabled = true
+config.assets.version = '1.0'
+</ruby>
+
+If your application is using an "/assets" route for a resource you may want change the prefix used for assets to avoid conflicts:
+
+<ruby>
+# Defaults to '/assets'
+config.assets.prefix = '/asset-files'
+</ruby>
+
+h4(#config_dev3_1). config/environments/development.rb
+
+Remove the RJS setting <tt>config.action_view.debug_rjs = true</tt>.
+
+Add these settings if you enable the asset pipeline:
+
+<ruby>
+# Do not compress assets
+config.assets.compress = false
+
+# Expands the lines which load the assets
+config.assets.debug = true
+</ruby>
+
+h4(#config_prod3_1). config/environments/production.rb
+
+Again, most of the changes below are for the asset pipeline. You can read more about these in the "Asset Pipeline":asset_pipeline.html guide.
+
+<ruby>
+# Compress JavaScripts and CSS
+config.assets.compress = true
+
+# Don't fallback to assets pipeline if a precompiled asset is missed
+config.assets.compile = false
+
+# Generate digests for assets URLs
+config.assets.digest = true
+
+# Defaults to Rails.root.join("public/assets")
+# config.assets.manifest = YOUR_PATH
+
+# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+# config.assets.precompile += %w( search.js )
+
+# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+# config.force_ssl = true
+</ruby>
+
+h4(#config_test3_1). config/environments/test.rb
+
+You can help test performance with these additions to your test environment:
+
+<ruby>
+# Configure static asset server for tests with Cache-Control for performance
+config.serve_static_assets = true
+config.static_cache_control = "public, max-age=3600"
+</ruby>
+
+h4(#config_wp3_1). config/initializers/wrap_parameters.rb
+
+Add this file with the following contents, if you wish to wrap parameters into a nested hash. This is on by default in new applications.
+
+<ruby>
+# Be sure to restart your server when you modify this file.
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters :format => [:json]
+end
+
+# Disable root element in JSON by default.
+ActiveSupport.on_load(:active_record) do
+ self.include_root_in_json = false
+end
+</ruby>
diff --git a/railties/guides/w3c_validator.rb b/guides/w3c_validator.rb
index f1fe1e0f33..5e340499c4 100644
--- a/railties/guides/w3c_validator.rb
+++ b/guides/w3c_validator.rb
@@ -26,7 +26,6 @@
#
# ---------------------------------------------------------------------------
-require 'rubygems'
require 'w3c_validators'
include W3CValidators
@@ -76,7 +75,7 @@ module RailsGuides
error_summary += "\n #{name}"
error_detail += "\n\n #{name} has #{errors.size} validation error(s):\n"
errors.each do |error|
- error_detail += "\n "+error.to_s.gsub("\n", "")
+ error_detail += "\n "+error.to_s.delete("\n")
end
end
diff --git a/install.rb b/install.rb
index 05bba27a14..b87b008c2e 100644
--- a/install.rb
+++ b/install.rb
@@ -1,6 +1,6 @@
version = ARGV.pop
-%w( activesupport activemodel activerecord activeresource actionpack actionmailer railties ).each do |framework|
+%w( activesupport activemodel activerecord actionpack actionmailer railties ).each do |framework|
puts "Installing #{framework}..."
`cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-ri --no-rdoc && rm #{framework}-#{version}.gem`
end
@@ -8,4 +8,4 @@ end
puts "Installing Rails..."
`gem build rails.gemspec`
`gem install rails-#{version}.gem --no-ri --no-rdoc `
-`rm rails-#{version}.gem` \ No newline at end of file
+`rm rails-#{version}.gem`
diff --git a/load_paths.rb b/load_paths.rb
index 17f5ce180d..0ad8fcfeda 100644
--- a/load_paths.rb
+++ b/load_paths.rb
@@ -1,4 +1,3 @@
# bust gem prelude
-require 'rubygems' unless defined? Gem
require 'bundler'
-Bundler.setup \ No newline at end of file
+Bundler.setup
diff --git a/rails.gemspec b/rails.gemspec
index 3377b4e175..8314036ad1 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -7,21 +7,22 @@ Gem::Specification.new do |s|
s.summary = 'Full-stack web application framework.'
s.description = 'Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration.'
- s.required_ruby_version = '>= 1.8.7'
- s.required_rubygems_version = ">= 1.3.6"
+ s.required_ruby_version = '>= 1.9.3'
+ s.required_rubygems_version = ">= 1.8.11"
- s.author = 'David Heinemeier Hansson'
- s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.author = 'David Heinemeier Hansson'
+ s.email = 'david@loudthinking.com'
+ s.homepage = 'http://www.rubyonrails.org'
- s.bindir = 'bin'
- s.executables = []
+ s.bindir = 'bin'
+ s.executables = []
+ s.files = Dir['guides/**/*']
- s.add_dependency('activesupport', version)
- s.add_dependency('actionpack', version)
- s.add_dependency('activerecord', version)
- s.add_dependency('activeresource', version)
- s.add_dependency('actionmailer', version)
- s.add_dependency('railties', version)
- s.add_dependency('bundler', '~> 1.0')
+ s.add_dependency('activesupport', version)
+ s.add_dependency('actionpack', version)
+ s.add_dependency('activerecord', version)
+ s.add_dependency('actionmailer', version)
+ s.add_dependency('railties', version)
+ s.add_dependency('bundler', '~> 1.1')
+ s.add_dependency('sprockets-rails', '~> 1.0')
end
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
deleted file mode 100644
index 187dd2428f..0000000000
--- a/railties/CHANGELOG
+++ /dev/null
@@ -1,2386 +0,0 @@
-*Rails 3.2.0 (unreleased)*
-
-* Default options to `rails new` can be set in ~/.railsrc [Guillermo Iguaran]
-
-* Added destroy alias to Rails engines. [Guillermo Iguaran]
-
-* Added destroy alias for Rails command line. This allows the following: `rails d model post`. [Andrey Ognevsky]
-
-* Attributes on scaffold and model generators default to string. This allows the following: "rails g scaffold Post title body:text author" [José Valim]
-
-* Removed old plugin generator (`rails generate plugin`) in favor of `rails plugin new` command. [Guillermo Iguaran]
-
-* Removed old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API. [Guillermo Iguaran]
-
-
-*Rails 3.1.1
-
-* Add jquery-rails to Gemfile of plugins, test/dummy app needs it. Closes #3091. [Santiago Pastorino]
-
-* Add config.assets.initialize_on_precompile which, when set to false, forces
- `rake assets:precompile` to load the application but does not initialize it.
-
- To the app developer, this means configuration add in
- config/initializers/* will not be executed.
-
- Plugins developers need to special case their initializers that are
- meant to be run in the assets group by adding :group => :assets.
-
-
-*Rails 3.1.0 (August 30, 2011)*
-
-* The default database schema file is written as UTF-8. [Aaron Patterson]
-
-* Generated apps with --dev or --edge flags depend on git versions of
-sass-rails and coffee-rails. [Santiago Pastorino]
-
-* Rack::Sendfile middleware is used only if x_sendfile_header is present. [Santiago Pastorino]
-
-* Add JavaScript Runtime name to the Rails Info properties. [DHH]
-
-* Make pp enabled by default in Rails console. [Akira Matsuda]
-
-* Add alias `r` for rails runner. [Jordi Romero]
-
-* Make sprockets/railtie require explicit and add --skip-sprockets to app generator [José Valim]
-
-* Added Rails.groups that automatically handles Rails.env and ENV["RAILS_GROUPS"] [José Valim]
-
-* The new rake task assets:clean removes precompiled assets. [fxn]
-
-* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. [fxn]
-
-* Fixed database tasks for jdbc* adapters #jruby [Rashmi Yadav]
-
-* Template generation for jdbcpostgresql #jruby [Vishnu Atrai]
-
-* Template generation for jdbcmysql and jdbcsqlite3 #jruby [Arun Agrawal]
-
-* The -j option of the application generator accepts an arbitrary string. If passed "foo",
-the gem "foo-rails" is added to the Gemfile, and the application JavaScript manifest
-requires "foo" and "foo_ujs". As of this writing "prototype-rails" and "jquery-rails"
-exist and provide those files via the asset pipeline. Default is "jquery". [fxn]
-
-* jQuery is no longer vendored, it is provided from now on by the jquery-rails gem. [fxn]
-
-* Prototype and Scriptaculous are no longer vendored, they are provided from now on
-by the prototype-rails gem. [fxn]
-
-* 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, application, and mailer 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]
-
-* Added `config.force_ssl` configuration which loads Rack::SSL middleware and force all requests to be under HTTPS protocol [DHH, Prem Sichanugrist, and Josh Peek]
-
-* Added `rails plugin new` command which generates rails plugin with gemspec, tests and dummy application for testing [Piotr Sarnacki]
-
-* Added -j parameter with jquery/prototype as options. Now you can create your apps with jQuery using `rails new myapp -j jquery`. The default is still Prototype. [siong1987]
-
-* Added Rack::Etag and Rack::ConditionalGet to the default middleware stack [José Valim]
-
-* Added Rack::Cache to the default middleware stack [Yehuda Katz and Carl Lerche]
-
-* Engine is now rack application [Piotr Sarnacki]
-
-* Added middleware stack to Engine [Piotr Sarnacki]
-
-* Engine can now load plugins [Piotr Sarnacki]
-
-* Engine can load its own environment file [Piotr Sarnacki]
-
-* Added helpers to call engines' route helpers from application and vice versa [Piotr Sarnacki]
-
-* Task for copying plugins' and engines' migrations to application's db/migrate directory [Piotr Sarnacki]
-
-* Changed ActionDispatch::Static to allow handling multiple directories [Piotr Sarnacki]
-
-* Added isolate_namespace() method to Engine, which sets Engine as isolated [Piotr Sarnacki]
-
-* Include all helpers from plugins and shared engines in application [Piotr Sarnacki]
-
-
-*Rails 3.0.7 (April 18, 2011)*
-
-*No changes.
-
-
-*Rails 3.0.6 (April 5, 2011)
-
-* No changes.
-
-
-*Rails 3.0.5 (February 26, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.4 (February 8, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.3 (November 16, 2010)*
-
-* No changes.
-
-
-*Rails 3.0.2 (November 15, 2010)*
-
-* No changes.
-
-
-*Rails 3.0.1 (October 15, 2010)*
-
-* No Changes, just a version bump.
-
-
-*Rails 3.0.0 (August 29, 2010)*
-
-* Application generation: --skip-testunit and --skip-activerecord become --skip-test-unit and --skip-active-record respectively. [fxn]
-
-* Added console to Rails::Railtie as a hook called just after console starts. [José Valim]
-
-* Rails no longer autoload code in lib for application. You need to explicitly require it. [José Valim]
-
-* Rails::LogSubscriber was renamed to ActiveSupport::LogSubscriber [José Valim]
-
-* config.load_(once_)paths in config/application.rb got renamed to config.autoload_(once_)paths. [fxn]
-
-* Abort generation/booting on Ruby 1.9.1. [fxn]
-
-* Made the rails command work even when you're in a subdirectory [Chad Fowler]
-
-* Removed Rails Metal [Yehuda Katz, José Valim].
-
-* Renamed config.cookie_secret to config.secret_token and pass it as env key. [José Valim]
-
-* Session store configuration has changed [Yehuda Katz, Carl Lerche]
-
- config.session_store :cookie_store, {:key => "..."}
- config.cookie_secret = "fdsfhisdghfidugnfdlg"
-
-* railtie_name and engine_name are deprecated. You can now add any object to
- the configuration object: config.your_plugin = {} [José Valim]
-
-* Added config.generators.templates to provide alternative paths for the generators
- to look for templates [José Valim]
-
-* Added "rake about" as a replacement for script/about [DHH]
-
-* Removed all the default commands in script/* and replaced them with script/rails and a rails command that'll act the same when run from within the app [DHH]. Example:
-
- ./script/generate scaffold post title:string can now be called as rails g scaffold post title:string
-
- Run rails --help inside an app for more help.
-
-* Removed config/initializers/new_rails_defaults.rb as all frameworks now follow the settings from it [DHH]
-
-* Set config.time_zone to UTC by default [DHH]
-
-* Added default .gitignore (this is just recognizing Git market share, don't throw a hissy if you use another SCM) [DHH]
-
-* Added cookies.permanent, cookies.signed, and cookies.permanent.signed accessor for common cookie actions [DHH]. Examples:
-
- cookies.permanent[:prefers_open_id] = true
- # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
-
- cookies.signed[:discount] = 45
- # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
-
- cookies.signed[:discount]
- # => 45 (if the cookie was changed, you'll get a InvalidSignature exception)
-
- cookies.permanent.signed[:remember_me] = current_user.id
- # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
-
- ...to use the signed cookies, you need to set a secret to ActionController::Base.cookie_verifier_secret (automatically done in config/initializers/cookie_verification_secret.rb for new Rails applications).
-
-* Added config/initializers/cookie_verification_secret.rb with an auto-generated secret for using ActionController::Base#cookies.signed [DHH]
-
-* Fixed that the debugger wouldn't go into IRB mode because of left-over ARGVs [DHH]
-
-* I18n support for plugins. #2325 [Antonio Tapiador, Sven Fuchs]
-
-* Ruby 1.9: use UTF-8 for default internal and external encodings. [Jeremy Kemper]
-
-* Added db/seeds.rb as a default file for storing seed data for the database. Can be loaded with rake db:seed (or created alongside the db with db:setup). (This is also known as the "Stop Putting Gawd Damn Seed Data In Your Migrations" feature) [DHH]
-
-
-*2.3.2 [Final] (March 15, 2009)*
-
-* Remove outdated script/plugin options [Pratik Naik]
-
-* Allow metal to live in plugins #2045 [Matthew Rudy]
-
-* Added metal [Josh Peek]
-
-* Remove script/performance/request in favour of the performance integration tests. [Pratik Naik]
-
- To continue using script/performance/request, install the request_profiler plugin :
-
- script/plugin install git://github.com/rails/request_profiler.git
-
-* Add a rake task to apply a template to an existing application : rake rails:template LOCATION=~/template.rb [Pratik Naik]
-
-* Add "-m/--template" option to Rails generator to apply a template to the generated application. [Jeremy McAnally]
-
- This has been extracted from rg - http://github.com/jeremymcanally/rg
-
- Example:
-
- # template.rb
-
- # Install plugins from git or svn
- plugin "will-paginate", :git => "git://github.com/mislav/will_paginate.git"
- plugin "old-restful-auth", :svn => "http://svn.techno-weenie.net/projects/plugins/restful_authentication/"
-
- # Add gems to environment.rb
- gem "jeremymcanally-context"
- gem "bluecloth"
-
- # Vendor file. Data in a string or...
- vendor("borrowed.rb", <<CODE
- def helpful_method
- do_something_helpful_here
- end
- CODE
-
- # ...file data from block return value.
- # #initializer creates a new initializer file
- initializer("crypto.rb") do
- salt = "--#{Time.now}--#{rand}--#{srand(Time.now.to_i)}"
-
- "SPECIAL_SALT = '#{salt}'"
- end
-
- Usage:
-
- To use a template, provide a file path or URL:
-
- 1. Using a local file :
-
- rails <application name> -m /path/to/my/template.rb
-
- 2. Or directly from a URL :
-
- rails <application name> --template=http://gist.github.com/31208.txt
-
-* Extracted the process scripts (inspector, reaper, spawner) into the plugin irs_process_scripts [David Heinemeier Hansson]
-
-* Changed Rails.root to return a Pathname object (allows for Rails.root.join('app', 'controllers') => "#{RAILS_ROOT}/app/controllers") #1482 [Damian Janowski/?]
-
-* Added view path support for engines [David Heinemeier Hansson]
-
-* Added that config/routes.rb files in engine plugins are automatically loaded (and reloaded when they change in dev mode) [David Heinemeier Hansson]
-
-* Added app/[models|controllers|helpers] to the load path for plugins that has an app directory (go engines ;)) [David Heinemeier Hansson]
-
-* Add config.preload_frameworks to load all frameworks at startup. Default to false so Rails autoloads itself as it's used. Turn this on for Passenger and JRuby. Also turned on by config.threadsafe! [Jeremy Kemper]
-
-* Add a rake task to generate dispatchers : rake rails:generate_dispatchers [Pratik Naik]
-
-* "rails <app>" will not generate public/dispatch.cgi/fcgi/rb files by default now. Please use "--with-dispatchers" option if you need them. [Yaroslav Markin, Pratik Naik]
-
-* Added rake rails:update:application_controller to renamed application.rb to application_controller.rb -- included in rake rails:update so upgrading to 2.3 will automatically trigger it #1439 [kastner]
-
-* Added Rails.backtrace_cleaner as an accessor for the Rails::BacktraceCleaner instance used by the framework to cut down on backtrace noise and config/initializers/backtrace_silencers.rb to add your own (or turn them all off) [David Heinemeier Hansson]
-
-* Switch from Test::Unit::TestCase to ActiveSupport::TestCase. [Jeremy Kemper]
-
-* Added config.i18n settings gatherer to config/environment, auto-loading of all locales in config/locales/*.rb,yml, and config/locales/en.yml as a sample locale [David Heinemeier Hansson]
-
-* BACKWARDS INCOMPATIBLE: Renamed application.rb to application_controller.rb and removed all the special casing that was in place to support the former. You must do this rename in your own application when you upgrade to this version [David Heinemeier Hansson]
-
-
-*2.2.1 [RC2] (November 14th, 2008)*
-
-* Fixed plugin generator so that generated unit tests would subclass ActiveSupport::TestCase, also introduced a helper script to reduce the needed require statements #1137 [Mathias Meyer]
-
-* Update Prototype to 1.6.0.3 [sam]
-
-
-*2.2.0 [RC1] (October 24th, 2008)*
-
-* Fixed that sqlite would report "db/development.sqlite3 already exists" whether true or not on db:create #614 [Antonio Cangiano]
-
-* Added config.threadsafe! to toggle allow concurrency settings and disable the dependency loader [Josh Peek]
-
-* Turn cache_classes on by default [Josh Peek]
-
-* Added configurable eager load paths. Defaults to app/models, app/controllers, and app/helpers [Josh Peek]
-
-* Introduce simple internationalization support. [Ruby i18n team]
-
-* Make script/plugin install <plugin> -r <revision> option work with git based plugins. #257. [Tim Pope Jakub Kuźma]. Example:
-
- script/plugin install git://github.com/mislav/will_paginate.git -r agnostic # Installs 'agnostic' branch
- script/plugin install git://github.com/dchelimsky/rspec.git -r 'tag 1.1.4'
-
-* Added Rails.initialized? flag [Josh Peek]
-
-* Make rake test:uncommitted work with Git. [Tim Pope]
-
-* Added Thin support to script/server. #488 [Bob Klosinski]
-
-* Fix script/about in production mode. #370 [Cheah Chu Yeow, Xavier Noria, David Krmpotic]
-
-* Add the gem load paths before the framework is loaded, so certain gems like RedCloth and BlueCloth can be frozen.
-
-* Fix discrepancies with loading rails/init.rb from gems.
-
-* Plugins check for the gem init path (rails/init.rb) before the standard plugin init path (init.rb) [Jacek Becela]
-
-* Changed all generated tests to use the test/do declaration style [David Heinemeier Hansson]
-
-* Wrapped Rails.env in StringInquirer so you can do Rails.env.development? [David Heinemeier Hansson]
-
-* Fixed that RailsInfoController wasn't considering all requests local in development mode (Edgard Castro) [#310 state:resolved]
-
-
-*2.1.0 (May 31st, 2008)*
-
-* script/dbconsole fires up the command-line database client. #102 [Steve Purcell]
-
-* Fix bug where plugin init.rb files from frozen gem specs weren't being run. (pjb3) [#122 state:resolved]
-
-* Made the location of the routes file configurable with config.routes_configuration_file (Scott Fleckenstein) [#88]
-
-* Rails Edge info returns the latest git commit hash [Francesc Esplugas]
-
-* Added Rails.public_path to control where HTML and assets are expected to be loaded from (defaults to Rails.root + "/public") #11581 [Nick Sieger]
-
-* rake time:zones:local finds correct base utc offset for zones in the Southern Hemisphere [Geoff Buesing]
-
-* Don't require rails/gem_builder during rails initialization, it's only needed for the gems:build task. [Rick Olson]
-
-* script/performance/profiler compatibility with the ruby-prof >= 0.5.0. Closes #9176. [Jonathan del Strother]
-
-* Flesh out rake gems:unpack to unpack all gems, and add rake gems:build for native extensions. #11513 [ddollar]
-
- rake gems:unpack # unpacks all gems
- rake gems:unpack GEM=mygem # unpacks only the gem 'mygem'
-
- rake gems:build # builds all unpacked gems
- rake gems:build GEM=mygem # builds only the gem 'mygem'
-
-* Add config.active_support for future configuration options. Also, add more new Rails 3 config settings to new_rails_defaults.rb [Rick Olson]
-
-* Add Rails.logger, Rails.root, Rails.env and Rails.cache shortcuts for RAILS_* constants [Pratik Naik]
-
-* Allow files in plugins to be reloaded like the rest of the application. [Rick Olson]
-
- Enables or disables plugin reloading.
-
- config.reload_plugins = true
-
- You can get around this setting per plugin.
- If #reload_plugins? == false (DEFAULT), add this to your plugin's init.rb to make it reloadable:
-
- Dependencies.load_once_paths.delete lib_path
-
- If #reload_plugins? == true, add this to your plugin's init.rb to only load it once:
-
- Dependencies.load_once_paths << lib_path
-
-* Small tweak to allow plugins to specify gem dependencies. [Rick Olson]
-
- # OLD open_id_authentication plugin init.rb
- require 'yadis'
- require 'openid'
- ActionController::Base.send :include, OpenIdAuthentication
-
- # NEW
- config.gem "ruby-openid", :lib => "openid", :version => "1.1.4"
- config.gem "ruby-yadis", :lib => "yadis", :version => "0.3.4"
-
- config.after_initialize do
- ActionController::Base.send :include, OpenIdAuthentication
- end
-
-* Added config.gem for specifying which gems are required by the application, as well as rake tasks for installing and freezing gems. [Rick Olson]
-
- Rails::Initializer.run do |config|
- config.gem "bj"
- config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
- config.gem "aws-s3", :lib => "aws/s3"
- end
-
- # List required gems.
- rake gems
-
- # Install all required gems:
- rake gems:install
-
- # Unpack specified gem to vendor/gems/gem_name-x.x.x
- rake gems:unpack GEM=bj
-
-* Removed the default .htaccess configuration as there are so many good deployment options now (kept it as an example in README) [David Heinemeier Hansson]
-
-* config.time_zone accepts TZInfo::Timezone identifiers as well as Rails TimeZone identifiers [Geoff Buesing]
-
-* Rails::Initializer#initialize_time_zone raises an error if value assigned to config.time_zone is not recognized. Rake time zone tasks only require ActiveSupport instead of entire environment [Geoff Buesing]
-
-* Stop adding the antiquated test/mocks/* directories and only add them to the path if they're still there for legacy reasons [David Heinemeier Hansson]
-
-* Added that gems can now be plugins if they include rails/init.rb #11444 [John Barnette]
-
-* Added Plugin#about method to programmatically access the about.yml in a plugin #10979 [James Adam]
-
- plugin = Rails::Plugin.new(path_to_my_plugin)
- plugin.about["author"] # => "James Adam"
- plugin.about["url"] # => "http://interblah.net"
-
-* Improve documentation. [Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert]
-
-* Added config.time_zone = 'UTC' in the default environment.rb [Geoff Buesing]
-
-* Added rake tasks time:zones:all, time:zones:us and time:zones:local for finding time zone names for config.time_zone option [Geoff Buesing]
-
-* Add config.time_zone for configuring the default Time.zone value. #10982 [Geoff Buesing]
-
-* Added support for installing plugins hosted at git repositories #11294 [Jack Danger Canty]
-
-* Fixed that script/generate would not look for plugin generators in plugin_paths #11000 [glv]
-
-* Fixed database rake tasks to work with charset/collation and show proper error messages on failure. Closes #11301 [matt]
-
-* Added a -e/--export to script/plugin install, uses svn export. #10847 [jon@blankpad.net)]
-
-* Reshuffle load order so that routes and observers are initialized after plugins and app initializers. Closes #10980 [Rick Olson]
-
-* Git support for script/generate. #10690 [ssoroka]
-
-* Update scaffold to use labels instead of bold tags. Closes #10757 [zach-inglis-lt3]
-
-* Resurrect WordNet synonym lookups. #10710 [tom./, matt]
-
-* Added config.cache_store to environment options to control the default cache store (default is FileStore if tmp/cache is present, otherwise MemoryStore is used) [David Heinemeier Hansson]
-
-* Added that rails:update is run when you do rails:freeze:edge to ensure you also get the latest JS and config files #10565 [jeff]
-
-* SQLite: db:drop:all doesn't fail silently if the database is already open. #10577 [Cheah Chu Yeow, mrichman]
-
-* Introduce native mongrel handler and push mutex into dispatcher. [Jeremy Kemper]
-
-* Ruby 1.9 compatibility. #1689, #10546 [Cheah Chu Yeow, frederico]
-
-
-*2.0.2* (December 16th, 2007)
-
-* Changed the default database from mysql to sqlite3, so now running "rails myapp" will have a config/database.yml that's setup for SQLite3 (which in OS X Leopard is installed by default, so is the gem, so everything Just Works with no database configuration at all). To get a Rails application preconfigured for MySQL, just run "rails -d mysql myapp" [David Heinemeier Hansson]
-
-* Turned on ActionView::Base.cache_template_loading by default in config/environments/production.rb to prevent file system stat calls for every template loading to see if it changed (this means that you have to restart the application to see template changes in production mode) [David Heinemeier Hansson]
-
-* Introduce `rake secret` to output a crytographically secure secret key for use with cookie sessions #10363 [revans]
-
-* Fixed that local database creation should consider 127.0.0.1 local #9026 [parcelbrat]
-
-* Fixed that functional tests generated for scaffolds should use fixture calls instead of hard-coded IDs #10435 [boone]
-
-* Added db:migrate:redo and db:migrate:reset for rerunning existing migrations #10431, #10432 [matt]
-
-* RAILS_GEM_VERSION may be double-quoted also. #10443 [James Cox]
-
-* Update rails:freeze:gems to work with RubyGems 0.9.5. [Jeremy Kemper]
-
-
-*2.0.1* (December 7th, 2007)
-
-* Fixed Active Record bug
-
-
-*2.0.0* (December 6th, 2007)
-
-* The test task stops with a warning if you have pending migrations. #10377 [Josh Knowles]
-
-* Add warning to documentation about using transactional fixtures when the code under test uses transactions itself. Closes #7548 [Thijs van der Vossen]
-
-* Update Prototype to 1.6.0.1. [sam]
-
-* Update script.aculo.us to 1.8.0.1. [madrobby]
-
-* Added db:fixtures:identity as a way of locating what ID a foxy fixture was assigned #10332 [John Barnette]
-
-* Generated fixtures should not specify ids since theyre expected to be foxy fixtures #10330 [John Barnette]
-
-* Update to Prototype -r8232. [sam]
-
-* Introduce SecretKeyGenerator for more secure session secrets than CGI::Session's pseudo-random id generator. Consider extracting to Active Support later. #10286 [Hongli Lai (Phusion)]
-
-* RAILS_GEM_VERSION may be set to any valid gem version specifier. #10057 [Chad Woolley, Cheah Chu Yeow]
-
-* Load config/preinitializer.rb, if present, before loading the environment. #9943 [Chad Woolley]
-
-* FastCGI handler ignores unsupported signals like USR2 on Windows. [Grzegorz Derebecki]
-
-* Only load ActionMailer::TestCase if ActionMailer is loaded. Closes #10137 [defunkt]
-
-* Fixed that db:reset would use migrations instead of loading db/schema.rb [David Heinemeier Hansson]
-
-* Ensure the plugin loader only loads plugins once. Closes #10102 [haruki_zaemon]
-
-* Refactor Plugin Loader. Add plugin lib paths early, and add lots of tests. Closes #9795 [James Adam]
-
-* Added --skip-timestamps to generators that produce models #10036 [Tim Pope]
-
-* Update Prototype to 1.6.0 and script.aculo.us to 1.8.0. [sam, madrobby]
-
-* Added db:rollback to rollback the schema one version (or multiple as specified by STEP) [Jeffrey Allan Hardy]
-
-* Fix typo in test_helper. Closes #9925 [viktor tron]
-
-* Request profiler. [Jeremy Kemper]
-
-* config/boot.rb correctly detects RAILS_GEM_VERSION. #9834 [alexch, thewoolleyman]
-
-* Fixed incorrect migration number if script/generate executed outside of Rails root #7080 [Jeremy McAnally]
-
-* Update Prototype to 1.6.0_rc1 and script.aculo.us to 1.8.0 preview 0. [sam, madrobby]
-
-* Generated fixtures use the actual primary key instead of id. #4343 [Frederick Ros, Tarmo Tänav]
-
-* Extend the console +helper+ method to allow you to include custom helpers. e.g:
- >> helper :posts
- >> helper.some_method_from_posts_helper(Post.find(1))
-
-* db:create works with remote databases whereas db:create:all only creates
-databases on localhost. #9753 [Trevor Wennblom]
-
-* Removed calls to fixtures in generated tests as fixtures :all is now present by default in test_helper.rb [David Heinemeier Hansson]
-
-* Add --prefix option to script/server when using mongrel. [dacat]
-
-
-*2.0.0 [Preview Release]* (September 29th, 2007) [Includes duplicates of changes from 1.1.4 - 1.2.3]
-
-* Fixed that installing plugins from SVN repositories that use trunk/ will work #8188 [evan]
-
-* Moved the SourceAnnotationExtractor to a separate file in case libraries try to load the rails rake tasks twice. [Rick Olson]
-
-* Moved Dispatcher to ActionController::Dispatcher. [Jeremy Kemper]
-
-* Changed the default logger from Ruby's own Logger with the clean_logger extensions to ActiveSupport::BufferedLogger for performance reasons [David Heinemeier Hansson]. (You can change it back with config.logger = Logger.new("/path/to/log", level).)
-
-* Added a default 422.html page to be rendered when ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved, or ActionController::InvalidAuthenticityToken is raised [David Heinemeier Hansson]
-
-* Added --skip-fixture option to script/generate model #6862 [sandofsky]
-
-* Print Rails version when starting console #7440 [eyematz]
-
-* Fixed the placement of fixture files for nested models when generating through script/generate model #7547 [jkit]
-
-* Added TEMPLATE option to rake doc:app to set a custom output template #7737 [Jakob Skjerning]
-
-* Added VERBOSE option to rake db:migrate to turn off output #8204 [John Barnette]
-
-* Fixed that rake doc:app should use UTF-8 #8906 [farzy]
-
-* Fixes rake annotations to search erb and builder files as well #9150 [m.langenberg]
-
-* Removed web_service generator [Michael Koziarski]
-
-* Added the :all option to config.plugins that'll include the rest of the plugins not already explicitly named #9613 [Frederick Cheung]. Example:
-
- # Loads :classic_pagination before all the other plugins
- config.plugins = [ :classic_pagination, :all ]
-
-* Added symbols as a legal way of specifying plugins in config.plugins #9629 [tom]
-
-* Removed deprecated task names, like clear_logs, in favor of the new namespaced style [David Heinemeier Hansson]
-
-* Support multiple config.after_initialize blocks so plugins and apps can more easily cooperate. #9582 [Zach Dennis]
-
-* Added db:drop:all to drop all databases declared in config/database.yml [David Heinemeier Hansson]
-
-* Use attribute pairs instead of the migration name to create add and remove column migrations. Closes #9166 [Pratik Naik]
-
- For example:
-
- ruby script/generation migration AddSomeStuffToCustomers first_name:string last_name:string
-
- or
-
- ruby script/generation migration RemoveSomeStuffFromCustomers first_name:string last_name:string
-
-* Add ActiveResource to Rails::Info. Closes #8741 [Chris Kampmeier]
-
-* use Gem.find_name instead of search when freezing gems. Prevent false positives for other gems with rails in the name. Closes #8729 [wselman]
-
-* Automatically generate add/remove column commands in specially named migrations like AddLocationToEvent. Closes #9006 [Ryan Davis]
-
-* Default to plural table name in Rails Generator if ActiveRecord is not present. Closes #8963 [evan]
-
-* Added rake routes for listing all the defined routes in the system. #8795 [Josh Peek]
-
-* db:create creates the database for the current environment if it's on localhost. db:create:all creates local databases for all environments. #8783 [matt]
-
-* Generators: look for generators in all gems, not just those suffixed with _generator, in the gem's generators or rails_generators directory. Allow use of the rails_generators directory instead of the standard generators directory in plugins also. #8730 [Dr Nic, topfunky]
-
-* MySQL, PostgreSQL: database.yml defaults to utf-8. #8701 [matt]
-
-* Added db:version to get the current schema number [via Err The Blog]
-
-* Added --skip-migration option to scaffold and resource generators #8656 [Michael Glaesemann]
-
-* Fix that FCGIs would leave log files open when asked to shut down by USR2. #3028 [Sebastian Kanthak, Josh Peek]
-
-* Fixed that dispatcher preparation callbacks only run once in production mode. Mock Routes.reload so that dispatcher preparation callback tests run. [Rick Olson]
-
-* Fix syntax error in dispatcher than wrecked failsafe responses. #8625 [mtitorenko]
-
-* Scaffolded validation errors set the appropriate HTTP status for XML responses. #6946, #8622 [Manfred Stienstra, mmmultiworks]
-
-* Sexy migrations for the session_migration generator. #8561 [Vladislav]
-
-* Console reload! runs to_prepare callbacks also. #8393 [f.svehla]
-
-* Generated migrations include timestamps by default. #8501 [Shane Vitarana]
-
-* Drop Action Web Service from rails:freeze:edge. [Jeremy Kemper]
-
-* Add db:create, drop, reset, charset, and collation tasks. #8448 [matt]
-
-* Scaffold generator depends on model generator instead of duplicating it. #7222 [bscofield]
-
-* Scaffold generator tests. #8443 [pelle]
-
-* Generated scaffold functional tests use assert_difference. #8421 [Norbert Crombach]
-
-* Update to Prototype 1.5.1. [Sam Stephenson]
-
-* Update to script.aculo.us 1.7.1_beta3. [Thomas Fuchs]
-
-* Generators use *.html.erb view template naming. #8278 [Tim Pope]
-
-* Updated resource_scaffold and model generators to use short-hand style migrations [David Heinemeier Hansson]
-
-* Updated initializer to only load #{RAILS_ENV}.rb once. Added deprecation warning for config.breakpoint_server. [Nicholas Seckar]
-
-* Removed breakpointer and Binding.of_caller in favor of relying on ruby-debug by Kent Sibilev since the breakpointer has been broken since Ruby 1.8.4 and will not be coming back [David Heinemeier Hansson]
-
- To use the new debugger, start your server with script/server --debugger and insert a call to 'debugger'
- (instead of 'breakpoint') where you want to jump into the debugger.
-
- BACKWARDS INCOMPATIBILITY NOTE: You must remove the default line 12 from config/environments/development.rb:
-
- config.breakpoint_server = true
-
- This configuration option is no longer available. Rails will fail to start in development mode as long as
- that's still present.
-
-* Resource scaffolding returns the created entity.to_xml. [Jeremy Kemper]
-
-* Resource scaffolding responds to new.xml. #8185 [Eric Mill]
-
-* Include Active Resource in rails:freeze:edge rake task. [Thomas Fuchs]
-
-* Include Active Resource instead of Action Web Service [David Heinemeier Hansson] You can add AWS back with this in config/environment.rb:
-
- config.load_paths += %W( #{RAILS_ROOT}/vendor/rails/actionwebservice/lib )
-
- ...or just gem 'actionwebservice'
-
-* Give generate scaffold a more descriptive database message. Closes #7316 [Jeremy McAnally]
-
-* Canonicalize RAILS_ROOT by using File.expand_path on Windows, which doesn't have to worry about symlinks, and Pathname#realpath elsewhere, which respects symlinks in relative paths but is incompatible with Windows. #6755 [Jeremy Kemper, trevor]
-
-* Deprecation: remove components from controller paths. [Jeremy Kemper]
-
-* Add environment variable RAILS_DEFAULT_DATABASE, which allows the builtin default of 'mysql' to be overridden. [Nicholas Seckar]
-
-* Windows: include MinGW in RUBY_PLATFORM check. #2982 [okkez000@gmail.com, Kaspar Schiess]
-
-* Split out the basic plugin locator functionality into an abstract super class. Add a FileSystemLocator to do the job of checking the plugin_paths for plugins. Add plugin_locators configuration option which will iterate over the set of plugin locators and load each of the plugin loaders they return. Rename locater everywhere to locator. [Marcel Molina Jr.]
-
-* Split plugin location and loading out of the initializer and into a new Plugin namespace, which includes Plugin::Locater and Plugin::Loader. The loader class that is used can be customized using the config.plugin_loader option. Those monkey patching the plugin loading subsystem take note, the internals changing here will likely break your modifications. The good news is that it should be substantially easier to hook into the plugin locating and loading process now. [Marcel Molina Jr.]
-
-* Added assumption that all plugin creators desire to be sharing individuals and release their work under the MIT license [David Heinemeier Hansson]
-
-* Added source-annotations extractor tasks to rake [Jamis Buck]. This allows you to add FIXME, OPTIMIZE, and TODO comments to your source code that can then be extracted in concert with rake notes (shows all), rake notes:fixme, rake notes:optimize and rake notes:todo.
-
-* Added fixtures :all to test_helper.rb to assume that most people just want all their fixtures loaded all the time [David Heinemeier Hansson]
-
-* Added config/initializers where all ruby files within it are automatically loaded after the Rails configuration is done, so you don't have to litter the environment.rb file with a ton of mixed stuff [David Heinemeier Hansson]
-
-* For new apps, generate a random secret for the cookie-based session store. [Jeremy Kemper]
-
-* Stop swallowing errors during rake test [Michael Koziarski]
-
-* Update Rails Initializer to use ActionController::Base#view_paths [Rick Olson]
-
-* Fix gem deprecation warnings, which also means depending on RubyGems 0.9.0+ [Chad Fowler]
-
-* Plugins may be symlinked in vendor/plugins. #4245 [brandon, progrium@gmail.com]
-
-* Resource generator depends on the model generator rather than duplicating it. #7269 [bscofield]
-
-* Add/Update usage documentation for script/destroy, resource generator and scaffold_resource generator. Closes #7092, #7271, #7267. [bscofield]
-
-* Update to script.aculo.us 1.7.0. [Thomas Fuchs]
-
-* Update to Prototype 1.5.0. [Sam Stephenson]
-
-* Generator: use destination path for diff tempfiles. #7015 [alfeld]
-
-* Fixed that webrick would strip leading newlines and hang connection #4156 [psross]
-
-* Ensure plugins are in the Dependencies.load_once_paths collection by default. [Rick Olson]
- If you really want your plugins to reload, add this to the very top of init.rb:
-
- Dependencies.load_once_paths.delete(lib_path)
-
-* Allow config.to_prepare to work, make the dispatcher safe to 're require'. [Michael Koziarski, Nicholas Seckar]
-
-* Fix scaffold_resource generator so it respects the --pretend argument when creating the routes file. Closes #6852 [fearoffish]
-
-* Fix Webrick Daemon dispatching bug regarding a bad current working directory. Closes #4899 [Rick Olson]
-
-* Make config.plugins affect the load path and the dependencies system. Allows you to control plugin loading order, and keep disabled plugins off the load path. [James Adam]
-
-* Don't generate a components directory in new Rails apps. [Jeremy Kemper]
-
-* Fixed script/process/spawner to work properly with Mongrel including in -r (daemonize mode) [David Heinemeier Hansson]
-
-* Added one-letter aliases for the three default environments to script/console, so script/console p will load the production environment (t for test, d for development) [David Heinemeier Hansson]
-
-* Fixed that script/server running against Mongrel should tail the proper log regardless of the environment [David Heinemeier Hansson]
-
-* Update initializer to load Rails::VERSION as soon as possible. Closes #6698. [Nicholas Seckar]
-
-* Added ActiveRecord::Base.clear_active_connections! in development mode so the database connection is not carried over from request to request. Some databases won't reread the schema if that doesn't happen (I'm looking at you SQLite), so you have to restart the server after each migration (= no fun) [David Heinemeier Hansson]
-
-* Made RAILS_GEM_VERSION work for beta gems too, so specifying 1.1.6 will give you 1.1.6.4520 if available [David Heinemeier Hansson]
-
-* Update to Prototype and script.aculo.us [5579]. [Thomas Fuchs]
-
-* Made script/server work with -e and -d when using Mongrel [David Heinemeier Hansson]
-
-* Update to Prototype 1.5.0_rc2 [5550] which makes it work in Opera again [Thomas Fuchs]
-
-* Make sure that exceptions which are thrown outside of the user code try their best to be handeled in ApplicationController#rescue_action [Tobias Lütke]
-
-* Rails::VERSION::STRING should always be available without having to require 'rails/version'. #6244 [fearoffish]
-
-* Update to Prototype 1.5.0_rc2. [Sam Stephenson]
-
-* Add grep-based fallback to reaper, to work in pidless setups [Jamis Buck]
-
-* Only wrap request processing with our USR1 signal handler so FastCGI can trap it and raise an exception while waiting for connections. Idle processes exit immediately rather than waiting for another request; active processes gracefully exit when the request is finished. [Jeremy Kemper]
-
-* Alter prior change to use require_dependency instead of require_or_load. Causes ApplicationController to be reloaded again. Closes #6587. [Nicholas Seckar]
-
-* Rake: use absolute paths to load lib and vendor tasks so they may be run outside of RAILS_ROOT. #6584 [jchris]
-
-* Remove temporary crutch to help ApplicationController be unloaded. Closes #6496. [Nicholas Seckar]
-
-* scaffold_resource generator uses _path named routes and head instead of render :nothing => true. #6545 [Josh Susser]
-
-* Generator can show diff on file collision to help you decide whether to skip or overwrite. #6364 [jeffw, Jeremy Kemper]
-
-* Generated directories are recursively svn added, like mkdir -p. #6416 [NeilW]
-
-* resource and scaffold_resource generators add a restful route to config/routes.rb [Jeremy Kemper]
-
-* Revert environment changes for autoload_paths. [Michael Koziarski]
-
-* Update to latest Prototype, which doesn't serialize disabled form elements, adds clone() to arrays, empty/non-string Element.update() and adds a fixes excessive error reporting in WebKit beta versions [Thomas Fuchs]
-
-* Clean up the output of rake stats, de-emphasise components and apis, and remove the indents for tests [Michael Koziarski]
-
-* Added option to script/process/spawner of specifying the binding address #5133 [Dee Zsombor]
-
-* Update environment.rb comments to include config.autoload_paths. Closes #6478 [caio]
-
-* Update scaffold to use new form_tag block functionality. Closes #6480. [Bob Silva]
-
-* Plugin generator: check for class collisions. #4833 [vinbarnes@gmail.com]
-
-* Mailer generator: handle mailers in modules, set mime_version in unit test. [Jeremy Kemper]
-
-* Set $KCODE to 'u' by default to enable the multibyte safe String#chars proxy. [Michael Koziarski]
-
-* Added config.plugins to control which plugins are loaded #6269 [Stefan Kaes]. By default, everything in vendor/plugins will be loaded, but if you specify config.plugins, only those will be loaded. Example:
-
- config.plugins = %w[ routing_navigator simply_helpful ]
-
-* Clean up html on included error pages. [Tim Lucas]
-
-* Fixed default 404.html and 500.htmls to remove extreme ugliness and include human language [David Heinemeier Hansson]
-
-* Update to latest Prototype and script.aculo.us trunk versions [Thomas Fuchs]
-
-* PostgreSQL: db:test:purge closes open database connections first. #6236 [alex]
-
-* Fixed test:uncommitted on Windows (backslash issue) #4999 [paul@paulbutcher.com]
-
-* Fixed migration creation to work with namespaced models, so script/generate model Gallery::Image will use create_table :gallery_images #6327 [Bob Silva]
-
-* Fixed rename_table on SQLite tables with indexes defined #5942 [brandon@opensoul.org]
-
-* Added default timeout setting of 5 seconds to SQLite3 database.yml configurations [David Heinemeier Hansson]
-
-* Added generated attribute options to script/generate model, like the one found in scaffold_resource and resource [David Heinemeier Hansson]. Examples:
-
- ./script/generate model post title:string created_on:date body:text published:boolean
-
-* Added script/generate resource which works just like scaffold_resource, but creates empty placeholders instead of predefined [David Heinemeier Hansson]
-
-* script/runner can run files, pass on arguments, and be used as a shebang. #6286 [Tuxie, dlpond]
- #!/usr/bin/env /path/to/my/app/script/runner
- # Example: just start using your models as if you are in script/console
- Product.find(:all).each { |product| product.check_inventory }
-
-* Look for rake tasks in plugin subdirs. #6259 [obrie]
-
-* Added map.connect ':controller/:action/:id.:format' as a default route to config/routes.rb [David Heinemeier Hansson]
-
-* Updated prototype.js to 1.5.0_rc1 with latest fixes. [Rick Olson]
-
- - XPATH support
- - Make Form.getElements() return elements in the correct order
- - fix broken Form.serialize return
-
-* session_migration generator adds an index on updated_at. #6207 [grg]
-
-* script/server creates the tmp/pids directory. #6204 [jonathan]
-
-* Fix script/console --sandbox for internal transactions changes. #5738 [Chris McGrath, charles.gerungan@gmail.com]
-
-* Remove the uncanny default of adding all app/models/*/ directories to the load path. This change will break application which expect the current behavior. As
-documented in initializer.rb, the workaround is:
-
- config.autoload_paths += Dir[RAILS_ROOT + '/app/models/*/']
-
-References #6031. [Nicholas Seckar]
-
-* Update to script.aculo.us 1.6.3 [Thomas Fuchs]
-
-* Update to Prototype 1.5.0_rc1 [sam]
-
-* Formally Deprecate the old rake tasks. [Michael Koziarski]
-
-* Thoroughly test the FCGI dispatcher. #5970 [Kevin Clark]
-
-* Remove Dir.chdir in the Webrick DispatchServlet#initialize method. Fix bad path errors when trying to load config/routes.rb. [Rick Olson]
-
-* Tighten rescue clauses. #5985 [james@grayproductions.net]
-
-* Cleaning up tests. [Kevin Clark, Jeremy Kemper]
-
-* Add Dependencies.load_once_paths. [Nicholas Seckar]
-
-* Updated to script.aculo.us 1.6.2 [Thomas Fuchs]
-
-* Assign Routing.controller_paths; fix script/about and rails info controller. [Nicholas Seckar]
-
-* Don't warn dispatcher of Reloadable deprecations. [Nicholas Seckar]
-
-* Rearrange application resetting and preparation, fix bug with leaking subclasses hash in ActiveRecord::Base [Rick Olson]
-
- ActiveRecord::Base.reset_subclasses is called before Dependencies are cleared and classes removed.
- ActiveRecord::Base.instantiate_observers is called during a Dispatcher preparation callback.
-
-* Add missing mock directories from the autoload_paths configuration. [Rick Olson]
-
-* Nested controller scaffolding also nests the generated layout. [iain d broadfoot]
-
-* Add "require 'dispatcher'" to webrick server in the continuing quest to squash webrick weirdness. [Nicholas Seckar]
-
-* Add autoload_paths support to Initializer. [Nicholas Seckar]
-
-* Fix Dispatcher.reset_application! so that AR subclasses are removed and Observers re-initialized *after* Reloadable classes are removed. Closes #5743. [Rick Olson]
-
-* Clarify usage of script/plugin source. Closes #5344. [James Adam]
-
-* Add Dispatcher.to_prepare and config.to_prepare to provide a pre-request hook. [Nicholas Seckar]
-
-* Tweak the Rails load order so observers are loaded after plugins, and reloaded in development mode. Closed #5279. [Rick Olson]
-
-* Added that you can change the web server port in config/lighttpd.conf from script/server --port/-p #5465 [mats@imediatec.co.uk]
-
-* script/performance/profiler compatibility with the new ruby-prof, including an option to choose the results printer. #5679 [Shugo Maeda]
-
-* Fixed the failsafe response so it uses either the current recognized controller or ApplicationController. [Rick Olson]
-
-* Make sure script/reaper only reaps dispatcher pids by default, and not the spawner's pid. [Jamis Buck]
-
-* Fix script/plugin about so it uses about.yml and not meta.yml. [James Adam]
-
-* Dispatcher processes rescued actions with the same controller that processed the request. #4625 [sd@notso.net]
-
-* rails -d frontbase to create a new project with a frontbase database.yml. #4945 [mlaster@metavillage.com]
-
-* Ensure the logger is initialized. #5629 [mike@clarkware.com]
-
-* Added Mongrel-spawning capabilities to script/process/spawner. Mongrel will be the default choice if installed, otherwise FCGI is tried [David Heinemeier Hansson]. Examples:
-
- spawner # starts instances on 8000, 8001, and 8002 using Mongrel if available
- spawner fcgi # starts instances on 8000, 8001, and 8002 using FCGI
- spawner mongrel -i 5 # starts instances on 8000, 8001, 8002, 8003, and 8004 using Mongrel
- spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to 9109 using Mongrel if available
- spawner -p 9100 -r 5 # starts 3 instances counting from 9100 to 9102 and attempts start them every 5 seconds
-
- Also note that script/process/reaper is Mongrel capable. So the combination of spawner and reaper is a built-in alternative to something like mongrel_cluster.
-
-* Update scaffolding functional tests to use :id => people(:first) instead of :id => 1. #5612 [evan@protest.net]
-
-* db:test:clone should remove existing tables before reloading the schema. #5607 [sveit@tradeharbor.com]
-
-* Fixed migration generation for class names like ACLController #5197 [brad@madriska.com]
-
-* Added show_source_list and show_call_stack to breakpoints to make it easier to get context #5476 [takiuchi@drecom.co.jp]. Examples:
-
- irb(#<TopController:0x40822a68>):002:0> show_source_list
- 0001 class TopController < ApplicationController
- 0002 def show
- 0003-> breakpoint
- 0004 end
- 0005
- 0006 def index
- 0007 end
- 0008
- => "/path/to/rails/root/app/controllers/top_controller.rb"
-
- irb(#<TopController:0x40822a68>):004:0> show_call_stack 3
- vendor/rails/railties/lib/breakpoint.rb:536:in `breakpoint'
- vendor/rails/railties/lib/breakpoint.rb:536:in `breakpoint'
- app/controllers/top_controller.rb:3:in `show'
- => "/path/to/rails/root/app/controllers/top_controller.rb:3"
-
-* Generate scaffold layout in subdirectory appropriate to its module nesting. #5511 [nils@alumni.rice.edu]
-
-* Mongrel: script/server tails the rails log like it does with lighttpd. Prefer mongrel over lighttpd. #5541 [mike@clarkware.com]
-
-* Don't assume Active Record is available. #5497 [bob@sporkmonger.com]
-
-* Mongrel: script/server works on Win32. #5499 [jeremydurham@gmail.com]
-
-* Remove opts.on { |options[:option_name] } style hash assignment. Closes #4440. [Nick Sieger]
-
-* Mongrel support for script/server. #5475 [jeremydurham@gmail.com]
-
-* Fix script/plugin so it doesn't barf on invalid URLs [Rick Olson]
-
-* Fix plugin install bug at dir with space. (closes #5359) [Yoshimasa NIWA]
-
-* Fix bug with 'script/plugin install' so it reports unknown plugin names correctly. [Rick Olson]
-
-* Added uninstall.rb hook to plugin handling, such that plugins have a way of removing assets and other artifacts on removal #5003 [takiuchi@drecom.co.jp]
-
-* Create temporary dirs relative to RAILS_ROOT when running script/server #5014 [elliot@townx.org]
-
-* Minor tweak to dispatcher to use recognize instead of recognize!, as per the new routes. [Jamis Buck]
-
-* Make "script/plugin install" work with svn+ssh URLs. [Sam Stephenson]
-
-* Added lib/ to the directories that will get application docs generated [David Heinemeier Hansson]
-
-* Add observer generator. Closes #5167. [François Beausoleil]
-
-* Session migration generator obeys pluralize_table_names. #5145 [James Adam]
-
-* rake test:recent understands subdirectories. #2925 [jerrett@bravenet.com]
-
-* The app generator detects the XAMPP package's MySQL socket location. #3832 [elliot@townx.org]
-
-* The app generator sets a session key in application.rb so apps running on the same host may distinguish their cookies. #2967 [rcoder, rails-bug@owl.me.uk]
-
-* Distinguish the spawners for different processes [David Heinemeier Hansson]
-
-* Added -n/--process to script/process/spawner name the process pid (default is dispatch) [David Heinemeier Hansson]
-
-* Namespaced OrderedHash so the Rails implementation does not clash with any others. (fixes #4911) [Julian Tarkhanov]
-
-* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
-
-* Added script/process/inspector to do simple process status information on Rails dispatchers keeping pid files in tmp/pids [David Heinemeier Hansson]
-
-* Added pid file usage to script/process/spawner and script/process/reaper along with a directive in default config/lighttpd.conf file to record the pid. They will all save their pid file in tmp/pids [David Heinemeier Hansson]
-
-
-*1.2.3* (March 12th, 2007)
-
-* Ruby 1.8.6 compatibility
-
-* Windows: include MinGW in RUBY_PLATFORM check. #2982 [okkez000@gmail.com, Kaspar Schiess]
-
-* Stop swallowing errors during rake test [Michael Koziarski]
-
-
-*1.2.2* (February 5th, 2007)
-
-* Fix gem deprecation warnings, which also means depending on RubyGems 0.9.0+ [Chad Fowler]
-
-* Require the dispatcher for Rails::Configuration#to_prepare. [Rick Olson]
-
-
-*1.2.1* (January 16th, 2007)
-
-* Updated to Active Record 1.15.1, Action Pack 1.13.1, Action Mailer 1.3.1, Action Web Service 1.2.1
-
-
-*1.2.0* (January 16th, 2007)
-
-* Update to Prototype 1.5.0. [Sam Stephenson]
-
-* Generator: use destination path for diff tempfiles. #7015 [alfeld]
-
-* Fixed that webrick would strip leading newlines and hang connection #4156 [psross]
-
-* Ensure plugins are in the Dependencies.load_once_paths collection by default. [Rick Olson]
- If you really want your plugins to reload, add this to the very top of init.rb:
-
- Dependencies.load_once_paths.delete(lib_path)
-
-* Fix scaffold_resource generator so it respects the --pretend argument when creating the routes file. Closes #6852 [fearoffish]
-
-* Fix Webrick Daemon dispatching bug regarding a bad current working directory. Closes #4899 [Rick Olson]
-
-* Make config.plugins affect the load path and the dependencies system. Allows you to control plugin loading order, and keep disabled plugins off the load path. [James Adam]
-
-* Don't generate a components directory in new Rails apps. [Jeremy Kemper]
-
-* Fixed script/process/spawner to work properly with Mongrel including in -r (daemonize mode) [David Heinemeier Hansson]
-
-* Deprecated the name route "root" as it'll be used as a shortcut for map.connect '' in Rails 2.0 [David Heinemeier Hansson]
-
-* Fixed that script/server running against Mongrel should tail the proper log regardless of the environment [David Heinemeier Hansson]
-
-* Update initializer to load Rails::VERSION as soon as possible. Closes #6698. [Nicholas Seckar]
-
-* Added ActiveRecord::Base.clear_active_connections! in development mode so the database connection is not carried over from request to request. Some databases won't reread the schema if that doesn't happen (I'm looking at you SQLite), so you have to restart the server after each migration (= no fun) [David Heinemeier Hansson]
-
-* Made RAILS_GEM_VERSION work for beta gems too, so specifying 1.1.6 will give you 1.1.6.4520 if available [David Heinemeier Hansson]
-
-* Update to Prototype and script.aculo.us [5579]. [Sam Stephenson, Thomas Fuchs]
-
-* Made script/server work with -e and -d when using Mongrel [David Heinemeier Hansson]
-
-* Make sure that exceptions which are thrown outside of the user code try their best to be handeled in ApplicationController#rescue_action [Tobias Lütke]
-
-* Rails::VERSION::STRING should always be available without having to require 'rails/version'. #6244 [fearoffish]
-
-* Add grep-based fallback to reaper, to work in pidless setups [Jamis Buck]
-
-* Only wrap request processing with our USR1 signal handler so FastCGI can trap it and raise an exception while waiting for connections. Idle processes exit immediately rather than waiting for another request; active processes gracefully exit when the request is finished. [Jeremy Kemper]
-
-* Alter prior change to use require_dependency instead of require_or_load. Causes ApplicationController to be reloaded again. Closes #6587. [Nicholas Seckar]
-
-* Rake: use absolute paths to load lib and vendor tasks so they may be run outside of RAILS_ROOT. #6584 [jchris]
-
-* scaffold_resource generator uses _path named routes and head instead of render :nothing => true. #6545 [Josh Susser]
-
-* Generator can show diff on file collision to help you decide whether to skip or overwrite. #6364 [jeffw, Jeremy Kemper]
-
-* Generated directories are recursively svn added, like mkdir -p. #6416 [NeilW]
-
-* resource and scaffold_resource generators add a restful route to config/routes.rb [Jeremy Kemper]
-
-* Revert environment changes for autoload_paths. [Michael Koziarski]
-
-* Clean up the output of rake stats, de-emphasise components and apis, and remove the indents for tests [Michael Koziarski]
-
-* Added option to script/process/spawner of specifying the binding address #5133 [Dee Zsombor]
-
-* Update environment.rb comments to include config.autoload_paths. Closes #6478 [caio]
-
-* Update scaffold to use new form_tag block functionality. Closes #6480. [Bob Silva]
-
-* Plugin generator: check for class collisions. #4833 [vinbarnes@gmail.com]
-
-* Mailer generator: handle mailers in modules, set mime_version in unit test. [Jeremy Kemper]
-
-* Set $KCODE to 'u' by default to enable the multibyte safe String#chars proxy. [Michael Koziarski]
-
-* Added config.plugins to control which plugins are loaded #6269 [Stefan Kaes]. By default, everything in vendor/plugins will be loaded, but if you specify config.plugins, only those will be loaded. Example:
-
- config.plugins = %w[ routing_navigator simply_helpful ]
-
-* Clean up html on included error pages. [Tim Lucas]
-
-* Fixed default 404.html and 500.htmls to remove extreme ugliness and include human language [David Heinemeier Hansson]
-
-* Update to latest Prototype and script.aculo.us trunk versions [Thomas Fuchs]
-
-* PostgreSQL: db:test:purge closes open database connections first. #6236 [alex]
-
-* Fixed test:uncommitted on Windows (backslash issue) #4999 [paul@paulbutcher.com]
-
-* Fixed migration creation to work with namespaced models, so script/generate model Gallery::Image will use create_table :gallery_images #6327 [Bob Silva]
-
-* Fixed rename_table on SQLite tables with indexes defined #5942 [brandon@opensoul.org]
-
-* Added default timeout setting of 5 seconds to SQLite3 database.yml configurations [David Heinemeier Hansson]
-
-* Added generated attribute options to script/generate model, like the one found in scaffold_resource and resource [David Heinemeier Hansson]. Examples:
-
- ./script/generate model post title:string created_on:date body:text published:boolean
-
-* Added script/generate resource which works just like scaffold_resource, but creates empty placeholders instead of predefined [David Heinemeier Hansson]
-
-* script/runner can run files, pass on arguments, and be used as a shebang. #6286 [Tuxie, dlpond]
- #!/usr/bin/env /path/to/my/app/script/runner
- # Example: just start using your models as if you are in script/console
- Product.find(:all).each { |product| product.check_inventory }
-
-* Look for rake tasks in plugin subdirs. #6259 [obrie]
-
-* Added map.connect ':controller/:action/:id.:format' as a default route to config/routes.rb [David Heinemeier Hansson]
-
-* session_migration generator adds an index on updated_at. #6207 [grg]
-
-* script/server creates the tmp/pids directory. #6204 [jonathan]
-
-* Fix script/console --sandbox for internal transactions changes. #5738 [Chris McGrath, charles.gerungan@gmail.com]
-
-* Remove the uncanny default of adding all app/models/*/ directories to the load path. This change will break application which expect the current behavior. As
-documented in initializer.rb, the workaround is:
-
- config.autoload_paths += Dir[RAILS_ROOT + '/app/models/*/']
-
-References #6031. [Nicholas Seckar]
-
-* Update to script.aculo.us 1.6.3 [Thomas Fuchs]
-
-* Formally Deprecate the old rake tasks. [Michael Koziarski]
-
-* Thoroughly test the FCGI dispatcher. #5970 [Kevin Clark]
-
-* Remove Dir.chdir in the Webrick DispatchServlet#initialize method. Fix bad path errors when trying to load config/routes.rb. [Rick Olson]
-
-* Tighten rescue clauses. #5985 [james@grayproductions.net]
-
-* Cleaning up tests. [Kevin Clark, Jeremy Kemper]
-
-* Add Dependencies.load_once_paths. [Nicholas Seckar]
-
-* Assign Routing.controller_paths; fix script/about and rails info controller. [Nicholas Seckar]
-
-* Don't warn dispatcher of Reloadable deprecations. [Nicholas Seckar]
-
-* Rearrange application resetting and preparation, fix bug with leaking subclasses hash in ActiveRecord::Base [Rick Olson]
-
- ActiveRecord::Base.reset_subclasses is called before Dependencies are cleared and classes removed.
- ActiveRecord::Base.instantiate_observers is called during a Dispatcher preparation callback.
-
-* Add missing mock directories from the autoload_paths configuration. [Rick Olson]
-
-* Nested controller scaffolding also nests the generated layout. [iain d broadfoot]
-
-* Add "require 'dispatcher'" to webrick server in the continuing quest to squash webrick weirdness. [Nicholas Seckar]
-
-* Add autoload_paths support to Initializer. [Nicholas Seckar]
-
-* Fix Dispatcher.reset_application! so that AR subclasses are removed and Observers re-initialized *after* Reloadable classes are removed. Closes #5743. [Rick Olson]
-
-* Clarify usage of script/plugin source. Closes #5344. [James Adam]
-
-* Add Dispatcher.to_prepare and config.to_prepare to provide a pre-request hook. [Nicholas Seckar]
-
-* Tweak the Rails load order so observers are loaded after plugins, and reloaded in development mode. Closed #5279. [Rick Olson]
-
-* Added that you can change the web server port in config/lighttpd.conf from script/server --port/-p #5465 [mats@imediatec.co.uk]
-
-* script/performance/profiler compatibility with the new ruby-prof, including an option to choose the results printer. #5679 [Shugo Maeda]
-
-* Fixed the failsafe response so it uses either the current recognized controller or ApplicationController. [Rick Olson]
-
-* Make sure script/reaper only reaps dispatcher pids by default, and not the spawner's pid. [Jamis Buck]
-
-* Fix script/plugin about so it uses about.yml and not meta.yml. [James Adam]
-
-* Dispatcher processes rescued actions with the same controller that processed the request. #4625 [sd@notso.net]
-
-* rails -d frontbase to create a new project with a frontbase database.yml. #4945 [mlaster@metavillage.com]
-
-* Ensure the logger is initialized. #5629 [mike@clarkware.com]
-
-* Added Mongrel-spawning capabilities to script/process/spawner. Mongrel will be the default choice if installed, otherwise FCGI is tried [David Heinemeier Hansson]. Examples:
-
- spawner # starts instances on 8000, 8001, and 8002 using Mongrel if available
- spawner fcgi # starts instances on 8000, 8001, and 8002 using FCGI
- spawner mongrel -i 5 # starts instances on 8000, 8001, 8002, 8003, and 8004 using Mongrel
- spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to 9109 using Mongrel if available
- spawner -p 9100 -r 5 # starts 3 instances counting from 9100 to 9102 and attempts start them every 5 seconds
-
- Also note that script/process/reaper is Mongrel capable. So the combination of spawner and reaper is a built-in alternative to something like mongrel_cluster.
-
-* Update scaffolding functional tests to use :id => people(:first) instead of :id => 1. #5612 [evan@protest.net]
-
-* db:test:clone should remove existing tables before reloading the schema. #5607 [sveit@tradeharbor.com]
-
-* Fixed migration generation for class names like ACLController #5197 [brad@madriska.com]
-
-* Added show_source_list and show_call_stack to breakpoints to make it easier to get context #5476 [takiuchi@drecom.co.jp]. Examples:
-
- irb(#<TopController:0x40822a68>):002:0> show_source_list
- 0001 class TopController < ApplicationController
- 0002 def show
- 0003-> breakpoint
- 0004 end
- 0005
- 0006 def index
- 0007 end
- 0008
- => "/path/to/rails/root/app/controllers/top_controller.rb"
-
- irb(#<TopController:0x40822a68>):004:0> show_call_stack 3
- vendor/rails/railties/lib/breakpoint.rb:536:in `breakpoint'
- vendor/rails/railties/lib/breakpoint.rb:536:in `breakpoint'
- app/controllers/top_controller.rb:3:in `show'
- => "/path/to/rails/root/app/controllers/top_controller.rb:3"
-
-* Generate scaffold layout in subdirectory appropriate to its module nesting. #5511 [nils@alumni.rice.edu]
-
-* Mongrel: script/server tails the rails log like it does with lighttpd. Prefer mongrel over lighttpd. #5541 [mike@clarkware.com]
-
-* Don't assume Active Record is available. #5497 [bob@sporkmonger.com]
-
-* Mongrel: script/server works on Win32. #5499 [jeremydurham@gmail.com]
-
-* Remove opts.on { |options[:option_name] } style hash assignment. Closes #4440. [Nick Sieger]
-
-* Mongrel support for script/server. #5475 [jeremydurham@gmail.com]
-
-* Fix script/plugin so it doesn't barf on invalid URLs [Rick Olson]
-
-* Fix plugin install bug at dir with space. (closes #5359) [Yoshimasa NIWA]
-
-* Fix bug with 'script/plugin install' so it reports unknown plugin names correctly. [Rick Olson]
-
-* Added uninstall.rb hook to plugin handling, such that plugins have a way of removing assets and other artifacts on removal #5003 [takiuchi@drecom.co.jp]
-
-* Create temporary dirs relative to RAILS_ROOT when running script/server #5014 [elliot@townx.org]
-
-* Minor tweak to dispatcher to use recognize instead of recognize!, as per the new routes. [Jamis Buck]
-
-* Make "script/plugin install" work with svn+ssh URLs. [Sam Stephenson]
-
-* Added lib/ to the directories that will get application docs generated [David Heinemeier Hansson]
-
-* Add observer generator. Closes #5167. [François Beausoleil]
-
-* Session migration generator obeys pluralize_table_names. #5145 [James Adam]
-
-* rake test:recent understands subdirectories. #2925 [jerrett@bravenet.com]
-
-* The app generator detects the XAMPP package's MySQL socket location. #3832 [elliot@townx.org]
-
-* The app generator sets a session key in application.rb so apps running on the same host may distinguish their cookies. #2967 [rcoder, rails-bug@owl.me.uk]
-
-* Distinguish the spawners for different processes [David Heinemeier Hansson]
-
-* Added -n/--process to script/process/spawner name the process pid (default is dispatch) [David Heinemeier Hansson]
-
-* Namespaced OrderedHash so the Rails implementation does not clash with any others. (fixes #4911) [Julian Tarkhanov]
-
-* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
-
-* Added script/process/inspector to do simple process status information on Rails dispatchers keeping pid files in tmp/pids [David Heinemeier Hansson]
-
-* Added pid file usage to script/process/spawner and script/process/reaper along with a directive in default config/lighttpd.conf file to record the pid. They will all save their pid file in tmp/pids [David Heinemeier Hansson]
-
-
-*1.1.6* (August 10th, 2006)
-
-* Additional security patch
-
-
-*1.1.5* (August 8th, 2006)
-
-* Mention in docs that config.frameworks doesn't work when getting Rails via Gems. #4857 [Alisdair McDiarmid]
-
-* Change the scaffolding layout to use yield rather than @content_for_layout. [Marcel Molina Jr.]
-
-* Includes critical security patch
-
-
-*1.1.4* (June 29th, 2006)
-
-* Remove use of opts.on { |options[:name] } style hash assignment. References #4440. [headius@headius.com]
-
-* Updated to Action Pack 1.12.3, ActionWebService 1.1.4, ActionMailer 1.2.3
-
-
-*1.1.3* (June 27th, 2006)
-
-* Updated to Active Record 1.14.3, Action Pack 1.12.2, ActionWebService 1.1.3, ActionMailer 1.2.2
-
-
-*1.1.2* (April 9th, 2006)
-
-* Mention in docs that config.frameworks doesn't work when getting Rails via Gems. Closes #4857. [Alisdair McDiarmid]
-
-* Change the scaffolding layout to use yield rather than @content_for_layout. [Marcel Molina Jr.]
-
-* Added rake rails:update:configs to update config/boot.rb from the latest (also included in rake rails:update) [David Heinemeier Hansson]
-
-* Fixed that boot.rb would set RAILS_GEM_VERSION twice, not respect an uncommented RAILS_GEM_VERSION line, and not use require_gem [David Heinemeier Hansson]
-
-
-*1.1.1* (April 6th, 2006)
-
-* Enhances plugin#discover allowing it to discover svn:// like URIs (closes #4565) [ruben.nine@gmail.com]
-
-* Update to Prototype 1.5.0_rc0 [Sam Stephenson]
-
-* Fixed that the -r/--ruby path option of the rails command was not being respected #4549 [ryan.raaum@gmail.com]
-
-* Added that Dispatcher exceptions should not be shown to the user unless a default log has not been configured. Instead show public/500.html [David Heinemeier Hansson]
-
-* Fixed that rake clone_structure_to_test should quit on pgsql if the dump is unsuccesful #4585 [augustz@augustz.com]
-
-* Fixed that rails --version should have the return code of 0 (success) #4560 [blair@orcaware.com]
-
-* Install alias so Rails::InfoController is accessible at /rails_info. Closes #4546. [Nicholas Seckar]
-
-* Fixed that spawner should daemonize if running in repeat mode [David Heinemeier Hansson]
-
-* Added TAG option for rake rails:freeze:edge, so you can say rake rails:freeze:edge TAG=rel_1-1-0 to lock to the 1.1.0 release [David Heinemeier Hansson]
-
-* Applied Prototype $() performance patches (#4465, #4477) and updated script.aculo.us [Sam Stephenson, Thomas Fuchs]
-
-* Use --simple-prompt instead of --prompt-mode simple for console compatibility with Windows/Ruby 1.8.2 #4532 [starr@starrnhorne.com]
-
-* Make Rails::VERSION implicitly loadable #4491. [Nicholas Seckar]
-
-* Fixed rake rails:freeze:gems #4518 [benji@silverinsanity.com]
-
-* Added -f/--freeze option to rails command for freezing the application to the Rails version it was generated with [David Heinemeier Hansson]
-
-* Added gem binding of apps generated through the rails command to the gems of they were generated with [Nicholas Seckar]
-
-* Added expiration settings for JavaScript, CSS, HTML, and images to default lighttpd.conf [David Heinemeier Hansson]
-
-* Added gzip compression for JavaScript, CSS, and HTML to default lighttpd.conf [David Heinemeier Hansson]
-
-* Avoid passing escapeHTML non-string in Rails' info controller [Nicholas Seckar]
-
-
-*1.1.0* (March 27th, 2006)
-
-* Allow db:fixtures:load to load a subset of the applications fixtures. [Chad Fowler]
-
- ex.
-
- rake db:fixtures:load FIXTURES=customers,plans
-
-* Update to Prototype 1.5.0_pre1 [Sam Stephenson]
-
-* Update to script.aculo.us 1.6 [Thomas Fuchs]
-
-* Add an integration_test generator [Jamis Buck]
-
-* Make all ActionView helpers available in the console from the helper method for debugging purposes. n.b.: Only an 80% solution. Some stuff won't work, most will. [Marcel Molina Jr.]
-
- ex.
-
- >> puts helper.options_for_select([%w(a 1), %w(b 2), %w(c 3)])
- <option value="1">a</option>
- <option value="2">b</option>
- <option value="3">c</option>
- => nil
-
-* Replaced old session rake tasks with db:sessions:create to generate a migration, and db:sessions:clear to remove sessions. [Rick Olson]
-
-* Reject Ruby 1.8.3 when loading Rails; extract version checking code. [Chad Fowler]
-
-* Remove explicit loading of RailsInfo and RailsInfoController. [Nicholas Seckar]
-
-* Move RailsInfo and RailsInfoController to Rails::Info and Rails::InfoController. [Nicholas Seckar]
-
-* Extend load path with Railties' builtin directory to make adding support code easy. [Nicholas Seckar]
-
-* Fix the rails_info controller by explicitly loading it, and marking it as not reloadable. [Nicholas Seckar]
-
-* Fixed rails:freeze:gems for Windows #3274 [paul@paulbutcher.com]
-
-* Added 'port open?' check to the spawner when running in repeat mode so we don't needlessly boot the dispatcher if the port is already in use anyway #4089 [guy.naor@famundo.com]
-
-* Add verification to generated scaffolds, don't allow get for unsafe actions [Michael Koziarski]
-
-* Don't replace application.js in public/javascripts if it already exists [Cody Fauser]
-
-* Change test:uncommitted to delay execution of `svn status` by using internal Rake API's. [Nicholas Seckar]
-
-* Use require_library_or_gem to load rake in commands/server.rb. Closes #4205. [rob.rasmussen@gmail.com]
-
-* Use the Rake API instead of shelling out to create the tmp directory in commands/server.rb. [Chad Fowler]
-
-* Added a backtrace to the evil WSOD (White Screen of Death). Closes #4073. TODO: Clearer exceptions [Rick Olson]
-
-* Added tracking of database and framework versions in script/about #4088 [charles.gerungan@gmail.com/Rick Olson]
-
-* Added public/javascripts/application.js as a sample since it'll automatically be included in javascript_include_tag :defaults [David Heinemeier Hansson]
-
-* Added socket cleanup for lighttpd, both before and after [David Heinemeier Hansson]
-
-* Added automatic creation of tmp/ when running script/server [David Heinemeier Hansson]
-
-* Added silence_stream that'll work on both STDERR or STDOUT or any other stream and deprecated silence_stderr in the process [David Heinemeier Hansson]
-
-* Added reload! method to script/console to reload all models and others that include Reloadable without quitting the console #4056 [esad@esse.at]
-
-* Added that rake rails:freeze:edge will now just export all the contents of the frameworks instead of just lib, so stuff like rails:update:scripts, rails:update:javascripts, and script/server on lighttpd still just works #4047 [David Heinemeier Hansson]
-
-* Added fix for upload problems with lighttpd from Safari/IE to config/lighttpd.conf #3999 [Thijs van der Vossen]
-
-* Added test:uncommitted to test changes since last checkin to Subversion #4035 [technomancy@gmail.com]
-
-* Help script/about print the correct svn revision when in a non-English locale. #4026 [babie7a0@ybb.ne.jp]
-
-* Add 'app' accessor to script/console as an instance of Integration::Session [Jamis Buck]
-
-* Generator::Base#usage takes an optional message argument which defaults to Generator::Base#usage_message. [Jeremy Kemper]
-
-* Remove the extraneous AR::Base.threaded_connections setting from the webrick server. [Jeremy Kemper]
-
-* Add integration test support to app generation and testing [Jamis Buck]
-
-* Added namespaces to all tasks, so for example load_fixtures is now db:fixtures:load. All the old task names are still valid, they just point to the new namespaced names. "rake -T" will only show the namespaced ones, though [David Heinemeier Hansson]
-
-* CHANGED DEFAULT: ActiveRecord::Base.schema_format is now :ruby by default instead of :sql. This means that we'll assume you want to live in the world of db/schema.rb where the grass is green and the girls are pretty. If your schema contains un-dumpable elements, such as constraints or database-specific column types, you just got an invitation to either 1) patch the dumper to include foreign key support, 2) stop being db specific, or 3) just change the default in config/environment.rb to config.active_record.schema_format = :sql -- we even include an example for that on new Rails skeletons now. Brought to you by the federation of opinionated framework builders! [David Heinemeier Hansson]
-
-* Added -r/--repeat option to script/process/spawner that offers the same loop protection as the spinner did. This deprecates the script/process/spinner, so it's no longer included in the default Rails skeleton, but still available for backwards compatibility #3461 [ror@andreas-s.net]
-
-* Added collision option to template generation in generators #3329 [anna@wota.jp]. Examples:
-
- m.template "stuff.config" , "config/stuff.config" , :collision => :skip
- m.template "auto-stamping", "config/generator.log", :collision => :force
-
-* Added more information to script/plugin's doings to ease debugging #3755 [Rick Olson]
-
-* Changed the default configuration for lighttpd to use tmp/sockets instead of log/ for the FastCGI sockets [David Heinemeier Hansson]
-
-* Added a default configuration of the FileStore for fragment caching if tmp/cache is available, which makes action/fragment caching ready to use out of the box with no additional configuration [David Heinemeier Hansson]
-
-* Changed the default session configuration to place sessions in tmp/sessions, if that directory is available, instead of /tmp (this essentially means a goodbye to 9/10 White Screen of Death errors and should have web hosting firms around the world cheering) [David Heinemeier Hansson]
-
-* Added tmp/sessions, tmp/cache, and tmp/sockets as default directories in the Rails skeleton [David Heinemeier Hansson]
-
-* Added that script/generate model will now automatically create a migration file for the model created. This can be turned off by calling the generator with --skip-migration [David Heinemeier Hansson]
-
-* Added -d/--database option to the rails command, so you can do "rails --database=sqlite2 myapp" to start a new application preconfigured to use SQLite2 as the database. Removed the configuration examples from SQLite and PostgreSQL from the default MySQL configuration [David Heinemeier Hansson]
-
-* Allow script/server -c /path/to/lighttpd.conf [Jeremy Kemper]
-
-* Remove hardcoded path to reaper script in script/server [Jeremy Kemper]
-
-* Update script.aculo.us to V1.5.3 [Thomas Fuchs]
-
-* Added SIGTRAP signal handler to RailsFCGIHandler that'll force the process into a breakpoint after the next request. This breakpoint can then be caught with script/breakpointer and give you access to the Ruby image inside that process. Useful for debugging memory leaks among other things [David Heinemeier Hansson]
-
-* Changed default lighttpd.conf to use CWD from lighttpd 1.4.10 that allows the same configuration to be used for both detach and not. Also ensured that auto-repeaping of FCGIs only happens when lighttpd is not detached. [David Heinemeier Hansson]
-
-* Added Configuration#after_initialize for registering a block which gets called after the framework is fully initialized. Useful for things like per-environment configuration of plugins. [Michael Koziarski]
-
-* Added check for RAILS_FRAMEWORK_ROOT constant that allows the Rails framework to be found in a different place than vendor/rails. Should be set in boot.rb. [David Heinemeier Hansson]
-
-* Fixed that static requests could unlock the mutex guarding dynamic requests in the WEBrick servlet #3433 [tom@craz8.com]
-
-* Fixed documentation tasks to work with Rake 0.7.0 #3563 [kazuhiko@fdiary.net]
-
-* Update to Prototype 1.5.0_pre0 [Sam Stephenson]
-
-* Sort the list of plugins so we load in a consistent order [Rick Olson]
-
-* Show usage when script/plugin is called without arguments [tom@craz8.com]
-
-* Corrected problems with plugin loader where plugins set 'name' incorrectly #3297 [anna@wota.jp]
-
-* Make migration generator only report on exact duplicate names, not partial dupliate names. #3442 [jeremy@planetargon.com Marcel Molina Jr.]
-
-* Fix typo in mailer generator USAGE. #3458 [chriztian.steinmeier@gmail.com]
-
-* Ignore version mismatch between pg_dump and the database server. #3457 [Simon Stapleton]
-
-* Reap FCGI processes after lighttpd exits. [Sam Stephenson]
-
-* Honor ActiveRecord::Base.pluralize_table_names when creating and destroying session store table. #3204. [rails@bencurtis.com, Marcel Molina Jr.]
-
-*1.0.0* (December 13th, 2005)
-
-* Update instructions on how to find and install generators. #3172. [Chad Fowler]
-
-* Generator looks in vendor/generators also. [Chad Fowler]
-
-* Generator copies files in binary mode. #3156 [minimudboy@gmail.com]
-
-* Add builtin/ to the gemspec. Closes #3047. [Nicholas Seckar, Sam Stephenson]
-
-* Add install.rb file to plugin generation which is loaded, if it exists, when you install a plugin. [Marcel Molina Jr.]
-
-* Run initialize_logger in script/lighttpd to ensure the log file exists before tailing it. [Sam Stephenson]
-
-* Make load_fixtures include csv fixtures. #3053. [me@mdaines.com]
-
-* Fix freeze_gems so that the latest rails version is dumped by default. [Nicholas Seckar]
-
-* script/plugin: handle root paths and plugin names which contain spaces. #2995 [justin@aspect.net]
-
-* Model generator: correct relative path to test_helper in unit test. [Jeremy Kemper]
-
-* Make the db_schema_dump task honor the SCHEMA environment variable if present the way db_schema_import does. #2931. [Blair Zajac]
-
-* Have the lighttpd server script report the actual ip to which the server is bound. #2903. [Adam]
-
-* Add plugin library directories to the load path after the lib directory so that libraries in the lib directory get precedence. #2910. [James Adam]
-
-* Make help for the console command more explicit about how to specify the desired environment in which to run the console. #2911. [anonymous]
-
-* PostgreSQL: the purge_test_database Rake task shouldn't explicitly specify the template0 template when creating a fresh test database. #2964 [Dreamer3]
-
-* Introducing the session_migration generator. Creates an add_session_table migration. Allows generator to specify migrations directory. #2958, #2960 [Rick Olson]
-
-* script/console uses RAILS_ENV environment variable if present. #2932 [Blair Zajac <blair@orcaware.com>
-
-* Windows: eliminate the socket option in database.yml. #2924 [Wayne Vucenic <waynev@gmail.com>]
-
-* Eliminate nil from newly generated logfiles. #2927 [Blair Zajac <blair@orcaware.com>]
-
-* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
-
-* Eliminate Subversion dependencies in scripts/plugin. Correct install options. Introduce --force option to reinstall a plugin. Remove useless --long option for list. Use --quiet to quiet the download output and --revision to update to a specific svn revision. #2842 [Chad Fowler, Rick Olson]
-
-* SQLite: the clone_structure_to_test and purge_test_database Rake tasks should always use the test environment. #2846 [Rick Olson]
-
-* Make sure that legacy db tasks also reference :database for SQLite #2830 [kazuhiko@fdiary.net]
-
-* Pass __FILE__ when evaluating plugins' init.rb. #2817 [James Adam]
-
-* Better svn status matching for generators. #2814 [François Beausoleil <francois.beausoleil@gmail.com>, Blair Zajac <blair@orcaware.com>]
-
-* Don't reload routes until plugins have been loaded so they have a chance to extend the routing capabilities [David Heinemeier Hansson]
-
-* Don't detach or fork for script/server tailing [Nicholas Seckar]
-
-* Changed all script/* to use #!/usr/bin/env ruby instead of hard-coded Ruby path. public/dispatcher.* still uses the hard-coded path for compatibility with web servers that don't have Ruby in path [David Heinemeier Hansson]
-
-* Force RAILS_ENV to be "test" when running tests, so that ENV["RAILS_ENV"] = "production" in config/environment.rb doesn't wreck havok [David Heinemeier Hansson] #2660
-
-* Correct versioning in :freeze_gems Rake task. #2778 [Jakob Skjerning, Jeremy Kemper]
-
-* Added an omnipresent RailsInfoController with a properties action that delivers an HTML rendering of Rails::Info (but only when local_request? is true). Added a new default index.html which fetches this with Ajax. [Sam Stephenson]
-
-
-*0.14.3 (RC4)* (November 7th, 2005)
-
-* Add 'add_new_scripts' rake task for adding new rails scripts to script/* [Jamis Buck]
-
-* Remove bogus hyphen from script/process/reaper calls to 'ps'. #2767 [anonymous]
-
-* Copy lighttpd.conf when it is first needed, instead of on app creation [Jamis Buck]
-
-* Use require_library_or_gem 'fcgi' in script/server [Sam Stephenson]
-
-* Added default lighttpd config in config/lighttpd.conf and added a default runner for lighttpd in script/server (works like script/server, but using lighttpd and FastCGI). It will use lighttpd if available, otherwise WEBrick. You can force either or using 'script/server lighttpd' or 'script/server webrick' [David Heinemeier Hansson]
-
-* New configuration option config.plugin_paths which may be a single path like the default 'vendor/plugins' or an array of paths: ['vendor/plugins', 'lib/plugins']. [Jeremy Kemper]
-
-* Plugins are discovered in nested paths, so you can organize your plugins directory as you like. [Jeremy Kemper]
-
-* Refactor load_plugin from load_plugins. #2757 [alex.r.moon@gmail.com]
-
-* Make use of silence_stderr in script/lighttpd, script/plugin, and Rails::Info [Sam Stephenson]
-
-* Enable HTTP installation of plugins when svn isn't avaialable. Closes #2661. [Chad Fowler]
-
-* Added script/about to display formatted Rails::Info output [Sam Stephenson]
-
-* Added Rails::Info to catalog assorted information about a Rails application's environment [Sam Stephenson]
-
-* Tail the logfile when running script/server lighttpd in the foreground [Sam Stephenson]
-
-* Try to guess the port number from config/lighttpd.conf in script/server lighttpd [Sam Stephenson]
-
-* Don't reap spawn-fcgi. #2727 [matthew@walker.wattle.id.au]
-
-* Reaper knows how to find processes even if the dispatch path is very long. #2711 [matthew@walker.wattle.id.au]
-
-* Make fcgi handler respond to TERM signals with an explicit exit [Jamis Buck]
-
-* Added demonstration of fixture use to the test case generated by the model generator [David Heinemeier Hansson]
-
-* If specified, pass PostgreSQL client character encoding to createdb. #2703 [Kazuhiko <kazuhiko@fdiary.net>]
-
-* Catch CGI multipart parse errors. Wrap dispatcher internals in a failsafe response handler. [Jeremy Kemper]
-
-* The freeze_gems Rake task accepts the VERSION environment variable to decide which version of Rails to pull into vendor/rails. [Chad Fowler, Jeremy Kemper]
-
-* Removed script.aculo.us.js, builder.js and slider.js (preperation for move of scriptaculous extensions to plugins, core scriptaculous will remain in Railties) [Thomas Fuchs]
-
-* The freeze_edge Rake task does smarter svn detection and can export a specific revision by passing the REVISION environment variable. For example: rake freeze_edge REVISION=1234. #2663 [Rick Olson]
-
-* Comment database.yml and include PostgreSQL and SQLite examples. [Jeremy Kemper]
-
-* Improve script/plugin on Windows. #2646 [Chad Fowler]
-
-* The *_plugindoc Rake tasks look deeper into the plugins' lib directories. #2652 [bellis@deepthought.org]
-
-* The PostgreSQL :db_structure_dump Rake task limits its dump to the schema search path in database.yml. [Anatol Pomozov <anatol.pomozov@gmail.com>]
-
-* Add task to generate rdoc for all installed plugins. [Marcel Molina Jr.]
-
-* Update script.aculo.us to V1.5_rc4 [Thomas Fuchs]
-
-* Add default Mac + DarwinPorts MySQL socket locations to the app generator. [Jeremy Kemper]
-
-* Migrations may be destroyed: script/destroy migration foo. #2635 [Charles M. Gerungan <charles.gerungan@gmail.com>, Jamis Buck, Jeremy Kemper]
-
-* Added that plugins can carry generators and that generator stub files can be created along with new plugins using script/generate plugin <name> --with-generator [David Heinemeier Hansson]
-
-* Removed app/apis as a default empty dir since its automatically created when using script/generate web_service [David Heinemeier Hansson]
-
-* Added script/plugin to manage plugins (install, remove, list, etc) [Ryan Tomayko]
-
-* Added test_plugins task: Run the plugin tests in vendor/plugins/**/test (or specify with PLUGIN=name) [David Heinemeier Hansson]
-
-* Added plugin generator to create a stub structure for a new plugin in vendor/plugins (see "script/generate plugin" for help) [David Heinemeier Hansson]
-
-* Fixed scaffold generator when started with only 1 parameter #2609 [self@mattmower.com]
-
-* rake should run functional tests even if the unit tests have failures [Jim Weirich]
-
-* Back off cleanpath to be symlink friendly. Closes #2533 [Nicholas Seckar]
-
-* Load rake task files in alphabetical order so you can build dependencies and count on them #2554 [Blair Zajac]
-
-
-*0.14.2 (RC3)* (October 26th, 2005)
-
-* Constants set in the development/test/production environment file are set in Object
-
-* Scaffold generator pays attention to the controller name. #2562 [self@mattmower.com]
-
-* Include tasks from vendor/plugins/*/tasks in the Rakefile #2545 [Rick Olson]
-
-
-*0.14.1 (RC2)* (October 19th, 2005)
-
-* Don't clean RAILS_ROOT on windows
-
-* Remove trailing '/' from RAILS_ROOT [Nicholas Seckar]
-
-* Upgraded to Active Record 1.12.1 and Action Pack 1.10.1
-
-
-*0.14.0 (RC1)* (October 16th, 2005)
-
-* Moved generator folder from RAILS_ROOT/generators to RAILS_ROOT/lib/generators [Tobias Lütke]
-
-* Fix rake dev and related commands [Nicholas Seckar]
-
-* The rails command tries to deduce your MySQL socket by running `mysql_config
---socket`. If it fails, default to /path/to/your/mysql.sock
-
-* Made the rails command use the application name for database names in the tailored database.yml file. Example: "rails ~/projects/blog" will use "blog_development" instead of "rails_development". [Florian Weber]
-
-* Added Rails framework freezing tasks: freeze_gems (freeze to current gems), freeze_edge (freeze to Rails SVN trunk), unfreeze_rails (float with newest gems on system)
-
-* Added update_javascripts task which will fetch all the latest js files from your current rails install. Use after updating rails. [Tobias Lütke]
-
-* Added cleaning of RAILS_ROOT to useless elements such as '../non-dot-dot/'. Provides cleaner backtraces and error messages. [Nicholas Seckar]
-
-* Made the instantiated/transactional fixtures settings be controlled through Rails::Initializer. Transactional and non-instantiated fixtures are default from now on. [Florian Weber]
-
-* Support using different database adapters for development and test with ActiveRecord::Base.schema_format = :ruby [Sam Stephenson]
-
-* Make webrick work with session(:off)
-
-* Add --version, -v option to the Rails command. Closes #1840. [stancell]
-
-* Update Prototype to V1.4.0_pre11, script.aculo.us to V1.5_rc3 [2504] and fix the rails generator to include the new .js files [Thomas Fuchs]
-
-* Make the generator skip a file if it already exists and is identical to the new file.
-
-* Add experimental plugin support #2335
-
-* Made Rakefile aware of new .js files in script.aculo.us [Thomas Fuchs]
-
-* Make table_name and controller_name in generators honor AR::Base.pluralize_table_names. #1216 #2213 [kazuhiko@fdiary.net]
-
-* Clearly label functional and unit tests in rake stats output. #2297 [lasse.koskela@gmail.com]
-
-* Make the migration generator only check files ending in *.rb when calculating the next file name #2317 [Chad Fowler]
-
-* Added prevention of duplicate migrations from the generator #2240 [François Beausoleil]
-
-* Add db_schema_dump and db_schema_import rake tasks to work with the new ActiveRecord::SchemaDumper (for dumping a schema to and reading a schema from a ruby file).
-
-* Reformed all the config/environments/* files to conform to the new Rails::Configuration approach. Fully backwards compatible.
-
-* Added create_sessions_table, drop_sessions_table, and purge_sessions_table as rake tasks for databases that supports migrations (MySQL, PostgreSQL, SQLite) to get a table for use with CGI::Session::ActiveRecordStore
-
-* Added dump of schema version to the db_structure_dump task for databases that support migrations #1835 [Rick Olson]
-
-* Fixed script/profiler for Ruby 1.8.2 #1863 [Rick Olson]
-
-* Fixed clone_structure_to_test task for SQLite #1864 [jon@burningbush.us]
-
-* Added -m/--mime-types option to the WEBrick server, so you can specify a Apache-style mime.types file to load #2059 [ask@develooper.com]
-
-* Added -c/--svn option to the generator that'll add new files and remove destroyed files using svn add/revert/remove as appropriate #2064 [Kevin Clark]
-
-* Added -c/--charset option to WEBrick server, so you can specify a default charset (which without changes is UTF-8) #2084 [wejn@box.cz]
-
-* Make the default stats task extendable by modifying the STATS_DIRECTORIES constant
-
-* Allow the selected environment to define RAILS_DEFAULT_LOGGER, and have Rails::Initializer use it if it exists.
-
-* Moved all the shared tasks from Rakefile into Rails, so that the Rakefile is empty and doesn't require updating.
-
-* Added Rails::Initializer and Rails::Configuration to abstract all of the common setup out of config/environment.rb (uses config/boot.rb to bootstrap the initializer and paths)
-
-* Fixed the scaffold generator to fail right away if the database isn't accessible instead of in mid-air #1169 [Chad Fowler]
-
-* Corrected project-local generator location in scripts.rb #2010 [Michael Schuerig]
-
-* Don't require the environment just to clear the logs #2093 [Scott Barron]
-
-* Make the default rakefile read *.rake files from config/tasks (for easy extension of the rakefile by e.g. generators)
-
-* Only load breakpoint in development mode and when BREAKPOINT_SERVER_PORT is defined.
-
-* Allow the --toggle-spin switch on process/reaper to be negated
-
-* Replace render_partial with render :partial in scaffold generator [Nicholas Seckar]
-
-* Added -w flag to ps in process/reaper #1934 [Scott Barron]
-
-* Allow ERb in the database.yml file (just like with fixtures), so you can pull out the database configuration in environment variables #1822 [Duane Johnson]
-
-* Added convenience controls for FCGI processes (especially when managed remotely): spinner, spawner, and reaper. They reside in script/process. More details can be had by calling them with -h/--help.
-
-* Added load_fixtures task to the Rakefile, which will load all the fixtures into the database for the current environment #1791 [Marcel Molina Jr.]
-
-* Added an empty robots.txt to public/, so that web servers asking for it won't trigger a dynamic call, like favicon.ico #1738 [Michael Schubert]
-
-* Dropped the 'immediate close-down' of FCGI processes since it didn't work consistently and produced bad responses when it didn't. So now a TERM ensures exit after the next request (just as if the process is handling a request when it receives the signal). This means that you'll have to 'nudge' all FCGI processes with a request in order to ensure that they have all reloaded. This can be done by something like ./script/process/repear --nudge 'http://www.myapp.com' --instances 10, which will load the myapp site 10 times (and thus hit all of the 10 FCGI processes once, enough to shut down).
-
-
-*0.13.1* (11 July, 2005)
-
-* Look for app-specific generators in RAILS_ROOT/generators rather than the clunky old RAILS_ROOT/script/generators. Nobody really uses this feature except for the unit tests, so it's a negligible-impact change. If you want to work with third-party generators, drop them in ~/.rails/generators or simply install gems.
-
-* Fixed that each request with the WEBrick adapter would open a new database connection #1685 [Sam Stephenson]
-
-* Added support for SQL Server in the database rake tasks #1652 [Ken Barker] Note: osql and scptxfr may need to be installed on your development environment. This involves getting the .exes and a .rll (scptxfr) from a production SQL Server (not developer level SQL Server). Add their location to your Environment PATH and you are all set.
-
-* Added a VERSION parameter to the migrate task that allows you to do "rake migrate VERSION=34" to migrate to the 34th version traveling up or down depending on the current version
-
-* Extend Ruby version check to include RUBY_RELEASE_DATE >= '2005-12-25', the final Ruby 1.8.2 release #1674 [court3nay]
-
-* Improved documentation for environment config files #1625 [court3nay]
-
-
-*0.13.0* (6 July, 2005)
-
-* Changed the default logging level in config/environment.rb to INFO for production (so SQL statements won't be logged)
-
-* Added migration generator: ./script/generate migration add_system_settings
-
-* Added "migrate" as rake task to execute all the pending migrations from db/migrate
-
-* Fixed that model generator would make fixtures plural, even if ActiveRecord::Base.pluralize_table_names was false #1185 [Marcel Molina Jr.]
-
-* Added a DOCTYPE of HTML transitional to the HTML files generated by Rails #1124 [Michael Koziarski]
-
-* SIGTERM also gracefully exits dispatch.fcgi. Ignore SIGUSR1 on Windows.
-
-* Add the option to manually manage garbage collection in the FastCGI dispatcher. Set the number of requests between GC runs in your public/dispatch.fcgi [Stefan Kaes]
-
-* Allow dynamic application reloading for dispatch.fcgi processes by sending a SIGHUP. If the process is currently handling a request, the request will be allowed to complete first. This allows production fcgi's to be reloaded without having to restart them.
-
-* RailsFCGIHandler (dispatch.fcgi) no longer tries to explicitly flush $stdout (CgiProcess#out always calls flush)
-
-* Fixed rakefile actions against PostgreSQL when the password is all numeric #1462 [Michael Schubert]
-
-* ActionMailer::Base subclasses are reloaded with the other rails components #1262
-
-* Made the WEBrick adapter not use a mutex around action performance if ActionController::Base.allow_concurrency is true (default is false)
-
-* Fixed that mailer generator generated fixtures/plural while units expected fixtures/singular #1457 [Scott Barron]
-
-* Added a 'whiny nil' that's aim to ensure that when users pass nil to methods where that isn't appropriate, instead of NoMethodError? and the name of some method used by the framework users will see a message explaining what type of object was expected. Only active in test and development environments by default #1209 [Michael Koziarski]
-
-* Fixed the test_helper.rb to be safe for requiring controllers from multiple spots, like app/controllers/article_controller.rb and app/controllers/admin/article_controller.rb, without reloading the environment twice #1390 [Nicholas Seckar]
-
-* Fixed Webrick to escape + characters in URL's the same way that lighttpd and apache do #1397 [Nicholas Seckar]
-
-* Added -e/--environment option to script/runner #1408 [François Beausoleil]
-
-* Modernize the scaffold generator to use the simplified render and test methods and to change style from @params["id"] to params[:id]. #1367
-
-* Added graceful exit from pressing CTRL-C during the run of the rails command #1150 [Caleb Tennis]
-
-* Allow graceful exits for dispatch.fcgi processes by sending a SIGUSR1. If the process is currently handling a request, the request will be allowed to complete and then will terminate itself. If a request is not being handled, the process is terminated immediately (via #exit). This basically works like restart graceful on Apache. [Jamis Buck]
-
-* Made dispatch.fcgi more robust by catching fluke errors and retrying unless its a permanent condition. [Jamis Buck]
-
-* Added console --profile for profiling an IRB session #1154 [Jeremy Kemper]
-
-* Changed console_sandbox into console --sandbox #1154 [Jeremy Kemper]
-
-
-*0.12.1* (20th April, 2005)
-
-* Upgraded to Active Record 1.10.1, Action Pack 1.8.1, Action Mailer 0.9.1, Action Web Service 0.7.1
-
-
-*0.12.0* (19th April, 2005)
-
-* Fixed that purge_test_database would use database settings from the development environment when recreating the test database #1122 [rails@cogentdude.com]
-
-* Added script/benchmarker to easily benchmark one or more statement a number of times from within the environment. Examples:
-
- # runs the one statement 10 times
- script/benchmarker 10 'Person.expensive_method(10)'
-
- # pits the two statements against each other with 50 runs each
- script/benchmarker 50 'Person.expensive_method(10)' 'Person.cheap_method(10)'
-
-* Added script/profiler to easily profile a single statement from within the environment. Examples:
-
- script/profiler 'Person.expensive_method(10)'
- script/profiler 'Person.expensive_method(10)' 10 # runs the statement 10 times
-
-* Added Rake target clear_logs that'll truncate all the *.log files in log/ to zero #1079 [Lucas Carlson]
-
-* Added lazy typing for generate, such that ./script/generate cn == ./script/generate controller and the likes #1051 [k@v2studio.com]
-
-* Fixed that ownership is brought over in pg_dump during tests for PostgreSQL #1060 [pburleson]
-
-* Upgraded to Active Record 1.10.0, Action Pack 1.8.0, Action Mailer 0.9.0, Action Web Service 0.7.0, Active Support 1.0.4
-
-
-*0.11.1* (27th March, 2005)
-
-* Fixed the dispatch.fcgi use of a logger
-
-* Upgraded to Active Record 1.9.1, Action Pack 1.7.0, Action Mailer 0.8.1, Action Web Service 0.6.2, Active Support 1.0.3
-
-
-*0.11.0* (22th March, 2005)
-
-* Removed SCRIPT_NAME from the WEBrick environment to prevent conflicts with PATH_INFO #896 [Nicholas Seckar]
-
-* Removed ?$1 from the dispatch.f/cgi redirect line to get rid of 'complete/path/from/request.html' => nil being in the @params now that the ENV["REQUEST_URI"] is used to determine the path #895 [dblack/Nicholas Seckar]
-
-* Added additional error handling to the FastCGI dispatcher to catch even errors taking down the entire process
-
-* Improved the generated scaffold code a lot to take advantage of recent Rails developments #882 [Tobias Lütke]
-
-* Combined the script/environment.rb used for gems and regular files version. If vendor/rails/* has all the frameworks, then files version is used, otherwise gems #878 [Nicholas Seckar]
-
-* Changed .htaccess to allow dispatch.* to be called from a sub-directory as part of the push with Action Pack to make Rails work on non-vhost setups #826 [Nicholas Seckar/Tobias Lütke]
-
-* Added script/runner which can be used to run code inside the environment by eval'ing the first parameter. Examples:
-
- ./script/runner 'ReminderService.deliver'
- ./script/runner 'Mailer.receive(STDIN.read)'
-
- This makes it easier to do CRON and postfix scripts without actually making a script just to trigger 1 line of code.
-
-* Fixed webrick_server cookie handling to allow multiple cookes to be set at once #800, #813 [dave@cherryville.org]
-
-* Fixed the Rakefile's interaction with postgresql to:
-
- 1. Use PGPASSWORD and PGHOST in the environment to fix prompting for
- passwords when connecting to a remote db and local socket connections.
- 2. Add a '-x' flag to pg_dump which stops it dumping privileges #807 [rasputnik]
- 3. Quote the user name and use template0 when dumping so the functions doesn't get dumped too #855 [pburleson]
- 4. Use the port if available #875 [madrobby]
-
-* Upgraded to Active Record 1.9.0, Action Pack 1.6.0, Action Mailer 0.8.0, Action Web Service 0.6.1, Active Support 1.0.2
-
-
-*0.10.1* (7th March, 2005)
-
-* Fixed rake stats to ignore editor backup files like model.rb~ #791 [Sebastian Kanthak]
-
-* Added exception shallowing if the DRb server can't be started (not worth making a fuss about to distract new users) #779 [Tobias Lütke]
-
-* Added an empty favicon.ico file to the public directory of new applications (so the logs are not spammed by its absence)
-
-* Fixed that scaffold generator new template should use local variable instead of instance variable #778 [Dan Peterson]
-
-* Allow unit tests to run on a remote server for PostgreSQL #781 [adamm@galacticasoftware.com]
-
-* Added web_service generator (run ./script/generate web_service for help) #776 [Leon Bredt]
-
-* Added app/apis and components to code statistics report #729 [Scott Barron]
-
-* Fixed WEBrick server to use ABSOLUTE_RAILS_ROOT instead of working_directory #687 [Nicholas Seckar]
-
-* Fixed rails_generator to be usable without RubyGems #686 [Cristi BALAN]
-
-* Fixed -h/--help for generate and destroy generators #331
-
-* Added begin/rescue around the FCGI dispatcher so no uncaught exceptions can bubble up to kill the process (logs to log/fastcgi.crash.log)
-
-* Fixed that association#count would produce invalid sql when called sequentialy #659 [kanis@comcard.de]
-
-* Fixed test/mocks/testing to the correct test/mocks/test #740
-
-* Added early failure if the Ruby version isn't 1.8.2 or above #735
-
-* Removed the obsolete -i/--index option from the WEBrick servlet #743
-
-* Upgraded to Active Record 1.8.0, Action Pack 1.5.1, Action Mailer 0.7.1, Action Web Service 0.6.0, Active Support 1.0.1
-
-
-*0.10.0* (24th February, 2005)
-
-* Changed default IP binding for WEBrick from 127.0.0.1 to 0.0.0.0 so that the server is accessible both locally and remotely #696 [Marcel Molina Jr.]
-
-* Fixed that script/server -d was broken so daemon mode couldn't be used #687 [Nicholas Seckar]
-
-* Upgraded to breakpoint 92 which fixes:
-
- * overload IRB.parse_opts(), fixes #443
- => breakpoints in tests work even when running them via rake
- * untaint handlers, might fix an issue discussed on the Rails ML
- * added verbose mode to breakpoint_client
- * less noise caused by breakpoint_client by default
- * ignored TerminateLineInput exception in signal handler
- => quiet exit on Ctrl-C
-
-* Added support for independent components residing in /components. Example:
-
- Controller: components/list/items_controller.rb
- (holds a List::ItemsController class with uses_component_template_root called)
-
- Model : components/list/item.rb
- (namespace is still shared, so an Item model in app/models will take precedence)
-
- Views : components/list/items/show.rhtml
-
-
-* Added --sandbox option to script/console that'll roll back all changes made to the database when you quit #672 [Jeremy Kemper]
-
-* Added 'recent' as a rake target that'll run tests for files that changed in the last 10 minutes #612 [Jeremy Kemper]
-
-* Changed script/console to default to development environment and drop --no-inspect #650 [Jeremy Kemper]
-
-* Added that the 'fixture :posts' syntax can be used for has_and_belongs_to_many fixtures where a model doesn't exist #572 [Jeremy Kemper]
-
-* Added that running test_units and test_functional now performs the clone_structure_to_test as well #566 [rasputnik]
-
-* Added new generator framework that informs about its doings on generation and enables updating and destruction of generated artifacts. See the new script/destroy and script/update for more details #487 [Jeremy Kemper]
-
-* Added Action Web Service as a new add-on framework for Action Pack [Leon Bredt]
-
-* Added Active Support as an independent utility and standard library extension bundle
-
-* Upgraded to Active Record 1.7.0, Action Pack 1.5.0, Action Mailer 0.7.0
-
-
-*0.9.5* (January 25th, 2005)
-
-* Fixed dependency reloading by switching to a remove_const approach where all Active Records, Active Record Observers, and Action Controllers are reloading by undefining their classes. This enables you to remove methods in all three types and see the change reflected immediately and it fixes #539. This also means that only those three types of classes will benefit from the const_missing and reloading approach. If you want other classes (like some in lib/) to reload, you must use require_dependency to do it.
-
-* Added Florian Gross' latest version of Breakpointer and friends that fixes a variaty of bugs #441 [Florian Gross]
-
-* Fixed skeleton Rakefile to work with sqlite3 out of the box #521 [rasputnik]
-
-* Fixed that script/breakpointer didn't get the Ruby path rewritten as the other scripts #523 [brandt@kurowski.net]
-
-* Fixed handling of syntax errors in models that had already been succesfully required once in the current interpreter
-
-* Fixed that models that weren't referenced in associations weren't being reloaded in the development mode by reinstating the reload
-
-* Fixed that generate scaffold would produce bad functional tests
-
-* Fixed that FCGI can also display SyntaxErrors
-
-* Upgraded to Active Record 1.6.0, Action Pack 1.4.0
-
-
-*0.9.4.1* (January 18th, 2005)
-
-* Added 5-second timeout to WordNet alternatives on creating reserved-word models #501 [Marcel Molina Jr.]
-
-* Fixed binding of caller #496 [Alexey]
-
-* Upgraded to Active Record 1.5.1, Action Pack 1.3.1, Action Mailer 0.6.1
-
-
-*0.9.4* (January 17th, 2005)
-
-* Added that ApplicationController will catch a ControllerNotFound exception if someone attempts to access a url pointing to an unexisting controller [Tobias Lütke]
-
-* Flipped code-to-test ratio around to be more readable #468 [Scott Baron]
-
-* Fixed log file permissions to be 666 instead of 777 (so they're not executable) #471 [Lucas Carlson]
-
-* Fixed that auto reloading would some times not work or would reload the models twice #475 [Tobias Lütke]
-
-* Added rewrite rules to deal with caching to public/.htaccess
-
-* Added the option to specify a controller name to "generate scaffold" and made the default controller name the plural form of the model.
-
-* Added that rake clone_structure_to_test, db_structure_dump, and purge_test_database tasks now pick up the source database to use from
- RAILS_ENV instead of just forcing development #424 [Tobias Lütke]
-
-* Fixed script/console to work with Windows (that requires the use of irb.bat) #418 [Chris McGrath]
-
-* Fixed WEBrick servlet slowdown over time by restricting the load path reloading to mod_ruby
-
-* Removed Fancy Indexing as a default option on the WEBrick servlet as it made it harder to use various caching schemes
-
-* Upgraded to Active Record 1.5, Action Pack 1.3, Action Mailer 0.6
-
-
-*0.9.3* (January 4th, 2005)
-
-* Added support for SQLite in the auto-dumping/importing of schemas for development -> test #416
-
-* Added automated rewriting of the shebang lines on installs through the gem rails command #379 [Manfred Stienstra]
-
-* Added ActionMailer::Base.deliver_method = :test to the test environment so that mail objects are available in ActionMailer::Base.deliveries
- for functional testing.
-
-* Added protection for creating a model through the generators with a name of an existing class, like Thread or Date.
- It'll even offer you a synonym using wordnet.princeton.edu as a look-up. No, I'm not kidding :) [Florian Gross]
-
-* Fixed dependency management to happen in a unified fashion for Active Record and Action Pack using the new Dependencies module. This means that
- the environment options needs to change from:
-
- Before in development.rb:
- ActionController::Base.reload_dependencies = true  
- ActiveRecord::Base.reload_associations     = true
-
- Now in development.rb:
- Dependencies.mechanism = :load
-
- Before in production.rb and test.rb:
- ActionController::Base.reload_dependencies = false
- ActiveRecord::Base.reload_associations     = false
-
- Now in production.rb and test.rb:
- Dependencies.mechanism = :require
-
-* Fixed problems with dependency caching and controller hierarchies on Ruby 1.8.2 in development mode #351
-
-* Fixed that generated action_mailers doesnt need to require the action_mailer since thats already done in the environment #382 [Lucas Carlson]
-
-* Upgraded to Action Pack 1.2.0 and Active Record 1.4.0
-
-
-*0.9.2*
-
-* Fixed CTRL-C exists from the Breakpointer to be a clean affair without error dumping [Kent Sibilev]
-
-* Fixed "rake stats" to work with sub-directories in models and controllers and to report the code to test ration [Scott Baron]
-
-* Added that Active Record associations are now reloaded instead of cleared to work with the new const_missing hook in Active Record.
-
-* Added graceful handling of an inaccessible log file by redirecting output to STDERR with a warning #330 [rainmkr]
-
-* Added support for a -h/--help parameter in the generator #331 [Ulysses]
-
-* Fixed that File.expand_path in config/environment.rb would fail when dealing with symlinked public directories [mjobin]
-
-* Upgraded to Action Pack 1.1.0 and Active Record 1.3.0
-
-
-*0.9.1*
-
-* Upgraded to Action Pack 1.0.1 for important bug fix
-
-* Updated gem dependencies
-
-
-*0.9.0*
-
-* Renamed public/dispatch.servlet to script/server -- it wasn't really dispatching anyway as its delegating calls to public/dispatch.rb
-
-* Renamed AbstractApplicationController and abstract_application.rb to ApplicationController and application.rb, so that it will be possible
- for the framework to automatically pick up on app/views/layouts/application.rhtml and app/helpers/application.rb
-
-* Added script/console that makes it even easier to start an IRB session for interacting with the domain model. Run with no-args to
- see help.
-
-* Added breakpoint support through the script/breakpointer client. This means that you can break out of execution at any point in
- the code, investigate and change the model, AND then resume execution! Example:
-
- class WeblogController < ActionController::Base
- def index
- @posts = Post.find_all
- breakpoint "Breaking out from the list"
- end
- end
-
- So the controller will accept the action, run the first line, then present you with a IRB prompt in the breakpointer window.
- Here you can do things like:
-
- Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint'
-
- >> @posts.inspect
- => "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
- #<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
- >> @posts.first.title = "hello from a breakpoint"
- => "hello from a breakpoint"
-
- ...and even better is that you can examine how your runtime objects actually work:
-
- >> f = @posts.first
- => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
- >> f.
- Display all 152 possibilities? (y or n)
-
- Finally, when you're ready to resume execution, you press CTRL-D
-
-* Changed environments to be configurable through an environment variable. By default, the environment is "development", but you
- can change that and set your own by configuring the Apache vhost with a string like (mod_env must be available on the server):
-
- SetEnv RAILS_ENV production
-
- ...if you're using WEBrick, you can pick the environment to use with the command-line parameters -e/--environment, like this:
-
- ruby public/dispatcher.servlet -e production
-
-* Added a new default environment called "development", which leaves the production environment to be tuned exclusively for that.
-
-* Added a start_server in the root of the Rails application to make it even easier to get started
-
-* Fixed public/.htaccess to use RewriteBase and share the same rewrite rules for all the dispatch methods
-
-* Fixed webrick_server to handle requests in a serialized manner (the Rails reloading infrastructure is not thread-safe)
-
-* Added support for controllers in directories. So you can have:
-
- app/controllers/account_controller.rb # URL: /account/
- app/controllers/admin/account_controller.rb # URL: /admin/account/
-
- NOTE: You need to update your public/.htaccess with the new rules to pick it up
-
-* Added reloading for associations and dependencies under cached environments like FastCGI and mod_ruby. This makes it possible to use
- those environments for development. This is turned on by default, but can be turned off with
- ActiveRecord::Base.reload_associations = false and ActionController::Base.reload_dependencies = false in production environments.
-
-* Added support for sub-directories in app/models. So now you can have something like Basecamp with:
-
- app/models/accounting
- app/models/project
- app/models/participants
- app/models/settings
-
- It's poor man's namespacing, but only for file-system organization. You still require files just like before.
- Nothing changes inside the files themselves.
-
-
-* Fixed a few references in the tests generated by new_mailer [Jeremy Kemper]
-
-* Added support for mocks in testing with test/mocks
-
-* Cleaned up the environments a bit and added global constant RAILS_ROOT
-
-
-*0.8.5* (9)
-
-* Made dev-util available to all tests, so you can insert breakpoints in any test case to get an IRB prompt at that point [Jeremy Kemper]:
-
- def test_complex_stuff
- @david.projects << @new_project
- breakpoint "Let's have a closer look at @david"
- end
-
- You need to install dev-utils yourself for this to work ("gem install dev-util").
-
-* Added shared generator behavior so future upgrades should be possible without manually copying over files [Jeremy Kemper]
-
-* Added the new helper style to both controller and helper templates [Jeremy Kemper]
-
-* Added new_crud generator for creating a model and controller at the same time with explicit scaffolding [Jeremy Kemper]
-
-* Added configuration of Test::Unit::TestCase.fixture_path to test_helper to concide with the new AR fixtures style
-
-* Fixed that new_model was generating singular table/fixture names
-
-* Upgraded to Action Mailer 0.4.0
-
-* Upgraded to Action Pack 0.9.5
-
-* Upgraded to Active Record 1.1.0
-
-
-*0.8.0 (15)*
-
-* Removed custom_table_name option for new_model now that the Inflector is as powerful as it is
-
-* Changed the default rake action to just do testing and separate API generation and coding statistics into a "doc" task.
-
-* Fixed WEBrick dispatcher to handle missing slashes in the URLs gracefully [alexey]
-
-* Added user option for all postgresql tool calls in the rakefile [elvstone]
-
-* Fixed problem with running "ruby public/dispatch.servlet" instead of "cd public; ruby dispatch.servlet" [alexey]
-
-* Fixed WEBrick server so that it no longer hardcodes the ruby interpreter used to "ruby" but will get the one used based
- on the Ruby runtime configuration. [Marcel Molina Jr.]
-
-* Fixed Dispatcher so it'll route requests to magic_beans to MagicBeansController/magic_beans_controller.rb [Caio Chassot]
-
-* "new_controller MagicBeans" and "new_model SubscriptionPayments" will now both behave properly as they use the new Inflector.
-
-* Fixed problem with MySQL foreign key constraint checks in Rake :clone_production_structure_to_test target [Andreas Schwarz]
-
-* Changed WEBrick server to by default be auto-reloading, which is slower but makes source changes instant.
- Class compilation cache can be turned on with "-c" or "--cache-classes".
-
-* Added "-b/--binding" option to WEBrick dispatcher to bind the server to a specific IP address (default: 127.0.0.1) [Kevin Temp]
-
-* dispatch.fcgi now DOESN'T set FCGI_PURE_RUBY as it was slowing things down for now reason [Andreas Schwarz]
-
-* Added new_mailer generator to work with Action Mailer
-
-* Included new framework: Action Mailer 0.3
-
-* Upgraded to Action Pack 0.9.0
-
-* Upgraded to Active Record 1.0.0
-
-
-*0.7.0*
-
-* Added an optional second argument to the new_model script that allows the programmer to specify the table name,
- which will used to generate a custom table_name method in the model and will also be used in the creation of fixtures.
- [Kevin Radloff]
-
-* script/new_model now turns AccountHolder into account_holder instead of accountholder [Kevin Radloff]
-
-* Fixed the faulty handleing of static files with WEBrick [Andreas Schwarz]
-
-* Unified function_test_helper and unit_test_helper into test_helper
-
-* Fixed bug with the automated production => test database dropping on PostgreSQL [dhawkins]
-
-* create_fixtures in both the functional and unit test helper now turns off the log during fixture generation
- and can generate more than one fixture at a time. Which makes it possible for assignments like:
-
- @people, @projects, @project_access, @companies, @accounts =
- create_fixtures "people", "projects", "project_access", "companies", "accounts"
-
-* Upgraded to Action Pack 0.8.5 (locally-scoped variables, partials, advanced send_file)
-
-* Upgraded to Active Record 0.9.5 (better table_name guessing, cloning, find_all_in_collection)
-
-
-*0.6.5*
-
-* No longer specifies a template for rdoc, so it'll use whatever is default (you can change it in the rakefile)
-
-* The new_model generator will now use the same rules for plural wordings as Active Record
- (so Category will give categories, not categorys) [Kevin Radloff]
-
-* dispatch.fcgi now sets FCGI_PURE_RUBY to true to ensure that it's the Ruby version that's loaded [danp]
-
-* Made the GEM work with Windows
-
-* Fixed bug where mod_ruby would "forget" the load paths added when switching between controllers
-
-* PostgreSQL are now supported for the automated production => test database dropping [Kevin Radloff]
-
-* Errors thrown by the dispatcher are now properly handled in FCGI.
-
-* Upgraded to Action Pack 0.8.0 (lots and lots and lots of fixes)
-
-* Upgraded to Active Record 0.9.4 (a bunch of fixes)
-
-
-*0.6.0*
-
-* Added AbstractionApplicationController as a superclass for all controllers generated. This class can be used
- to carry filters and methods that are to be shared by all. It has an accompanying ApplicationHelper that all
- controllers will also automatically have available.
-
-* Added environments that can be included from any script to get the full Active Record and Action Controller
- context running. This can be used by maintenance scripts or to interact with the model through IRB. Example:
-
- require 'config/environments/production'
-
- for account in Account.find_all
- account.recalculate_interests
- end
-
- A short migration script for an account model that had it's interest calculation strategy changed.
-
-* Accessing the index of a controller with "/weblog" will now redirect to "/weblog/" (only on Apache, not WEBrick)
-
-* Simplified the default Apache config so even remote requests are served off CGI as a default.
- You'll now have to do something specific to activate mod_ruby and FCGI (like using the force urls).
- This should make it easier for new comers that start on an external server.
-
-* Added more of the necessary Apache options to .htaccess to make it easier to setup
-
-* Upgraded to Action Pack 0.7.9 (lots of fixes)
-
-* Upgraded to Active Record 0.9.3 (lots of fixes)
-
-
-*0.5.7*
-
-* Fixed bug in the WEBrick dispatcher that prevented it from getting parameters from the URL
- (through GET requests or otherwise)
-
-* Added lib in root as a place to store app specific libraries
-
-* Added lib and vendor to load_path, so anything store within can be loaded directly.
- Hence lib/redcloth.rb can be loaded with require "redcloth"
-
-* Upgraded to Action Pack 0.7.8 (lots of fixes)
-
-* Upgraded to Active Record 0.9.2 (minor upgrade)
-
-
-*0.5.6*
-
-* Upgraded to Action Pack 0.7.7 (multipart form fix)
-
-* Updated the generated template stubs to valid XHTML files
-
-* Ensure that controllers generated are capitalized, so "new_controller TodoLists"
- gives the same as "new_controller Todolists" and "new_controller todolists".
-
-
-*0.5.5*
-
-* Works on Windows out of the box! (Dropped symlinks)
-
-* Added webrick dispatcher: Try "ruby public/dispatch.servlet --help" [Florian Gross]
-
-* Report errors about initialization to browser (instead of attempting to use uninitialized logger)
-
-* Upgraded to Action Pack 0.7.6
-
-* Upgraded to Active Record 0.9.1
-
-* Added distinct 500.html instead of reusing 404.html
-
-* Added MIT license
-
-
-*0.5.0*
-
-* First public release
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
new file mode 100644
index 0000000000..ccccc178c5
--- /dev/null
+++ b/railties/CHANGELOG.md
@@ -0,0 +1,2514 @@
+## Rails 4.0.0 (unreleased) ##
+
+* Improved `rake routes` output for redirects *Łukasz Strzałkowski & Andrew White*
+
+* Load all environments available in `config.paths["config/environments"]`. *Piotr Sarnacki*
+
+* The application generator generates `public/humans.txt` with some basic data. *Paul Campbell*
+
+* Add `config.queue_consumer` to allow the default consumer to be configurable. *Carlos Antonio da Silva*
+
+* Add Rails.queue as an interface with a default implementation that consumes jobs in a separate thread. *Yehuda Katz*
+
+* Remove Rack::SSL in favour of ActionDispatch::SSL. *Rafael Mendonça França*
+
+* Remove Active Resource from Rails framework. *Prem Sichangrist*
+
+* Allow to set class that will be used to run as a console, other than IRB, with `Rails.application.config.console=`. It's best to add it to `console` block. *Piotr Sarnacki*
+
+ Example:
+
+ # it can be added to config/application.rb
+ console do
+ # this block is called only when running console,
+ # so we can safely require pry here
+ require "pry"
+ config.console = Pry
+ end
+
+* Add convenience `hide!` method to Rails generators to hide current generator
+ namespace from showing when running `rails generate`. *Carlos Antonio da Silva*
+
+* Scaffold now uses `content_tag_for` in index.html.erb *José Valim*
+
+* Rails::Plugin has gone. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino*
+
+
+## Rails 3.2.2 (March 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.1 (January 26, 2012) ##
+
+* Documentation fixes.
+
+* Migration generation understands decimal{1.2} and decimal{1-2}, in
+ addition to decimal{1,2}. *José Valim*
+
+
+## Rails 3.2.0 (January 20, 2012) ##
+
+* Turn gem has been removed from default Gemfile. We still looking for a best presentation for tests output. *Guillermo Iguaran*
+
+* Rails::Plugin is deprecated and will be removed in Rails 4.0. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino*
+
+* Guides are available as a single .mobi for the Kindle and free Kindle readers apps. *Michael Pearson & Xavier Noria*
+
+* Allow scaffold/model/migration generators to accept a "index" and "uniq" modifiers, as in: "tracking_id:integer:uniq" in order to generate (unique) indexes. Some types also accept custom options, for instance, you can specify the precision and scale for decimals as "price:decimal{7,2}". *Dmitrii Samoilov*
+
+* Added `config.exceptions_app` to set the exceptions application invoked by the ShowException middleware when an exception happens. Defaults to `ActionDispatch::PublicExceptions.new(Rails.public_path)`. *José Valim*
+
+* Speed up development by only reloading classes if dependencies files changed. This can be turned off by setting `config.reload_classes_only_on_change` to false. *José Valim*
+
+* New applications get a flag `config.active_record.auto_explain_threshold_in_seconds` in the environments configuration files. With a value of 0.5 in development.rb, and commented out in production.rb. No mention in test.rb. *fxn*
+
+* Add DebugExceptions middleware which contains features extracted from ShowExceptions middleware *José Valim*
+
+* Display mounted engine's routes in `rake routes` *Piotr Sarnacki*
+
+* Allow to change the loading order of railties with `config.railties_order=` *Piotr Sarnacki*
+
+ Example:
+ config.railties_order = [Blog::Engine, :main_app, :all]
+
+* Scaffold returns 204 No Content for API requests without content. This makes scaffold work with jQuery out of the box *José Valim*
+
+* Update Rails::Rack::Logger middleware to apply any tags set in config.log_tags to the newly ActiveSupport::TaggedLogging Rails.logger. This makes it easy to tag log lines with debug information like subdomain and request id -- both very helpful in debugging multi-user production applications *DHH*
+
+* Default options to `rails new` can be set in ~/.railsrc. You can specify extra command-line arguments to be used every time
+ 'rails new' runs in the .railsrc configuration file in your home directory. *Guillermo Iguaran*
+
+* Add destroy alias to Rails engines *Guillermo Iguaran*
+
+* Add destroy alias for Rails command line. This allows the following: `rails d model post` *Andrey Ognevsky*
+
+* Attributes on scaffold and model generators default to string. This allows the following: "rails g scaffold Post title body:text author" *José Valim*
+
+* Remove old plugin generator (`rails generate plugin`) in favor of `rails plugin new` command *Guillermo Iguaran*
+
+* Remove old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API *Guillermo Iguaran*
+
+
+## Rails 3.1.4 (March 1, 2012) ##
+
+* Setting config.force_ssl also marks the session cookie as secure.
+
+ *José Valim*
+
+* Add therubyrhino to Gemfile in new applications when running under JRuby.
+
+ *Guillermo Iguaran*
+
+
+## Rails 3.1.3 (November 20, 2011) ##
+
+* New apps should be generated with a sass-rails dependency of 3.1.5, not 3.1.5.rc.2
+
+
+## Rails 3.1.2 (November 18, 2011) ##
+
+* Engines: don't blow up if db/seeds.rb is missing.
+
+ *Jeremy Kemper*
+
+* `rails new foo --skip-test-unit` should not add the `:test` task to the rake default task.
+ *GH 2564*
+
+ *José Valim*
+
+## Rails 3.1.1 (October 07, 2011) ##
+
+* Add jquery-rails to Gemfile of plugins, test/dummy app needs it. Closes #3091. *Santiago Pastorino*
+
+* Add config.assets.initialize_on_precompile which, when set to false, forces
+ `rake assets:precompile` to load the application but does not initialize it.
+
+ To the app developer, this means configuration add in
+ config/initializers/* will not be executed.
+
+ Plugins developers need to special case their initializers that are
+ meant to be run in the assets group by adding :group => :assets.
+
+## Rails 3.1.0 (August 30, 2011) ##
+
+* The default database schema file is written as UTF-8. *Aaron Patterson*
+
+* Generated apps with --dev or --edge flags depend on git versions of
+ sass-rails and coffee-rails. *Santiago Pastorino*
+
+* Rack::Sendfile middleware is used only if x_sendfile_header is present. *Santiago Pastorino*
+
+* Add JavaScript Runtime name to the Rails Info properties. *DHH*
+
+* Make pp enabled by default in Rails console. *Akira Matsuda*
+
+* Add alias `r` for rails runner. *Jordi Romero*
+
+* Make sprockets/railtie require explicit and add --skip-sprockets to app generator *José Valim*
+
+* Added Rails.groups that automatically handles Rails.env and ENV["RAILS_GROUPS"] *José Valim*
+
+* The new rake task assets:clean removes precompiled assets. *fxn*
+
+* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. *fxn*
+
+* Fixed database tasks for jdbc* adapters #jruby *Rashmi Yadav*
+
+* Template generation for jdbcpostgresql #jruby *Vishnu Atrai*
+
+* Template generation for jdbcmysql and jdbcsqlite3 #jruby *Arun Agrawal*
+
+* The -j option of the application generator accepts an arbitrary string. If passed "foo",
+ the gem "foo-rails" is added to the Gemfile, and the application JavaScript manifest
+ requires "foo" and "foo_ujs". As of this writing "prototype-rails" and "jquery-rails"
+ exist and provide those files via the asset pipeline. Default is "jquery". *fxn*
+
+* jQuery is no longer vendored, it is provided from now on by the jquery-rails gem. *fxn*
+
+* Prototype and Scriptaculous are no longer vendored, they are provided from now on
+ by the prototype-rails gem. *fxn*
+
+* 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, application, and mailer 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*
+
+* Added `config.force_ssl` configuration which loads Rack::SSL middleware and force all requests to be under HTTPS protocol *DHH, Prem Sichanugrist, and Josh Peek*
+
+* Added `rails plugin new` command which generates rails plugin with gemspec, tests and dummy application for testing *Piotr Sarnacki*
+
+* Added -j parameter with jquery/prototype as options. Now you can create your apps with jQuery using `rails new myapp -j jquery`. The default is still Prototype. *siong1987*
+
+* Added Rack::Etag and Rack::ConditionalGet to the default middleware stack *José Valim*
+
+* Added Rack::Cache to the default middleware stack *Yehuda Katz and Carl Lerche*
+
+* Engine is now rack application *Piotr Sarnacki*
+
+* Added middleware stack to Engine *Piotr Sarnacki*
+
+* Engine can now load plugins *Piotr Sarnacki*
+
+* Engine can load its own environment file *Piotr Sarnacki*
+
+* Added helpers to call engines' route helpers from application and vice versa *Piotr Sarnacki*
+
+* Task for copying plugins' and engines' migrations to application's db/migrate directory *Piotr Sarnacki*
+
+* Changed ActionDispatch::Static to allow handling multiple directories *Piotr Sarnacki*
+
+* Added isolate_namespace() method to Engine, which sets Engine as isolated *Piotr Sarnacki*
+
+* Include all helpers from plugins and shared engines in application *Piotr Sarnacki*
+
+
+## Rails 3.0.12 (March 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.0.11 (November 18, 2011) ##
+
+* Updated Prototype UJS to lastest version fixing multiples errors in IE [Guillermo Iguaran]
+
+
+## Rails 3.0.10 (August 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.9 (June 16, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.8 (June 7, 2011) ##
+
+* Fix Rake 0.9.0 support.
+
+
+## Rails 3.0.7 (April 18, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.6 (April 5, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.5 (February 26, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.4 (February 8, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.3 (November 16, 2010) ##
+
+* No changes.
+
+
+## Rails 3.0.2 (November 15, 2010) ##
+
+* No changes.
+
+
+## Rails 3.0.1 (October 15, 2010) ##
+
+* No Changes, just a version bump.
+
+
+## Rails 3.0.0 (August 29, 2010) ##
+
+* Application generation: --skip-testunit and --skip-activerecord become --skip-test-unit and --skip-active-record respectively. *fxn*
+
+* Added console to Rails::Railtie as a hook called just after console starts. *José Valim*
+
+* Rails no longer autoload code in lib for application. You need to explicitly require it. *José Valim*
+
+* Rails::LogSubscriber was renamed to ActiveSupport::LogSubscriber *José Valim*
+
+* config.load_(once_)paths in config/application.rb got renamed to config.autoload_(once_)paths. *fxn*
+
+* Abort generation/booting on Ruby 1.9.1. *fxn*
+
+* Made the rails command work even when you're in a subdirectory *Chad Fowler*
+
+* Removed Rails Metal [Yehuda Katz, José Valim].
+
+* Renamed config.cookie_secret to config.secret_token and pass it as env key. *José Valim*
+
+* Session store configuration has changed *Yehuda Katz, Carl Lerche*
+
+ config.session_store :cookie_store, {:key => "..."}
+ config.cookie_secret = "fdsfhisdghfidugnfdlg"
+
+* railtie_name and engine_name are deprecated. You can now add any object to
+ the configuration object: config.your_plugin = {} *José Valim*
+
+* Added config.generators.templates to provide alternative paths for the generators
+ to look for templates *José Valim*
+
+* Added "rake about" as a replacement for script/about *DHH*
+
+* Removed all the default commands in script/* and replaced them with script/rails and a rails command that'll act the same when run from within the app [DHH]. Example:
+
+ ./script/generate scaffold post title:string can now be called as rails g scaffold post title:string
+
+ Run rails --help inside an app for more help.
+
+* Removed config/initializers/new_rails_defaults.rb as all frameworks now follow the settings from it *DHH*
+
+* Set config.time_zone to UTC by default *DHH*
+
+* Added default .gitignore (this is just recognizing Git market share, don't throw a hissy if you use another SCM) *DHH*
+
+* Added cookies.permanent, cookies.signed, and cookies.permanent.signed accessor for common cookie actions [DHH]. Examples:
+
+ cookies.permanent[:prefers_open_id] = true
+ # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+
+ cookies.signed[:discount] = 45
+ # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/
+
+ cookies.signed[:discount]
+ # => 45 (if the cookie was changed, you'll get a InvalidSignature exception)
+
+ cookies.permanent.signed[:remember_me] = current_user.id
+ # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+
+ ...to use the signed cookies, you need to set a secret to ActionController::Base.cookie_verifier_secret (automatically done in config/initializers/cookie_verification_secret.rb for new Rails applications).
+
+* Added config/initializers/cookie_verification_secret.rb with an auto-generated secret for using ActionController::Base#cookies.signed *DHH*
+
+* Fixed that the debugger wouldn't go into IRB mode because of left-over ARGVs *DHH*
+
+* I18n support for plugins. #2325 *Antonio Tapiador, Sven Fuchs*
+
+* Ruby 1.9: use UTF-8 for default internal and external encodings. *Jeremy Kemper*
+
+* Added db/seeds.rb as a default file for storing seed data for the database. Can be loaded with rake db:seed (or created alongside the db with db:setup). (This is also known as the "Stop Putting Gawd Damn Seed Data In Your Migrations" feature) *DHH*
+
+
+## 2.3.2 Final (March 15, 2009) ##
+
+* Remove outdated script/plugin options *Pratik Naik*
+
+* Allow metal to live in plugins #2045 *Matthew Rudy*
+
+* Added metal *Josh Peek*
+
+* Remove script/performance/request in favour of the performance integration tests. *Pratik Naik*
+
+ To continue using script/performance/request, install the request_profiler plugin :
+
+ script/plugin install git://github.com/rails/request_profiler.git
+
+* Add a rake task to apply a template to an existing application : rake rails:template LOCATION=~/template.rb *Pratik Naik*
+
+* Add "-m/--template" option to Rails generator to apply a template to the generated application. *Jeremy McAnally*
+
+ This has been extracted from rg - http://github.com/jeremymcanally/rg
+
+ Example:
+
+ # template.rb
+
+ # Install plugins from git or svn
+ plugin "will-paginate", :git => "git://github.com/mislav/will_paginate.git"
+ plugin "old-restful-auth", :svn => "http://svn.techno-weenie.net/projects/plugins/restful_authentication/"
+
+ # Add gems to environment.rb
+ gem "jeremymcanally-context"
+ gem "bluecloth"
+
+ # Vendor file. Data in a string or...
+ vendor("borrowed.rb", <<CODE
+ def helpful_method
+ do_something_helpful_here
+ end
+ CODE
+
+ # ...file data from block return value.
+ # #initializer creates a new initializer file
+ initializer("crypto.rb") do
+ salt = "--#{Time.now}--#{rand}--#{srand(Time.now.to_i)}"
+
+ "SPECIAL_SALT = '#{salt}'"
+ end
+
+ Usage:
+
+ To use a template, provide a file path or URL:
+
+ 1. Using a local file :
+
+ rails <application name> -m /path/to/my/template.rb
+
+ 2. Or directly from a URL :
+
+ rails <application name> --template=http://gist.github.com/31208.txt
+
+* Extracted the process scripts (inspector, reaper, spawner) into the plugin irs_process_scripts *David Heinemeier Hansson*
+
+* Changed Rails.root to return a Pathname object (allows for Rails.root.join('app', 'controllers') => "#{RAILS_ROOT}/app/controllers") #1482 *Damian Janowski/?*
+
+* Added view path support for engines *David Heinemeier Hansson*
+
+* Added that config/routes.rb files in engine plugins are automatically loaded (and reloaded when they change in dev mode) *David Heinemeier Hansson*
+
+* Added app/[models|controllers|helpers] to the load path for plugins that has an app directory (go engines ;)) *David Heinemeier Hansson*
+
+* Add config.preload_frameworks to load all frameworks at startup. Default to false so Rails autoloads itself as it's used. Turn this on for Passenger and JRuby. Also turned on by config.threadsafe! *Jeremy Kemper*
+
+* Add a rake task to generate dispatchers : rake rails:generate_dispatchers *Pratik Naik*
+
+* "rails <app>" will not generate public/dispatch.cgi/fcgi/rb files by default now. Please use "--with-dispatchers" option if you need them. *Yaroslav Markin, Pratik Naik*
+
+* Added rake rails:update:application_controller to renamed application.rb to application_controller.rb -- included in rake rails:update so upgrading to 2.3 will automatically trigger it #1439 *kastner*
+
+* Added Rails.backtrace_cleaner as an accessor for the Rails::BacktraceCleaner instance used by the framework to cut down on backtrace noise and config/initializers/backtrace_silencers.rb to add your own (or turn them all off) *David Heinemeier Hansson*
+
+* Switch from Test::Unit::TestCase to ActiveSupport::TestCase. *Jeremy Kemper*
+
+* Added config.i18n settings gatherer to config/environment, auto-loading of all locales in config/locales/*.rb,yml, and config/locales/en.yml as a sample locale *David Heinemeier Hansson*
+
+* BACKWARDS INCOMPATIBLE: Renamed application.rb to application_controller.rb and removed all the special casing that was in place to support the former. You must do this rename in your own application when you upgrade to this version *David Heinemeier Hansson*
+
+
+## 2.2.1 RC2 (November 14th, 2008) ##
+
+* Fixed plugin generator so that generated unit tests would subclass ActiveSupport::TestCase, also introduced a helper script to reduce the needed require statements #1137 *Mathias Meyer*
+
+* Update Prototype to 1.6.0.3 *sam*
+
+
+## 2.2.0 RC1 (October 24th, 2008) ##
+
+* Fixed that sqlite would report "db/development.sqlite3 already exists" whether true or not on db:create #614 *Antonio Cangiano*
+
+* Added config.threadsafe! to toggle allow concurrency settings and disable the dependency loader *Josh Peek*
+
+* Turn cache_classes on by default *Josh Peek*
+
+* Added configurable eager load paths. Defaults to app/models, app/controllers, and app/helpers *Josh Peek*
+
+* Introduce simple internationalization support. *Ruby i18n team*
+
+* Make script/plugin install <plugin> -r <revision> option work with git based plugins. #257. [Tim Pope Jakub Kuźma]. Example:
+
+ script/plugin install git://github.com/mislav/will_paginate.git -r agnostic # Installs 'agnostic' branch
+ script/plugin install git://github.com/dchelimsky/rspec.git -r 'tag 1.1.4'
+
+* Added Rails.initialized? flag *Josh Peek*
+
+* Make rake test:uncommitted work with Git. *Tim Pope*
+
+* Added Thin support to script/server. #488 *Bob Klosinski*
+
+* Fix script/about in production mode. #370 *Cheah Chu Yeow, Xavier Noria, David Krmpotic*
+
+* Add the gem load paths before the framework is loaded, so certain gems like RedCloth and BlueCloth can be frozen.
+
+* Fix discrepancies with loading rails/init.rb from gems.
+
+* Plugins check for the gem init path (rails/init.rb) before the standard plugin init path (init.rb) *Jacek Becela*
+
+* Changed all generated tests to use the test/do declaration style *David Heinemeier Hansson*
+
+* Wrapped Rails.env in StringInquirer so you can do Rails.env.development? *David Heinemeier Hansson*
+
+* Fixed that RailsInfoController wasn't considering all requests local in development mode (Edgard Castro) *#310 state:resolved*
+
+
+## 2.1.0 (May 31st, 2008) ##
+
+* script/dbconsole fires up the command-line database client. #102 *Steve Purcell*
+
+* Fix bug where plugin init.rb files from frozen gem specs weren't being run. (pjb3) *#122 state:resolved*
+
+* Made the location of the routes file configurable with config.routes_configuration_file (Scott Fleckenstein) *#88*
+
+* Rails Edge info returns the latest git commit hash *Francesc Esplugas*
+
+* Added Rails.public_path to control where HTML and assets are expected to be loaded from (defaults to Rails.root + "/public") #11581 *Nick Sieger*
+
+* rake time:zones:local finds correct base utc offset for zones in the Southern Hemisphere *Geoff Buesing*
+
+* Don't require rails/gem_builder during rails initialization, it's only needed for the gems:build task. *Rick Olson*
+
+* script/performance/profiler compatibility with the ruby-prof >= 0.5.0. Closes #9176. *Jonathan del Strother*
+
+* Flesh out rake gems:unpack to unpack all gems, and add rake gems:build for native extensions. #11513 *ddollar*
+
+ rake gems:unpack # unpacks all gems
+ rake gems:unpack GEM=mygem # unpacks only the gem 'mygem'
+
+ rake gems:build # builds all unpacked gems
+ rake gems:build GEM=mygem # builds only the gem 'mygem'
+
+* Add config.active_support for future configuration options. Also, add more new Rails 3 config settings to new_rails_defaults.rb *Rick Olson*
+
+* Add Rails.logger, Rails.root, Rails.env and Rails.cache shortcuts for RAILS_* constants *Pratik Naik*
+
+* Allow files in plugins to be reloaded like the rest of the application. *Rick Olson*
+
+ Enables or disables plugin reloading.
+
+ config.reload_plugins = true
+
+ You can get around this setting per plugin.
+ If #reload_plugins? == false (DEFAULT), add this to your plugin's init.rb to make it reloadable:
+
+ Dependencies.load_once_paths.delete lib_path
+
+ If #reload_plugins? == true, add this to your plugin's init.rb to only load it once:
+
+ Dependencies.load_once_paths << lib_path
+
+* Small tweak to allow plugins to specify gem dependencies. *Rick Olson*
+
+ \# OLD open_id_authentication plugin init.rb
+ require 'yadis'
+ require 'openid'
+ ActionController::Base.send :include, OpenIdAuthentication
+
+ \# NEW
+ config.gem "ruby-openid", :lib => "openid", :version => "1.1.4"
+ config.gem "ruby-yadis", :lib => "yadis", :version => "0.3.4"
+
+ config.after_initialize do
+ ActionController::Base.send :include, OpenIdAuthentication
+ end
+
+* Added config.gem for specifying which gems are required by the application, as well as rake tasks for installing and freezing gems. *Rick Olson*
+
+ Rails::Initializer.run do |config|
+ config.gem "bj"
+ config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
+ config.gem "aws-s3", :lib => "aws/s3"
+ end
+
+ \# List required gems.
+ rake gems
+
+ \# Install all required gems:
+ rake gems:install
+
+ \# Unpack specified gem to vendor/gems/gem_name-x.x.x
+ rake gems:unpack GEM=bj
+
+* Removed the default .htaccess configuration as there are so many good deployment options now (kept it as an example in README) *David Heinemeier Hansson*
+
+* config.time_zone accepts TZInfo::Timezone identifiers as well as Rails TimeZone identifiers *Geoff Buesing*
+
+* Rails::Initializer#initialize_time_zone raises an error if value assigned to config.time_zone is not recognized. Rake time zone tasks only require ActiveSupport instead of entire environment *Geoff Buesing*
+
+* Stop adding the antiquated test/mocks/* directories and only add them to the path if they're still there for legacy reasons *David Heinemeier Hansson*
+
+* Added that gems can now be plugins if they include rails/init.rb #11444 *John Barnette*
+
+* Added Plugin#about method to programmatically access the about.yml in a plugin #10979 *James Adam*
+
+ plugin = Rails::Plugin.new(path_to_my_plugin)
+ plugin.about["author"] # => "James Adam"
+ plugin.about["url"] # => "http://interblah.net"
+
+* Improve documentation. *Ryan Bigg, Jan De Poorter, Cheah Chu Yeow, Xavier Shay, Jack Danger Canty, Emilio Tagua, Xavier Noria, Sunny Ripert*
+
+* Added config.time_zone = 'UTC' in the default environment.rb *Geoff Buesing*
+
+* Added rake tasks time:zones:all, time:zones:us and time:zones:local for finding time zone names for config.time_zone option *Geoff Buesing*
+
+* Add config.time_zone for configuring the default Time.zone value. #10982 *Geoff Buesing*
+
+* Added support for installing plugins hosted at git repositories #11294 *Jack Danger Canty*
+
+* Fixed that script/generate would not look for plugin generators in plugin_paths #11000 *glv*
+
+* Fixed database rake tasks to work with charset/collation and show proper error messages on failure. Closes #11301 *matt*
+
+* Added a -e/--export to script/plugin install, uses svn export. #10847 *jon@blankpad.net)*
+
+* Reshuffle load order so that routes and observers are initialized after plugins and app initializers. Closes #10980 *Rick Olson*
+
+* Git support for script/generate. #10690 *ssoroka*
+
+* Update scaffold to use labels instead of bold tags. Closes #10757 *zach-inglis-lt3*
+
+* Resurrect WordNet synonym lookups. #10710 *tom./, matt*
+
+* Added config.cache_store to environment options to control the default cache store (default is FileStore if tmp/cache is present, otherwise MemoryStore is used) *David Heinemeier Hansson*
+
+* Added that rails:update is run when you do rails:freeze:edge to ensure you also get the latest JS and config files #10565 *jeff*
+
+* SQLite: db:drop:all doesn't fail silently if the database is already open. #10577 *Cheah Chu Yeow, mrichman*
+
+* Introduce native mongrel handler and push mutex into dispatcher. *Jeremy Kemper*
+
+* Ruby 1.9 compatibility. #1689, #10546 *Cheah Chu Yeow, frederico*
+
+
+## 2.0.2 (December 16th, 2007) ##
+
+* Changed the default database from mysql to sqlite3, so now running "rails myapp" will have a config/database.yml that's setup for SQLite3 (which in OS X Leopard is installed by default, so is the gem, so everything Just Works with no database configuration at all). To get a Rails application preconfigured for MySQL, just run "rails -d mysql myapp" *David Heinemeier Hansson*
+
+* Turned on ActionView::Base.cache_template_loading by default in config/environments/production.rb to prevent file system stat calls for every template loading to see if it changed (this means that you have to restart the application to see template changes in production mode) *David Heinemeier Hansson*
+
+* Introduce `rake secret` to output a crytographically secure secret key for use with cookie sessions #10363 *revans*
+
+* Fixed that local database creation should consider 127.0.0.1 local #9026 *parcelbrat*
+
+* Fixed that functional tests generated for scaffolds should use fixture calls instead of hard-coded IDs #10435 *boone*
+
+* Added db:migrate:redo and db:migrate:reset for rerunning existing migrations #10431, #10432 *matt*
+
+* RAILS_GEM_VERSION may be double-quoted also. #10443 *James Cox*
+
+* Update rails:freeze:gems to work with RubyGems 0.9.5. *Jeremy Kemper*
+
+
+## 2.0.1 (December 7th, 2007) ##
+
+* Fixed Active Record bug
+
+
+## 2.0.0 (December 6th, 2007) ##
+
+* The test task stops with a warning if you have pending migrations. #10377 *Josh Knowles*
+
+* Add warning to documentation about using transactional fixtures when the code under test uses transactions itself. Closes #7548 *Thijs van der Vossen*
+
+* Update Prototype to 1.6.0.1. *sam*
+
+* Update script.aculo.us to 1.8.0.1. *madrobby*
+
+* Added db:fixtures:identity as a way of locating what ID a foxy fixture was assigned #10332 *John Barnette*
+
+* Generated fixtures should not specify ids since theyre expected to be foxy fixtures #10330 *John Barnette*
+
+* Update to Prototype -r8232. *sam*
+
+* Introduce SecretKeyGenerator for more secure session secrets than CGI::Session's pseudo-random id generator. Consider extracting to Active Support later. #10286 *Hongli Lai (Phusion)*
+
+* RAILS_GEM_VERSION may be set to any valid gem version specifier. #10057 *Chad Woolley, Cheah Chu Yeow*
+
+* Load config/preinitializer.rb, if present, before loading the environment. #9943 *Chad Woolley*
+
+* FastCGI handler ignores unsupported signals like USR2 on Windows. *Grzegorz Derebecki*
+
+* Only load ActionMailer::TestCase if ActionMailer is loaded. Closes #10137 *defunkt*
+
+* Fixed that db:reset would use migrations instead of loading db/schema.rb *David Heinemeier Hansson*
+
+* Ensure the plugin loader only loads plugins once. Closes #10102 *haruki_zaemon*
+
+* Refactor Plugin Loader. Add plugin lib paths early, and add lots of tests. Closes #9795 *James Adam*
+
+* Added --skip-timestamps to generators that produce models #10036 *Tim Pope*
+
+* Update Prototype to 1.6.0 and script.aculo.us to 1.8.0. *sam, madrobby*
+
+* Added db:rollback to rollback the schema one version (or multiple as specified by STEP) *Jeffrey Allan Hardy*
+
+* Fix typo in test_helper. Closes #9925 *viktor tron*
+
+* Request profiler. *Jeremy Kemper*
+
+* config/boot.rb correctly detects RAILS_GEM_VERSION. #9834 *alexch, thewoolleyman*
+
+* Fixed incorrect migration number if script/generate executed outside of Rails root #7080 *Jeremy McAnally*
+
+* Update Prototype to 1.6.0_rc1 and script.aculo.us to 1.8.0 preview 0. *sam, madrobby*
+
+* Generated fixtures use the actual primary key instead of id. #4343 *Frederick Ros, Tarmo Tänav*
+
+* Extend the console +helper+ method to allow you to include custom helpers. e.g:
+ >> helper :posts
+ >> helper.some_method_from_posts_helper(Post.find(1))
+
+* db:create works with remote databases whereas db:create:all only creates
+ databases on localhost. #9753 *Trevor Wennblom*
+
+* Removed calls to fixtures in generated tests as fixtures :all is now present by default in test_helper.rb *David Heinemeier Hansson*
+
+* Add --prefix option to script/server when using mongrel. *dacat*
+
+
+## 2.0.0 Preview Release (September 29th, 2007) Includes duplicates of changes from 1.1.4 - 1.2.3 ##
+
+* Fixed that installing plugins from SVN repositories that use trunk/ will work #8188 *evan*
+
+* Moved the SourceAnnotationExtractor to a separate file in case libraries try to load the rails rake tasks twice. *Rick Olson*
+
+* Moved Dispatcher to ActionController::Dispatcher. *Jeremy Kemper*
+
+* Changed the default logger from Ruby's own Logger with the clean_logger extensions to ActiveSupport::BufferedLogger for performance reasons [David Heinemeier Hansson]. (You can change it back with config.logger = Logger.new("/path/to/log", level).)
+
+* Added a default 422.html page to be rendered when ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved, or ActionController::InvalidAuthenticityToken is raised *David Heinemeier Hansson*
+
+* Added --skip-fixture option to script/generate model #6862 *sandofsky*
+
+* Print Rails version when starting console #7440 *eyematz*
+
+* Fixed the placement of fixture files for nested models when generating through script/generate model #7547 *jkit*
+
+* Added TEMPLATE option to rake doc:app to set a custom output template #7737 *Jakob Skjerning*
+
+* Added VERBOSE option to rake db:migrate to turn off output #8204 *John Barnette*
+
+* Fixed that rake doc:app should use UTF-8 #8906 *farzy*
+
+* Fixes rake annotations to search erb and builder files as well #9150 *m.langenberg*
+
+* Removed web_service generator *Michael Koziarski*
+
+* Added the :all option to config.plugins that'll include the rest of the plugins not already explicitly named #9613 [Frederick Cheung]. Example:
+
+ # Loads :classic_pagination before all the other plugins
+ config.plugins = [ :classic_pagination, :all ]
+
+* Added symbols as a legal way of specifying plugins in config.plugins #9629 *tom*
+
+* Removed deprecated task names, like clear_logs, in favor of the new namespaced style *David Heinemeier Hansson*
+
+* Support multiple config.after_initialize blocks so plugins and apps can more easily cooperate. #9582 *Zach Dennis*
+
+* Added db:drop:all to drop all databases declared in config/database.yml *David Heinemeier Hansson*
+
+* Use attribute pairs instead of the migration name to create add and remove column migrations. Closes #9166 *Pratik Naik*
+
+ For example:
+
+ ruby script/generation migration AddSomeStuffToCustomers first_name:string last_name:string
+
+ or
+
+ ruby script/generation migration RemoveSomeStuffFromCustomers first_name:string last_name:string
+
+* Add ActiveResource to Rails::Info. Closes #8741 *Chris Kampmeier*
+
+* use Gem.find_name instead of search when freezing gems. Prevent false positives for other gems with rails in the name. Closes #8729 *wselman*
+
+* Automatically generate add/remove column commands in specially named migrations like AddLocationToEvent. Closes #9006 *Ryan Davis*
+
+* Default to plural table name in Rails Generator if ActiveRecord is not present. Closes #8963 *evan*
+
+* Added rake routes for listing all the defined routes in the system. #8795 *Josh Peek*
+
+* db:create creates the database for the current environment if it's on localhost. db:create:all creates local databases for all environments. #8783 *matt*
+
+* Generators: look for generators in all gems, not just those suffixed with _generator, in the gem's generators or rails_generators directory. Allow use of the rails_generators directory instead of the standard generators directory in plugins also. #8730 *Dr Nic, topfunky*
+
+* MySQL, PostgreSQL: database.yml defaults to utf-8. #8701 *matt*
+
+* Added db:version to get the current schema number *via Err The Blog*
+
+* Added --skip-migration option to scaffold and resource generators #8656 *Michael Glaesemann*
+
+* Fix that FCGIs would leave log files open when asked to shut down by USR2. #3028 *Sebastian Kanthak, Josh Peek*
+
+* Fixed that dispatcher preparation callbacks only run once in production mode. Mock Routes.reload so that dispatcher preparation callback tests run. *Rick Olson*
+
+* Fix syntax error in dispatcher than wrecked failsafe responses. #8625 *mtitorenko*
+
+* Scaffolded validation errors set the appropriate HTTP status for XML responses. #6946, #8622 *Manfred Stienstra, mmmultiworks*
+
+* Sexy migrations for the session_migration generator. #8561 *Vladislav*
+
+* Console reload! runs to_prepare callbacks also. #8393 *f.svehla*
+
+* Generated migrations include timestamps by default. #8501 *Shane Vitarana*
+
+* Drop Action Web Service from rails:freeze:edge. *Jeremy Kemper*
+
+* Add db:create, drop, reset, charset, and collation tasks. #8448 *matt*
+
+* Scaffold generator depends on model generator instead of duplicating it. #7222 *bscofield*
+
+* Scaffold generator tests. #8443 *pelle*
+
+* Generated scaffold functional tests use assert_difference. #8421 *Norbert Crombach*
+
+* Update to Prototype 1.5.1. *Sam Stephenson*
+
+* Update to script.aculo.us 1.7.1_beta3. *Thomas Fuchs*
+
+* Generators use *.html.erb view template naming. #8278 *Tim Pope*
+
+* Updated resource_scaffold and model generators to use short-hand style migrations *David Heinemeier Hansson*
+
+* Updated initializer to only load #{RAILS_ENV}.rb once. Added deprecation warning for config.breakpoint_server. *Nicholas Seckar*
+
+* Removed breakpointer and Binding.of_caller in favor of relying on ruby-debug by Kent Sibilev since the breakpointer has been broken since Ruby 1.8.4 and will not be coming back *David Heinemeier Hansson*
+
+ To use the new debugger, start your server with script/server --debugger and insert a call to 'debugger'
+ (instead of 'breakpoint') where you want to jump into the debugger.
+
+ BACKWARDS INCOMPATIBILITY NOTE: You must remove the default line 12 from config/environments/development.rb:
+
+ config.breakpoint_server = true
+
+ This configuration option is no longer available. Rails will fail to start in development mode as long as
+ that's still present.
+
+* Resource scaffolding returns the created entity.to_xml. *Jeremy Kemper*
+
+* Resource scaffolding responds to new.xml. #8185 *Eric Mill*
+
+* Include Active Resource in rails:freeze:edge rake task. *Thomas Fuchs*
+
+* Include Active Resource instead of Action Web Service [David Heinemeier Hansson] You can add AWS back with this in config/environment.rb:
+
+ config.load_paths += %W( #{RAILS_ROOT}/vendor/rails/actionwebservice/lib )
+
+ ...or just gem 'actionwebservice'
+
+* Give generate scaffold a more descriptive database message. Closes #7316 *Jeremy McAnally*
+
+* Canonicalize RAILS_ROOT by using File.expand_path on Windows, which doesn't have to worry about symlinks, and Pathname#realpath elsewhere, which respects symlinks in relative paths but is incompatible with Windows. #6755 *Jeremy Kemper, trevor*
+
+* Deprecation: remove components from controller paths. *Jeremy Kemper*
+
+* Add environment variable RAILS_DEFAULT_DATABASE, which allows the builtin default of 'mysql' to be overridden. *Nicholas Seckar*
+
+* Windows: include MinGW in RUBY_PLATFORM check. #2982 *okkez000@gmail.com, Kaspar Schiess*
+
+* Split out the basic plugin locator functionality into an abstract super class. Add a FileSystemLocator to do the job of checking the plugin_paths for plugins. Add plugin_locators configuration option which will iterate over the set of plugin locators and load each of the plugin loaders they return. Rename locater everywhere to locator. *Marcel Molina Jr.*
+
+* Split plugin location and loading out of the initializer and into a new Plugin namespace, which includes Plugin::Locater and Plugin::Loader. The loader class that is used can be customized using the config.plugin_loader option. Those monkey patching the plugin loading subsystem take note, the internals changing here will likely break your modifications. The good news is that it should be substantially easier to hook into the plugin locating and loading process now. *Marcel Molina Jr.*
+
+* Added assumption that all plugin creators desire to be sharing individuals and release their work under the MIT license *David Heinemeier Hansson*
+
+* Added source-annotations extractor tasks to rake [Jamis Buck]. This allows you to add FIXME, OPTIMIZE, and TODO comments to your source code that can then be extracted in concert with rake notes (shows all), rake notes:fixme, rake notes:optimize and rake notes:todo.
+
+* Added fixtures :all to test_helper.rb to assume that most people just want all their fixtures loaded all the time *David Heinemeier Hansson*
+
+* Added config/initializers where all ruby files within it are automatically loaded after the Rails configuration is done, so you don't have to litter the environment.rb file with a ton of mixed stuff *David Heinemeier Hansson*
+
+* For new apps, generate a random secret for the cookie-based session store. *Jeremy Kemper*
+
+* Stop swallowing errors during rake test *Michael Koziarski*
+
+* Update Rails Initializer to use ActionController::Base#view_paths *Rick Olson*
+
+* Fix gem deprecation warnings, which also means depending on RubyGems 0.9.0+ *Chad Fowler*
+
+* Plugins may be symlinked in vendor/plugins. #4245 *brandon, progrium@gmail.com*
+
+* Resource generator depends on the model generator rather than duplicating it. #7269 *bscofield*
+
+* Add/Update usage documentation for script/destroy, resource generator and scaffold_resource generator. Closes #7092, #7271, #7267. *bscofield*
+
+* Update to script.aculo.us 1.7.0. *Thomas Fuchs*
+
+* Update to Prototype 1.5.0. *Sam Stephenson*
+
+* Generator: use destination path for diff tempfiles. #7015 *alfeld*
+
+* Fixed that webrick would strip leading newlines and hang connection #4156 *psross*
+
+* Ensure plugins are in the Dependencies.load_once_paths collection by default. *Rick Olson*
+ If you really want your plugins to reload, add this to the very top of init.rb:
+
+ Dependencies.load_once_paths.delete(lib_path)
+
+* Allow config.to_prepare to work, make the dispatcher safe to 're require'. *Michael Koziarski, Nicholas Seckar*
+
+* Fix scaffold_resource generator so it respects the --pretend argument when creating the routes file. Closes #6852 *fearoffish*
+
+* Fix Webrick Daemon dispatching bug regarding a bad current working directory. Closes #4899 *Rick Olson*
+
+* Make config.plugins affect the load path and the dependencies system. Allows you to control plugin loading order, and keep disabled plugins off the load path. *James Adam*
+
+* Don't generate a components directory in new Rails apps. *Jeremy Kemper*
+
+* Fixed script/process/spawner to work properly with Mongrel including in -r (daemonize mode) *David Heinemeier Hansson*
+
+* Added one-letter aliases for the three default environments to script/console, so script/console p will load the production environment (t for test, d for development) *David Heinemeier Hansson*
+
+* Fixed that script/server running against Mongrel should tail the proper log regardless of the environment *David Heinemeier Hansson*
+
+* Update initializer to load Rails::VERSION as soon as possible. Closes #6698. *Nicholas Seckar*
+
+* Added ActiveRecord::Base.clear_active_connections! in development mode so the database connection is not carried over from request to request. Some databases won't reread the schema if that doesn't happen (I'm looking at you SQLite), so you have to restart the server after each migration (= no fun) *David Heinemeier Hansson*
+
+* Made RAILS_GEM_VERSION work for beta gems too, so specifying 1.1.6 will give you 1.1.6.4520 if available *David Heinemeier Hansson*
+
+* Update to Prototype and script.aculo.us [5579]. *Thomas Fuchs*
+
+* Made script/server work with -e and -d when using Mongrel *David Heinemeier Hansson*
+
+* Update to Prototype 1.5.0_rc2 [5550] which makes it work in Opera again *Thomas Fuchs*
+
+* Make sure that exceptions which are thrown outside of the user code try their best to be handeled in ApplicationController#rescue_action *Tobias Lütke*
+
+* Rails::VERSION::STRING should always be available without having to require 'rails/version'. #6244 *fearoffish*
+
+* Update to Prototype 1.5.0_rc2. *Sam Stephenson*
+
+* Add grep-based fallback to reaper, to work in pidless setups *Jamis Buck*
+
+* Only wrap request processing with our USR1 signal handler so FastCGI can trap it and raise an exception while waiting for connections. Idle processes exit immediately rather than waiting for another request; active processes gracefully exit when the request is finished. *Jeremy Kemper*
+
+* Alter prior change to use require_dependency instead of require_or_load. Causes ApplicationController to be reloaded again. Closes #6587. *Nicholas Seckar*
+
+* Rake: use absolute paths to load lib and vendor tasks so they may be run outside of RAILS_ROOT. #6584 *jchris*
+
+* Remove temporary crutch to help ApplicationController be unloaded. Closes #6496. *Nicholas Seckar*
+
+* scaffold_resource generator uses _path named routes and head instead of render :nothing => true. #6545 *Josh Susser*
+
+* Generator can show diff on file collision to help you decide whether to skip or overwrite. #6364 *jeffw, Jeremy Kemper*
+
+* Generated directories are recursively svn added, like mkdir -p. #6416 *NeilW*
+
+* resource and scaffold_resource generators add a restful route to config/routes.rb *Jeremy Kemper*
+
+* Revert environment changes for autoload_paths. *Michael Koziarski*
+
+* Update to latest Prototype, which doesn't serialize disabled form elements, adds clone() to arrays, empty/non-string Element.update() and adds a fixes excessive error reporting in WebKit beta versions *Thomas Fuchs*
+
+* Clean up the output of rake stats, de-emphasise components and apis, and remove the indents for tests *Michael Koziarski*
+
+* Added option to script/process/spawner of specifying the binding address #5133 *Dee Zsombor*
+
+* Update environment.rb comments to include config.autoload_paths. Closes #6478 *caio*
+
+* Update scaffold to use new form_tag block functionality. Closes #6480. *Bob Silva*
+
+* Plugin generator: check for class collisions. #4833 *vinbarnes@gmail.com*
+
+* Mailer generator: handle mailers in modules, set mime_version in unit test. *Jeremy Kemper*
+
+* Set $KCODE to 'u' by default to enable the multibyte safe String#chars proxy. *Michael Koziarski*
+
+* Added config.plugins to control which plugins are loaded #6269 [Stefan Kaes]. By default, everything in vendor/plugins will be loaded, but if you specify config.plugins, only those will be loaded. Example:
+
+ config.plugins = %w[ routing_navigator simply_helpful ]
+
+* Clean up html on included error pages. *Tim Lucas*
+
+* Fixed default 404.html and 500.htmls to remove extreme ugliness and include human language *David Heinemeier Hansson*
+
+* Update to latest Prototype and script.aculo.us trunk versions *Thomas Fuchs*
+
+* PostgreSQL: db:test:purge closes open database connections first. #6236 *alex*
+
+* Fixed test:uncommitted on Windows (backslash issue) #4999 *paul@paulbutcher.com*
+
+* Fixed migration creation to work with namespaced models, so script/generate model Gallery::Image will use create_table :gallery_images #6327 *Bob Silva*
+
+* Fixed rename_table on SQLite tables with indexes defined #5942 *brandon@opensoul.org*
+
+* Added default timeout setting of 5 seconds to SQLite3 database.yml configurations *David Heinemeier Hansson*
+
+* Added generated attribute options to script/generate model, like the one found in scaffold_resource and resource [David Heinemeier Hansson]. Examples:
+
+ ./script/generate model post title:string created_on:date body:text published:boolean
+
+* Added script/generate resource which works just like scaffold_resource, but creates empty placeholders instead of predefined *David Heinemeier Hansson*
+
+* script/runner can run files, pass on arguments, and be used as a shebang. #6286 *Tuxie, dlpond*
+ #!/usr/bin/env /path/to/my/app/script/runner
+ # Example: just start using your models as if you are in script/console
+ Product.find(:all).each { |product| product.check_inventory }
+
+* Look for rake tasks in plugin subdirs. #6259 *obrie*
+
+* Added map.connect ':controller/:action/:id.:format' as a default route to config/routes.rb *David Heinemeier Hansson*
+
+* Updated prototype.js to 1.5.0_rc1 with latest fixes. *Rick Olson*
+
+ - XPATH support
+ - Make Form.getElements() return elements in the correct order
+ - fix broken Form.serialize return
+
+* session_migration generator adds an index on updated_at. #6207 *grg*
+
+* script/server creates the tmp/pids directory. #6204 *jonathan*
+
+* Fix script/console --sandbox for internal transactions changes. #5738 *Chris McGrath, charles.gerungan@gmail.com*
+
+* Remove the uncanny default of adding all app/models/*/ directories to the load path. This change will break application which expect the current behavior. As
+ documented in initializer.rb, the workaround is:
+
+ config.autoload_paths += Dir*RAILS_ROOT + '/app/models/*/'*
+
+ References #6031. *Nicholas Seckar*
+
+* Update to script.aculo.us 1.6.3 *Thomas Fuchs*
+
+* Update to Prototype 1.5.0_rc1 *sam*
+
+* Formally Deprecate the old rake tasks. *Michael Koziarski*
+
+* Thoroughly test the FCGI dispatcher. #5970 *Kevin Clark*
+
+* Remove Dir.chdir in the Webrick DispatchServlet#initialize method. Fix bad path errors when trying to load config/routes.rb. *Rick Olson*
+
+* Tighten rescue clauses. #5985 *james@grayproductions.net*
+
+* Cleaning up tests. *Kevin Clark, Jeremy Kemper*
+
+* Add Dependencies.load_once_paths. *Nicholas Seckar*
+
+* Updated to script.aculo.us 1.6.2 *Thomas Fuchs*
+
+* Assign Routing.controller_paths; fix script/about and rails info controller. *Nicholas Seckar*
+
+* Don't warn dispatcher of Reloadable deprecations. *Nicholas Seckar*
+
+* Rearrange application resetting and preparation, fix bug with leaking subclasses hash in ActiveRecord::Base *Rick Olson*
+
+ ActiveRecord::Base.reset_subclasses is called before Dependencies are cleared and classes removed.
+ ActiveRecord::Base.instantiate_observers is called during a Dispatcher preparation callback.
+
+* Add missing mock directories from the autoload_paths configuration. *Rick Olson*
+
+* Nested controller scaffolding also nests the generated layout. *iain d broadfoot*
+
+* Add "require 'dispatcher'" to webrick server in the continuing quest to squash webrick weirdness. *Nicholas Seckar*
+
+* Add autoload_paths support to Initializer. *Nicholas Seckar*
+
+* Fix Dispatcher.reset_application! so that AR subclasses are removed and Observers re-initialized *after* Reloadable classes are removed. Closes #5743. *Rick Olson*
+
+* Clarify usage of script/plugin source. Closes #5344. *James Adam*
+
+* Add Dispatcher.to_prepare and config.to_prepare to provide a pre-request hook. *Nicholas Seckar*
+
+* Tweak the Rails load order so observers are loaded after plugins, and reloaded in development mode. Closed #5279. *Rick Olson*
+
+* Added that you can change the web server port in config/lighttpd.conf from script/server --port/-p #5465 *mats@imediatec.co.uk*
+
+* script/performance/profiler compatibility with the new ruby-prof, including an option to choose the results printer. #5679 *Shugo Maeda*
+
+* Fixed the failsafe response so it uses either the current recognized controller or ApplicationController. *Rick Olson*
+
+* Make sure script/reaper only reaps dispatcher pids by default, and not the spawner's pid. *Jamis Buck*
+
+* Fix script/plugin about so it uses about.yml and not meta.yml. *James Adam*
+
+* Dispatcher processes rescued actions with the same controller that processed the request. #4625 *sd@notso.net*
+
+* rails -d frontbase to create a new project with a frontbase database.yml. #4945 *mlaster@metavillage.com*
+
+* Ensure the logger is initialized. #5629 *mike@clarkware.com*
+
+* Added Mongrel-spawning capabilities to script/process/spawner. Mongrel will be the default choice if installed, otherwise FCGI is tried [David Heinemeier Hansson]. Examples:
+
+ spawner # starts instances on 8000, 8001, and 8002 using Mongrel if available
+ spawner fcgi # starts instances on 8000, 8001, and 8002 using FCGI
+ spawner mongrel -i 5 # starts instances on 8000, 8001, 8002, 8003, and 8004 using Mongrel
+ spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to 9109 using Mongrel if available
+ spawner -p 9100 -r 5 # starts 3 instances counting from 9100 to 9102 and attempts start them every 5 seconds
+
+ Also note that script/process/reaper is Mongrel capable. So the combination of spawner and reaper is a built-in alternative to something like mongrel_cluster.
+
+* Update scaffolding functional tests to use :id => people(:first) instead of :id => 1. #5612 *evan@protest.net*
+
+* db:test:clone should remove existing tables before reloading the schema. #5607 *sveit@tradeharbor.com*
+
+* Fixed migration generation for class names like ACLController #5197 *brad@madriska.com*
+
+* Added show_source_list and show_call_stack to breakpoints to make it easier to get context #5476 [takiuchi@drecom.co.jp]. Examples:
+
+ irb(#<TopController:0x40822a68>):002:0> show_source_list
+ 0001 class TopController < ApplicationController
+ 0002 def show
+ 0003-> breakpoint
+ 0004 end
+ 0005
+ 0006 def index
+ 0007 end
+ 0008
+ => "/path/to/rails/root/app/controllers/top_controller.rb"
+
+ irb(#<TopController:0x40822a68>):004:0> show_call_stack 3
+ vendor/rails/railties/lib/breakpoint.rb:536:in `breakpoint'
+ vendor/rails/railties/lib/breakpoint.rb:536:in `breakpoint'
+ app/controllers/top_controller.rb:3:in `show'
+ => "/path/to/rails/root/app/controllers/top_controller.rb:3"
+
+* Generate scaffold layout in subdirectory appropriate to its module nesting. #5511 *nils@alumni.rice.edu*
+
+* Mongrel: script/server tails the rails log like it does with lighttpd. Prefer mongrel over lighttpd. #5541 *mike@clarkware.com*
+
+* Don't assume Active Record is available. #5497 *bob@sporkmonger.com*
+
+* Mongrel: script/server works on Win32. #5499 *jeremydurham@gmail.com*
+
+* Remove opts.on { |options[:option_name] } style hash assignment. Closes #4440. *Nick Sieger*
+
+* Mongrel support for script/server. #5475 *jeremydurham@gmail.com*
+
+* Fix script/plugin so it doesn't barf on invalid URLs *Rick Olson*
+
+* Fix plugin install bug at dir with space. (closes #5359) *Yoshimasa NIWA*
+
+* Fix bug with 'script/plugin install' so it reports unknown plugin names correctly. *Rick Olson*
+
+* Added uninstall.rb hook to plugin handling, such that plugins have a way of removing assets and other artifacts on removal #5003 *takiuchi@drecom.co.jp*
+
+* Create temporary dirs relative to RAILS_ROOT when running script/server #5014 *elliot@townx.org*
+
+* Minor tweak to dispatcher to use recognize instead of recognize!, as per the new routes. *Jamis Buck*
+
+* Make "script/plugin install" work with svn+ssh URLs. *Sam Stephenson*
+
+* Added lib/ to the directories that will get application docs generated *David Heinemeier Hansson*
+
+* Add observer generator. Closes #5167. *François Beausoleil*
+
+* Session migration generator obeys pluralize_table_names. #5145 *James Adam*
+
+* rake test:recent understands subdirectories. #2925 *jerrett@bravenet.com*
+
+* The app generator detects the XAMPP package's MySQL socket location. #3832 *elliot@townx.org*
+
+* The app generator sets a session key in application.rb so apps running on the same host may distinguish their cookies. #2967 *rcoder, rails-bug@owl.me.uk*
+
+* Distinguish the spawners for different processes *David Heinemeier Hansson*
+
+* Added -n/--process to script/process/spawner name the process pid (default is dispatch) *David Heinemeier Hansson*
+
+* Namespaced OrderedHash so the Rails implementation does not clash with any others. (fixes #4911) *Julian Tarkhanov*
+
+* Replace Ruby's deprecated append_features in favor of included. *Marcel Molina Jr.*
+
+* Added script/process/inspector to do simple process status information on Rails dispatchers keeping pid files in tmp/pids *David Heinemeier Hansson*
+
+* Added pid file usage to script/process/spawner and script/process/reaper along with a directive in default config/lighttpd.conf file to record the pid. They will all save their pid file in tmp/pids *David Heinemeier Hansson*
+
+
+## 1.2.3 (March 12th, 2007) ##
+
+* Ruby 1.8.6 compatibility
+
+* Windows: include MinGW in RUBY_PLATFORM check. #2982 *okkez000@gmail.com, Kaspar Schiess*
+
+* Stop swallowing errors during rake test *Michael Koziarski*
+
+
+## 1.2.2 (February 5th, 2007) ##
+
+* Fix gem deprecation warnings, which also means depending on RubyGems 0.9.0+ *Chad Fowler*
+
+* Require the dispatcher for Rails::Configuration#to_prepare. *Rick Olson*
+
+
+## 1.2.1 (January 16th, 2007) ##
+
+* Updated to Active Record 1.15.1, Action Pack 1.13.1, Action Mailer 1.3.1, Action Web Service 1.2.1
+
+
+## 1.2.0 (January 16th, 2007) ##
+
+* Update to Prototype 1.5.0. *Sam Stephenson*
+
+* Generator: use destination path for diff tempfiles. #7015 *alfeld*
+
+* Fixed that webrick would strip leading newlines and hang connection #4156 *psross*
+
+* Ensure plugins are in the Dependencies.load_once_paths collection by default. *Rick Olson*
+ If you really want your plugins to reload, add this to the very top of init.rb:
+
+ Dependencies.load_once_paths.delete(lib_path)
+
+* Fix scaffold_resource generator so it respects the --pretend argument when creating the routes file. Closes #6852 *fearoffish*
+
+* Fix Webrick Daemon dispatching bug regarding a bad current working directory. Closes #4899 *Rick Olson*
+
+* Make config.plugins affect the load path and the dependencies system. Allows you to control plugin loading order, and keep disabled plugins off the load path. *James Adam*
+
+* Don't generate a components directory in new Rails apps. *Jeremy Kemper*
+
+* Fixed script/process/spawner to work properly with Mongrel including in -r (daemonize mode) *David Heinemeier Hansson*
+
+* Deprecated the name route "root" as it'll be used as a shortcut for map.connect '' in Rails 2.0 *David Heinemeier Hansson*
+
+* Fixed that script/server running against Mongrel should tail the proper log regardless of the environment *David Heinemeier Hansson*
+
+* Update initializer to load Rails::VERSION as soon as possible. Closes #6698. *Nicholas Seckar*
+
+* Added ActiveRecord::Base.clear_active_connections! in development mode so the database connection is not carried over from request to request. Some databases won't reread the schema if that doesn't happen (I'm looking at you SQLite), so you have to restart the server after each migration (= no fun) *David Heinemeier Hansson*
+
+* Made RAILS_GEM_VERSION work for beta gems too, so specifying 1.1.6 will give you 1.1.6.4520 if available *David Heinemeier Hansson*
+
+* Update to Prototype and script.aculo.us [5579]. *Sam Stephenson, Thomas Fuchs*
+
+* Made script/server work with -e and -d when using Mongrel *David Heinemeier Hansson*
+
+* Make sure that exceptions which are thrown outside of the user code try their best to be handeled in ApplicationController#rescue_action *Tobias Lütke*
+
+* Rails::VERSION::STRING should always be available without having to require 'rails/version'. #6244 *fearoffish*
+
+* Add grep-based fallback to reaper, to work in pidless setups *Jamis Buck*
+
+* Only wrap request processing with our USR1 signal handler so FastCGI can trap it and raise an exception while waiting for connections. Idle processes exit immediately rather than waiting for another request; active processes gracefully exit when the request is finished. *Jeremy Kemper*
+
+* Alter prior change to use require_dependency instead of require_or_load. Causes ApplicationController to be reloaded again. Closes #6587. *Nicholas Seckar*
+
+* Rake: use absolute paths to load lib and vendor tasks so they may be run outside of RAILS_ROOT. #6584 *jchris*
+
+* scaffold_resource generator uses _path named routes and head instead of render :nothing => true. #6545 *Josh Susser*
+
+* Generator can show diff on file collision to help you decide whether to skip or overwrite. #6364 *jeffw, Jeremy Kemper*
+
+* Generated directories are recursively svn added, like mkdir -p. #6416 *NeilW*
+
+* resource and scaffold_resource generators add a restful route to config/routes.rb *Jeremy Kemper*
+
+* Revert environment changes for autoload_paths. *Michael Koziarski*
+
+* Clean up the output of rake stats, de-emphasise components and apis, and remove the indents for tests *Michael Koziarski*
+
+* Added option to script/process/spawner of specifying the binding address #5133 *Dee Zsombor*
+
+* Update environment.rb comments to include config.autoload_paths. Closes #6478 *caio*
+
+* Update scaffold to use new form_tag block functionality. Closes #6480. *Bob Silva*
+
+* Plugin generator: check for class collisions. #4833 *vinbarnes@gmail.com*
+
+* Mailer generator: handle mailers in modules, set mime_version in unit test. *Jeremy Kemper*
+
+* Set $KCODE to 'u' by default to enable the multibyte safe String#chars proxy. *Michael Koziarski*
+
+* Added config.plugins to control which plugins are loaded #6269 [Stefan Kaes]. By default, everything in vendor/plugins will be loaded, but if you specify config.plugins, only those will be loaded. Example:
+
+ config.plugins = %w[ routing_navigator simply_helpful ]
+
+* Clean up html on included error pages. *Tim Lucas*
+
+* Fixed default 404.html and 500.htmls to remove extreme ugliness and include human language *David Heinemeier Hansson*
+
+* Update to latest Prototype and script.aculo.us trunk versions *Thomas Fuchs*
+
+* PostgreSQL: db:test:purge closes open database connections first. #6236 *alex*
+
+* Fixed test:uncommitted on Windows (backslash issue) #4999 *paul@paulbutcher.com*
+
+* Fixed migration creation to work with namespaced models, so script/generate model Gallery::Image will use create_table :gallery_images #6327 *Bob Silva*
+
+* Fixed rename_table on SQLite tables with indexes defined #5942 *brandon@opensoul.org*
+
+* Added default timeout setting of 5 seconds to SQLite3 database.yml configurations *David Heinemeier Hansson*
+
+* Added generated attribute options to script/generate model, like the one found in scaffold_resource and resource [David Heinemeier Hansson]. Examples:
+
+ ./script/generate model post title:string created_on:date body:text published:boolean
+
+* Added script/generate resource which works just like scaffold_resource, but creates empty placeholders instead of predefined *David Heinemeier Hansson*
+
+* script/runner can run files, pass on arguments, and be used as a shebang. #6286 *Tuxie, dlpond*
+ #!/usr/bin/env /path/to/my/app/script/runner
+ # Example: just start using your models as if you are in script/console
+ Product.find(:all).each { |product| product.check_inventory }
+
+* Look for rake tasks in plugin subdirs. #6259 *obrie*
+
+* Added map.connect ':controller/:action/:id.:format' as a default route to config/routes.rb *David Heinemeier Hansson*
+
+* session_migration generator adds an index on updated_at. #6207 *grg*
+
+* script/server creates the tmp/pids directory. #6204 *jonathan*
+
+* Fix script/console --sandbox for internal transactions changes. #5738 *Chris McGrath, charles.gerungan@gmail.com*
+
+* Remove the uncanny default of adding all app/models/*/ directories to the load path. This change will break application which expect the current behavior. As
+ documented in initializer.rb, the workaround is:
+
+ config.autoload_paths += Dir*RAILS_ROOT + '/app/models/*/'*
+
+ References #6031. *Nicholas Seckar*
+
+* Update to script.aculo.us 1.6.3 *Thomas Fuchs*
+
+* Formally Deprecate the old rake tasks. *Michael Koziarski*
+
+* Thoroughly test the FCGI dispatcher. #5970 *Kevin Clark*
+
+* Remove Dir.chdir in the Webrick DispatchServlet#initialize method. Fix bad path errors when trying to load config/routes.rb. *Rick Olson*
+
+* Tighten rescue clauses. #5985 *james@grayproductions.net*
+
+* Cleaning up tests. *Kevin Clark, Jeremy Kemper*
+
+* Add Dependencies.load_once_paths. *Nicholas Seckar*
+
+* Assign Routing.controller_paths; fix script/about and rails info controller. *Nicholas Seckar*
+
+* Don't warn dispatcher of Reloadable deprecations. *Nicholas Seckar*
+
+* Rearrange application resetting and preparation, fix bug with leaking subclasses hash in ActiveRecord::Base *Rick Olson*
+
+ ActiveRecord::Base.reset_subclasses is called before Dependencies are cleared and classes removed.
+ ActiveRecord::Base.instantiate_observers is called during a Dispatcher preparation callback.
+
+* Add missing mock directories from the autoload_paths configuration. *Rick Olson*
+
+* Nested controller scaffolding also nests the generated layout. *iain d broadfoot*
+
+* Add "require 'dispatcher'" to webrick server in the continuing quest to squash webrick weirdness. *Nicholas Seckar*
+
+* Add autoload_paths support to Initializer. *Nicholas Seckar*
+
+* Fix Dispatcher.reset_application! so that AR subclasses are removed and Observers re-initialized *after* Reloadable classes are removed. Closes #5743. *Rick Olson*
+
+* Clarify usage of script/plugin source. Closes #5344. *James Adam*
+
+* Add Dispatcher.to_prepare and config.to_prepare to provide a pre-request hook. *Nicholas Seckar*
+
+* Tweak the Rails load order so observers are loaded after plugins, and reloaded in development mode. Closed #5279. *Rick Olson*
+
+* Added that you can change the web server port in config/lighttpd.conf from script/server --port/-p #5465 *mats@imediatec.co.uk*
+
+* script/performance/profiler compatibility with the new ruby-prof, including an option to choose the results printer. #5679 *Shugo Maeda*
+
+* Fixed the failsafe response so it uses either the current recognized controller or ApplicationController. *Rick Olson*
+
+* Make sure script/reaper only reaps dispatcher pids by default, and not the spawner's pid. *Jamis Buck*
+
+* Fix script/plugin about so it uses about.yml and not meta.yml. *James Adam*
+
+* Dispatcher processes rescued actions with the same controller that processed the request. #4625 *sd@notso.net*
+
+* rails -d frontbase to create a new project with a frontbase database.yml. #4945 *mlaster@metavillage.com*
+
+* Ensure the logger is initialized. #5629 *mike@clarkware.com*
+
+* Added Mongrel-spawning capabilities to script/process/spawner. Mongrel will be the default choice if installed, otherwise FCGI is tried [David Heinemeier Hansson]. Examples:
+
+ spawner # starts instances on 8000, 8001, and 8002 using Mongrel if available
+ spawner fcgi # starts instances on 8000, 8001, and 8002 using FCGI
+ spawner mongrel -i 5 # starts instances on 8000, 8001, 8002, 8003, and 8004 using Mongrel
+ spawner -p 9100 -i 10 # starts 10 instances counting from 9100 to 9109 using Mongrel if available
+ spawner -p 9100 -r 5 # starts 3 instances counting from 9100 to 9102 and attempts start them every 5 seconds
+
+ Also note that script/process/reaper is Mongrel capable. So the combination of spawner and reaper is a built-in alternative to something like mongrel_cluster.
+
+* Update scaffolding functional tests to use :id => people(:first) instead of :id => 1. #5612 *evan@protest.net*
+
+* db:test:clone should remove existing tables before reloading the schema. #5607 *sveit@tradeharbor.com*
+
+* Fixed migration generation for class names like ACLController #5197 *brad@madriska.com*
+
+* Added show_source_list and show_call_stack to breakpoints to make it easier to get context #5476 [takiuchi@drecom.co.jp]. Examples:
+
+ irb(#<TopController:0x40822a68>):002:0> show_source_list
+ 0001 class TopController < ApplicationController
+ 0002 def show
+ 0003-> breakpoint
+ 0004 end
+ 0005
+ 0006 def index
+ 0007 end
+ 0008
+ => "/path/to/rails/root/app/controllers/top_controller.rb"
+
+ irb(#<TopController:0x40822a68>):004:0> show_call_stack 3
+ vendor/rails/railties/lib/breakpoint.rb:536:in `breakpoint'
+ vendor/rails/railties/lib/breakpoint.rb:536:in `breakpoint'
+ app/controllers/top_controller.rb:3:in `show'
+ => "/path/to/rails/root/app/controllers/top_controller.rb:3"
+
+* Generate scaffold layout in subdirectory appropriate to its module nesting. #5511 *nils@alumni.rice.edu*
+
+* Mongrel: script/server tails the rails log like it does with lighttpd. Prefer mongrel over lighttpd. #5541 *mike@clarkware.com*
+
+* Don't assume Active Record is available. #5497 *bob@sporkmonger.com*
+
+* Mongrel: script/server works on Win32. #5499 *jeremydurham@gmail.com*
+
+* Remove opts.on { |options[:option_name] } style hash assignment. Closes #4440. *Nick Sieger*
+
+* Mongrel support for script/server. #5475 *jeremydurham@gmail.com*
+
+* Fix script/plugin so it doesn't barf on invalid URLs *Rick Olson*
+
+* Fix plugin install bug at dir with space. (closes #5359) *Yoshimasa NIWA*
+
+* Fix bug with 'script/plugin install' so it reports unknown plugin names correctly. *Rick Olson*
+
+* Added uninstall.rb hook to plugin handling, such that plugins have a way of removing assets and other artifacts on removal #5003 *takiuchi@drecom.co.jp*
+
+* Create temporary dirs relative to RAILS_ROOT when running script/server #5014 *elliot@townx.org*
+
+* Minor tweak to dispatcher to use recognize instead of recognize!, as per the new routes. *Jamis Buck*
+
+* Make "script/plugin install" work with svn+ssh URLs. *Sam Stephenson*
+
+* Added lib/ to the directories that will get application docs generated *David Heinemeier Hansson*
+
+* Add observer generator. Closes #5167. *François Beausoleil*
+
+* Session migration generator obeys pluralize_table_names. #5145 *James Adam*
+
+* rake test:recent understands subdirectories. #2925 *jerrett@bravenet.com*
+
+* The app generator detects the XAMPP package's MySQL socket location. #3832 *elliot@townx.org*
+
+* The app generator sets a session key in application.rb so apps running on the same host may distinguish their cookies. #2967 *rcoder, rails-bug@owl.me.uk*
+
+* Distinguish the spawners for different processes *David Heinemeier Hansson*
+
+* Added -n/--process to script/process/spawner name the process pid (default is dispatch) *David Heinemeier Hansson*
+
+* Namespaced OrderedHash so the Rails implementation does not clash with any others. (fixes #4911) *Julian Tarkhanov*
+
+* Replace Ruby's deprecated append_features in favor of included. *Marcel Molina Jr.*
+
+* Added script/process/inspector to do simple process status information on Rails dispatchers keeping pid files in tmp/pids *David Heinemeier Hansson*
+
+* Added pid file usage to script/process/spawner and script/process/reaper along with a directive in default config/lighttpd.conf file to record the pid. They will all save their pid file in tmp/pids *David Heinemeier Hansson*
+
+
+## 1.1.6 (August 10th, 2006) ##
+
+* Additional security patch
+
+
+## 1.1.5 (August 8th, 2006) ##
+
+* Mention in docs that config.frameworks doesn't work when getting Rails via Gems. #4857 *Alisdair McDiarmid*
+
+* Change the scaffolding layout to use yield rather than @content_for_layout. *Marcel Molina Jr.*
+
+* Includes critical security patch
+
+
+## 1.1.4 (June 29th, 2006) ##
+
+* Remove use of opts.on { |options[:name] } style hash assignment. References #4440. *headius@headius.com*
+
+* Updated to Action Pack 1.12.3, ActionWebService 1.1.4, ActionMailer 1.2.3
+
+
+## 1.1.3 (June 27th, 2006) ##
+
+* Updated to Active Record 1.14.3, Action Pack 1.12.2, ActionWebService 1.1.3, ActionMailer 1.2.2
+
+
+## 1.1.2 (April 9th, 2006) ##
+
+* Mention in docs that config.frameworks doesn't work when getting Rails via Gems. Closes #4857. *Alisdair McDiarmid*
+
+* Change the scaffolding layout to use yield rather than @content_for_layout. *Marcel Molina Jr.*
+
+* Added rake rails:update:configs to update config/boot.rb from the latest (also included in rake rails:update) *David Heinemeier Hansson*
+
+* Fixed that boot.rb would set RAILS_GEM_VERSION twice, not respect an uncommented RAILS_GEM_VERSION line, and not use require_gem *David Heinemeier Hansson*
+
+
+## 1.1.1 (April 6th, 2006) ##
+
+* Enhances plugin#discover allowing it to discover svn:// like URIs (closes #4565) *ruben.nine@gmail.com*
+
+* Update to Prototype 1.5.0_rc0 *Sam Stephenson*
+
+* Fixed that the -r/--ruby path option of the rails command was not being respected #4549 *ryan.raaum@gmail.com*
+
+* Added that Dispatcher exceptions should not be shown to the user unless a default log has not been configured. Instead show public/500.html *David Heinemeier Hansson*
+
+* Fixed that rake clone_structure_to_test should quit on pgsql if the dump is unsuccesful #4585 *augustz@augustz.com*
+
+* Fixed that rails --version should have the return code of 0 (success) #4560 *blair@orcaware.com*
+
+* Install alias so Rails::InfoController is accessible at /rails_info. Closes #4546. *Nicholas Seckar*
+
+* Fixed that spawner should daemonize if running in repeat mode *David Heinemeier Hansson*
+
+* Added TAG option for rake rails:freeze:edge, so you can say rake rails:freeze:edge TAG=rel_1-1-0 to lock to the 1.1.0 release *David Heinemeier Hansson*
+
+* Applied Prototype $() performance patches (#4465, #4477) and updated script.aculo.us *Sam Stephenson, Thomas Fuchs*
+
+* Use --simple-prompt instead of --prompt-mode simple for console compatibility with Windows/Ruby 1.8.2 #4532 *starr@starrnhorne.com*
+
+* Make Rails::VERSION implicitly loadable #4491. *Nicholas Seckar*
+
+* Fixed rake rails:freeze:gems #4518 *benji@silverinsanity.com*
+
+* Added -f/--freeze option to rails command for freezing the application to the Rails version it was generated with *David Heinemeier Hansson*
+
+* Added gem binding of apps generated through the rails command to the gems of they were generated with *Nicholas Seckar*
+
+* Added expiration settings for JavaScript, CSS, HTML, and images to default lighttpd.conf *David Heinemeier Hansson*
+
+* Added gzip compression for JavaScript, CSS, and HTML to default lighttpd.conf *David Heinemeier Hansson*
+
+* Avoid passing escapeHTML non-string in Rails' info controller *Nicholas Seckar*
+
+
+## 1.1.0 (March 27th, 2006) ##
+
+* Allow db:fixtures:load to load a subset of the applications fixtures. *Chad Fowler*
+
+ ex.
+
+ rake db:fixtures:load FIXTURES=customers,plans
+
+* Update to Prototype 1.5.0_pre1 *Sam Stephenson*
+
+* Update to script.aculo.us 1.6 *Thomas Fuchs*
+
+* Add an integration_test generator *Jamis Buck*
+
+* Make all ActionView helpers available in the console from the helper method for debugging purposes. n.b.: Only an 80% solution. Some stuff won't work, most will. *Marcel Molina Jr.*
+
+ ex.
+
+ >> puts helper.options_for_select([%w(a 1), %w(b 2), %w(c 3)])
+ <option value="1">a</option>
+ <option value="2">b</option>
+ <option value="3">c</option>
+ => nil
+
+* Replaced old session rake tasks with db:sessions:create to generate a migration, and db:sessions:clear to remove sessions. *Rick Olson*
+
+* Reject Ruby 1.8.3 when loading Rails; extract version checking code. *Chad Fowler*
+
+* Remove explicit loading of RailsInfo and RailsInfoController. *Nicholas Seckar*
+
+* Move RailsInfo and RailsInfoController to Rails::Info and Rails::InfoController. *Nicholas Seckar*
+
+* Extend load path with Railties' builtin directory to make adding support code easy. *Nicholas Seckar*
+
+* Fix the rails_info controller by explicitly loading it, and marking it as not reloadable. *Nicholas Seckar*
+
+* Fixed rails:freeze:gems for Windows #3274 *paul@paulbutcher.com*
+
+* Added 'port open?' check to the spawner when running in repeat mode so we don't needlessly boot the dispatcher if the port is already in use anyway #4089 *guy.naor@famundo.com*
+
+* Add verification to generated scaffolds, don't allow get for unsafe actions *Michael Koziarski*
+
+* Don't replace application.js in public/javascripts if it already exists *Cody Fauser*
+
+* Change test:uncommitted to delay execution of `svn status` by using internal Rake API's. *Nicholas Seckar*
+
+* Use require_library_or_gem to load rake in commands/server.rb. Closes #4205. *rob.rasmussen@gmail.com*
+
+* Use the Rake API instead of shelling out to create the tmp directory in commands/server.rb. *Chad Fowler*
+
+* Added a backtrace to the evil WSOD (White Screen of Death). Closes #4073. TODO: Clearer exceptions *Rick Olson*
+
+* Added tracking of database and framework versions in script/about #4088 *charles.gerungan@gmail.com/Rick Olson*
+
+* Added public/javascripts/application.js as a sample since it'll automatically be included in javascript_include_tag :defaults *David Heinemeier Hansson*
+
+* Added socket cleanup for lighttpd, both before and after *David Heinemeier Hansson*
+
+* Added automatic creation of tmp/ when running script/server *David Heinemeier Hansson*
+
+* Added silence_stream that'll work on both STDERR or STDOUT or any other stream and deprecated silence_stderr in the process *David Heinemeier Hansson*
+
+* Added reload! method to script/console to reload all models and others that include Reloadable without quitting the console #4056 *esad@esse.at*
+
+* Added that rake rails:freeze:edge will now just export all the contents of the frameworks instead of just lib, so stuff like rails:update:scripts, rails:update:javascripts, and script/server on lighttpd still just works #4047 *David Heinemeier Hansson*
+
+* Added fix for upload problems with lighttpd from Safari/IE to config/lighttpd.conf #3999 *Thijs van der Vossen*
+
+* Added test:uncommitted to test changes since last checkin to Subversion #4035 *technomancy@gmail.com*
+
+* Help script/about print the correct svn revision when in a non-English locale. #4026 *babie7a0@ybb.ne.jp*
+
+* Add 'app' accessor to script/console as an instance of Integration::Session *Jamis Buck*
+
+* Generator::Base#usage takes an optional message argument which defaults to Generator::Base#usage_message. *Jeremy Kemper*
+
+* Remove the extraneous AR::Base.threaded_connections setting from the webrick server. *Jeremy Kemper*
+
+* Add integration test support to app generation and testing *Jamis Buck*
+
+* Added namespaces to all tasks, so for example load_fixtures is now db:fixtures:load. All the old task names are still valid, they just point to the new namespaced names. "rake -T" will only show the namespaced ones, though *David Heinemeier Hansson*
+
+* CHANGED DEFAULT: ActiveRecord::Base.schema_format is now :ruby by default instead of :sql. This means that we'll assume you want to live in the world of db/schema.rb where the grass is green and the girls are pretty. If your schema contains un-dumpable elements, such as constraints or database-specific column types, you just got an invitation to either 1) patch the dumper to include foreign key support, 2) stop being db specific, or 3) just change the default in config/environment.rb to config.active_record.schema_format = :sql -- we even include an example for that on new Rails skeletons now. Brought to you by the federation of opinionated framework builders! *David Heinemeier Hansson*
+
+* Added -r/--repeat option to script/process/spawner that offers the same loop protection as the spinner did. This deprecates the script/process/spinner, so it's no longer included in the default Rails skeleton, but still available for backwards compatibility #3461 *ror@andreas-s.net*
+
+* Added collision option to template generation in generators #3329 [anna@wota.jp]. Examples:
+
+ m.template "stuff.config" , "config/stuff.config" , :collision => :skip
+ m.template "auto-stamping", "config/generator.log", :collision => :force
+
+* Added more information to script/plugin's doings to ease debugging #3755 *Rick Olson*
+
+* Changed the default configuration for lighttpd to use tmp/sockets instead of log/ for the FastCGI sockets *David Heinemeier Hansson*
+
+* Added a default configuration of the FileStore for fragment caching if tmp/cache is available, which makes action/fragment caching ready to use out of the box with no additional configuration *David Heinemeier Hansson*
+
+* Changed the default session configuration to place sessions in tmp/sessions, if that directory is available, instead of /tmp (this essentially means a goodbye to 9/10 White Screen of Death errors and should have web hosting firms around the world cheering) *David Heinemeier Hansson*
+
+* Added tmp/sessions, tmp/cache, and tmp/sockets as default directories in the Rails skeleton *David Heinemeier Hansson*
+
+* Added that script/generate model will now automatically create a migration file for the model created. This can be turned off by calling the generator with --skip-migration *David Heinemeier Hansson*
+
+* Added -d/--database option to the rails command, so you can do "rails --database=sqlite2 myapp" to start a new application preconfigured to use SQLite2 as the database. Removed the configuration examples from SQLite and PostgreSQL from the default MySQL configuration *David Heinemeier Hansson*
+
+* Allow script/server -c /path/to/lighttpd.conf *Jeremy Kemper*
+
+* Remove hardcoded path to reaper script in script/server *Jeremy Kemper*
+
+* Update script.aculo.us to V1.5.3 *Thomas Fuchs*
+
+* Added SIGTRAP signal handler to RailsFCGIHandler that'll force the process into a breakpoint after the next request. This breakpoint can then be caught with script/breakpointer and give you access to the Ruby image inside that process. Useful for debugging memory leaks among other things *David Heinemeier Hansson*
+
+* Changed default lighttpd.conf to use CWD from lighttpd 1.4.10 that allows the same configuration to be used for both detach and not. Also ensured that auto-repeaping of FCGIs only happens when lighttpd is not detached. *David Heinemeier Hansson*
+
+* Added Configuration#after_initialize for registering a block which gets called after the framework is fully initialized. Useful for things like per-environment configuration of plugins. *Michael Koziarski*
+
+* Added check for RAILS_FRAMEWORK_ROOT constant that allows the Rails framework to be found in a different place than vendor/rails. Should be set in boot.rb. *David Heinemeier Hansson*
+
+* Fixed that static requests could unlock the mutex guarding dynamic requests in the WEBrick servlet #3433 *tom@craz8.com*
+
+* Fixed documentation tasks to work with Rake 0.7.0 #3563 *kazuhiko@fdiary.net*
+
+* Update to Prototype 1.5.0_pre0 *Sam Stephenson*
+
+* Sort the list of plugins so we load in a consistent order *Rick Olson*
+
+* Show usage when script/plugin is called without arguments *tom@craz8.com*
+
+* Corrected problems with plugin loader where plugins set 'name' incorrectly #3297 *anna@wota.jp*
+
+* Make migration generator only report on exact duplicate names, not partial dupliate names. #3442 *jeremy@planetargon.com Marcel Molina Jr.*
+
+* Fix typo in mailer generator USAGE. #3458 *chriztian.steinmeier@gmail.com*
+
+* Ignore version mismatch between pg_dump and the database server. #3457 *Simon Stapleton*
+
+* Reap FCGI processes after lighttpd exits. *Sam Stephenson*
+
+* Honor ActiveRecord::Base.pluralize_table_names when creating and destroying session store table. #3204. *rails@bencurtis.com, Marcel Molina Jr.*
+
+## 1.0.0 (December 13th, 2005) ##
+
+* Update instructions on how to find and install generators. #3172. *Chad Fowler*
+
+* Generator looks in vendor/generators also. *Chad Fowler*
+
+* Generator copies files in binary mode. #3156 *minimudboy@gmail.com*
+
+* Add builtin/ to the gemspec. Closes #3047. *Nicholas Seckar, Sam Stephenson*
+
+* Add install.rb file to plugin generation which is loaded, if it exists, when you install a plugin. *Marcel Molina Jr.*
+
+* Run initialize_logger in script/lighttpd to ensure the log file exists before tailing it. *Sam Stephenson*
+
+* Make load_fixtures include csv fixtures. #3053. *me@mdaines.com*
+
+* Fix freeze_gems so that the latest rails version is dumped by default. *Nicholas Seckar*
+
+* script/plugin: handle root paths and plugin names which contain spaces. #2995 *justin@aspect.net*
+
+* Model generator: correct relative path to test_helper in unit test. *Jeremy Kemper*
+
+* Make the db_schema_dump task honor the SCHEMA environment variable if present the way db_schema_import does. #2931. *Blair Zajac*
+
+* Have the lighttpd server script report the actual ip to which the server is bound. #2903. *Adam*
+
+* Add plugin library directories to the load path after the lib directory so that libraries in the lib directory get precedence. #2910. *James Adam*
+
+* Make help for the console command more explicit about how to specify the desired environment in which to run the console. #2911. *anonymous*
+
+* PostgreSQL: the purge_test_database Rake task shouldn't explicitly specify the template0 template when creating a fresh test database. #2964 *Dreamer3*
+
+* Introducing the session_migration generator. Creates an add_session_table migration. Allows generator to specify migrations directory. #2958, #2960 *Rick Olson*
+
+* script/console uses RAILS_ENV environment variable if present. #2932 [Blair Zajac <blair@orcaware.com>
+
+* Windows: eliminate the socket option in database.yml. #2924 *Wayne Vucenic <waynev@gmail.com>*
+
+* Eliminate nil from newly generated logfiles. #2927 *Blair Zajac <blair@orcaware.com>*
+
+* Rename Version constant to VERSION. #2802 *Marcel Molina Jr.*
+
+* Eliminate Subversion dependencies in scripts/plugin. Correct install options. Introduce --force option to reinstall a plugin. Remove useless --long option for list. Use --quiet to quiet the download output and --revision to update to a specific svn revision. #2842 *Chad Fowler, Rick Olson*
+
+* SQLite: the clone_structure_to_test and purge_test_database Rake tasks should always use the test environment. #2846 *Rick Olson*
+
+* Make sure that legacy db tasks also reference :database for SQLite #2830 *kazuhiko@fdiary.net*
+
+* Pass __FILE__ when evaluating plugins' init.rb. #2817 *James Adam*
+
+* Better svn status matching for generators. #2814 *François Beausoleil <francois.beausoleil@gmail.com>, Blair Zajac <blair@orcaware.com>*
+
+* Don't reload routes until plugins have been loaded so they have a chance to extend the routing capabilities *David Heinemeier Hansson*
+
+* Don't detach or fork for script/server tailing *Nicholas Seckar*
+
+* Changed all script/* to use #!/usr/bin/env ruby instead of hard-coded Ruby path. public/dispatcher.* still uses the hard-coded path for compatibility with web servers that don't have Ruby in path *David Heinemeier Hansson*
+
+* Force RAILS_ENV to be "test" when running tests, so that ENV["RAILS_ENV"] = "production" in config/environment.rb doesn't wreck havok [David Heinemeier Hansson] #2660
+
+* Correct versioning in :freeze_gems Rake task. #2778 *Jakob Skjerning, Jeremy Kemper*
+
+* Added an omnipresent RailsInfoController with a properties action that delivers an HTML rendering of Rails::Info (but only when local_request? is true). Added a new default index.html which fetches this with Ajax. *Sam Stephenson*
+
+
+## 0.14.3 (RC4) (November 7th, 2005) ##
+
+## Add 'add_new_scripts' rake task for adding new rails scripts to script/ Jamis Buck ##
+
+* Remove bogus hyphen from script/process/reaper calls to 'ps'. #2767 *anonymous*
+
+* Copy lighttpd.conf when it is first needed, instead of on app creation *Jamis Buck*
+
+* Use require_library_or_gem 'fcgi' in script/server *Sam Stephenson*
+
+* Added default lighttpd config in config/lighttpd.conf and added a default runner for lighttpd in script/server (works like script/server, but using lighttpd and FastCGI). It will use lighttpd if available, otherwise WEBrick. You can force either or using 'script/server lighttpd' or 'script/server webrick' *David Heinemeier Hansson*
+
+* New configuration option config.plugin_paths which may be a single path like the default 'vendor/plugins' or an array of paths: ['vendor/plugins', 'lib/plugins']. *Jeremy Kemper*
+
+* Plugins are discovered in nested paths, so you can organize your plugins directory as you like. *Jeremy Kemper*
+
+* Refactor load_plugin from load_plugins. #2757 *alex.r.moon@gmail.com*
+
+* Make use of silence_stderr in script/lighttpd, script/plugin, and Rails::Info *Sam Stephenson*
+
+* Enable HTTP installation of plugins when svn isn't avaialable. Closes #2661. *Chad Fowler*
+
+* Added script/about to display formatted Rails::Info output *Sam Stephenson*
+
+* Added Rails::Info to catalog assorted information about a Rails application's environment *Sam Stephenson*
+
+* Tail the logfile when running script/server lighttpd in the foreground *Sam Stephenson*
+
+* Try to guess the port number from config/lighttpd.conf in script/server lighttpd *Sam Stephenson*
+
+* Don't reap spawn-fcgi. #2727 *matthew@walker.wattle.id.au*
+
+* Reaper knows how to find processes even if the dispatch path is very long. #2711 *matthew@walker.wattle.id.au*
+
+* Make fcgi handler respond to TERM signals with an explicit exit *Jamis Buck*
+
+* Added demonstration of fixture use to the test case generated by the model generator *David Heinemeier Hansson*
+
+* If specified, pass PostgreSQL client character encoding to createdb. #2703 *Kazuhiko <kazuhiko@fdiary.net>*
+
+* Catch CGI multipart parse errors. Wrap dispatcher internals in a failsafe response handler. *Jeremy Kemper*
+
+* The freeze_gems Rake task accepts the VERSION environment variable to decide which version of Rails to pull into vendor/rails. *Chad Fowler, Jeremy Kemper*
+
+* Removed script.aculo.us.js, builder.js and slider.js (preperation for move of scriptaculous extensions to plugins, core scriptaculous will remain in Railties) *Thomas Fuchs*
+
+* The freeze_edge Rake task does smarter svn detection and can export a specific revision by passing the REVISION environment variable. For example: rake freeze_edge REVISION=1234. #2663 *Rick Olson*
+
+* Comment database.yml and include PostgreSQL and SQLite examples. *Jeremy Kemper*
+
+* Improve script/plugin on Windows. #2646 *Chad Fowler*
+
+* The *_plugindoc Rake tasks look deeper into the plugins' lib directories. #2652 *bellis@deepthought.org*
+
+* The PostgreSQL :db_structure_dump Rake task limits its dump to the schema search path in database.yml. *Anatol Pomozov <anatol.pomozov@gmail.com>*
+
+* Add task to generate rdoc for all installed plugins. *Marcel Molina Jr.*
+
+* Update script.aculo.us to V1.5_rc4 *Thomas Fuchs*
+
+* Add default Mac + DarwinPorts MySQL socket locations to the app generator. *Jeremy Kemper*
+
+* Migrations may be destroyed: script/destroy migration foo. #2635 *Charles M. Gerungan <charles.gerungan@gmail.com>, Jamis Buck, Jeremy Kemper*
+
+* Added that plugins can carry generators and that generator stub files can be created along with new plugins using script/generate plugin <name> --with-generator *David Heinemeier Hansson*
+
+* Removed app/apis as a default empty dir since its automatically created when using script/generate web_service *David Heinemeier Hansson*
+
+* Added script/plugin to manage plugins (install, remove, list, etc) *Ryan Tomayko*
+
+* Added test_plugins task: Run the plugin tests in vendor/plugins/**/test (or specify with PLUGIN=name) *David Heinemeier Hansson*
+
+* Added plugin generator to create a stub structure for a new plugin in vendor/plugins (see "script/generate plugin" for help) *David Heinemeier Hansson*
+
+* Fixed scaffold generator when started with only 1 parameter #2609 *self@mattmower.com*
+
+* rake should run functional tests even if the unit tests have failures *Jim Weirich*
+
+* Back off cleanpath to be symlink friendly. Closes #2533 *Nicholas Seckar*
+
+* Load rake task files in alphabetical order so you can build dependencies and count on them #2554 *Blair Zajac*
+
+
+## 0.14.2 (RC3) (October 26th, 2005) ##
+
+* Constants set in the development/test/production environment file are set in Object
+
+* Scaffold generator pays attention to the controller name. #2562 *self@mattmower.com*
+
+* Include tasks from vendor/plugins/*/tasks in the Rakefile #2545 *Rick Olson*
+
+
+## 0.14.1 (RC2) (October 19th, 2005) ##
+
+* Don't clean RAILS_ROOT on windows
+
+* Remove trailing '/' from RAILS_ROOT *Nicholas Seckar*
+
+* Upgraded to Active Record 1.12.1 and Action Pack 1.10.1
+
+
+## 0.14.0 (RC1) (October 16th, 2005) ##
+
+* Moved generator folder from RAILS_ROOT/generators to RAILS_ROOT/lib/generators *Tobias Lütke*
+
+* Fix rake dev and related commands *Nicholas Seckar*
+
+* The rails command tries to deduce your MySQL socket by running `mysql_config
+ --socket`. If it fails, default to /path/to/your/mysql.sock
+
+* Made the rails command use the application name for database names in the tailored database.yml file. Example: "rails ~/projects/blog" will use "blog_development" instead of "rails_development". *Florian Weber*
+
+* Added Rails framework freezing tasks: freeze_gems (freeze to current gems), freeze_edge (freeze to Rails SVN trunk), unfreeze_rails (float with newest gems on system)
+
+* Added update_javascripts task which will fetch all the latest js files from your current rails install. Use after updating rails. *Tobias Lütke*
+
+* Added cleaning of RAILS_ROOT to useless elements such as '../non-dot-dot/'. Provides cleaner backtraces and error messages. *Nicholas Seckar*
+
+* Made the instantiated/transactional fixtures settings be controlled through Rails::Initializer. Transactional and non-instantiated fixtures are default from now on. *Florian Weber*
+
+* Support using different database adapters for development and test with ActiveRecord::Base.schema_format = :ruby *Sam Stephenson*
+
+* Make webrick work with session(:off)
+
+* Add --version, -v option to the Rails command. Closes #1840. *stancell*
+
+* Update Prototype to V1.4.0_pre11, script.aculo.us to V1.5_rc3 [2504] and fix the rails generator to include the new .js files *Thomas Fuchs*
+
+* Make the generator skip a file if it already exists and is identical to the new file.
+
+* Add experimental plugin support #2335
+
+* Made Rakefile aware of new .js files in script.aculo.us *Thomas Fuchs*
+
+* Make table_name and controller_name in generators honor AR::Base.pluralize_table_names. #1216 #2213 *kazuhiko@fdiary.net*
+
+* Clearly label functional and unit tests in rake stats output. #2297 *lasse.koskela@gmail.com*
+
+* Make the migration generator only check files ending in *.rb when calculating the next file name #2317 *Chad Fowler*
+
+* Added prevention of duplicate migrations from the generator #2240 *François Beausoleil*
+
+* Add db_schema_dump and db_schema_import rake tasks to work with the new ActiveRecord::SchemaDumper (for dumping a schema to and reading a schema from a ruby file).
+
+* Reformed all the config/environments/* files to conform to the new Rails::Configuration approach. Fully backwards compatible.
+
+* Added create_sessions_table, drop_sessions_table, and purge_sessions_table as rake tasks for databases that supports migrations (MySQL, PostgreSQL, SQLite) to get a table for use with CGI::Session::ActiveRecordStore
+
+* Added dump of schema version to the db_structure_dump task for databases that support migrations #1835 *Rick Olson*
+
+* Fixed script/profiler for Ruby 1.8.2 #1863 *Rick Olson*
+
+* Fixed clone_structure_to_test task for SQLite #1864 *jon@burningbush.us*
+
+* Added -m/--mime-types option to the WEBrick server, so you can specify a Apache-style mime.types file to load #2059 *ask@develooper.com*
+
+* Added -c/--svn option to the generator that'll add new files and remove destroyed files using svn add/revert/remove as appropriate #2064 *Kevin Clark*
+
+* Added -c/--charset option to WEBrick server, so you can specify a default charset (which without changes is UTF-8) #2084 *wejn@box.cz*
+
+* Make the default stats task extendable by modifying the STATS_DIRECTORIES constant
+
+* Allow the selected environment to define RAILS_DEFAULT_LOGGER, and have Rails::Initializer use it if it exists.
+
+* Moved all the shared tasks from Rakefile into Rails, so that the Rakefile is empty and doesn't require updating.
+
+* Added Rails::Initializer and Rails::Configuration to abstract all of the common setup out of config/environment.rb (uses config/boot.rb to bootstrap the initializer and paths)
+
+* Fixed the scaffold generator to fail right away if the database isn't accessible instead of in mid-air #1169 *Chad Fowler*
+
+* Corrected project-local generator location in scripts.rb #2010 *Michael Schuerig*
+
+* Don't require the environment just to clear the logs #2093 *Scott Barron*
+
+* Make the default rakefile read *.rake files from config/tasks (for easy extension of the rakefile by e.g. generators)
+
+* Only load breakpoint in development mode and when BREAKPOINT_SERVER_PORT is defined.
+
+* Allow the --toggle-spin switch on process/reaper to be negated
+
+* Replace render_partial with render :partial in scaffold generator *Nicholas Seckar*
+
+* Added -w flag to ps in process/reaper #1934 *Scott Barron*
+
+* Allow ERb in the database.yml file (just like with fixtures), so you can pull out the database configuration in environment variables #1822 *Duane Johnson*
+
+* Added convenience controls for FCGI processes (especially when managed remotely): spinner, spawner, and reaper. They reside in script/process. More details can be had by calling them with -h/--help.
+
+* Added load_fixtures task to the Rakefile, which will load all the fixtures into the database for the current environment #1791 *Marcel Molina Jr.*
+
+* Added an empty robots.txt to public/, so that web servers asking for it won't trigger a dynamic call, like favicon.ico #1738 *Michael Schubert*
+
+* Dropped the 'immediate close-down' of FCGI processes since it didn't work consistently and produced bad responses when it didn't. So now a TERM ensures exit after the next request (just as if the process is handling a request when it receives the signal). This means that you'll have to 'nudge' all FCGI processes with a request in order to ensure that they have all reloaded. This can be done by something like ./script/process/repear --nudge 'http://www.myapp.com' --instances 10, which will load the myapp site 10 times (and thus hit all of the 10 FCGI processes once, enough to shut down).
+
+
+## 0.13.1 (11 July, 2005) ##
+
+* Look for app-specific generators in RAILS_ROOT/generators rather than the clunky old RAILS_ROOT/script/generators. Nobody really uses this feature except for the unit tests, so it's a negligible-impact change. If you want to work with third-party generators, drop them in ~/.rails/generators or simply install gems.
+
+* Fixed that each request with the WEBrick adapter would open a new database connection #1685 *Sam Stephenson*
+
+* Added support for SQL Server in the database rake tasks #1652 [Ken Barker] Note: osql and scptxfr may need to be installed on your development environment. This involves getting the .exes and a .rll (scptxfr) from a production SQL Server (not developer level SQL Server). Add their location to your Environment PATH and you are all set.
+
+* Added a VERSION parameter to the migrate task that allows you to do "rake migrate VERSION=34" to migrate to the 34th version traveling up or down depending on the current version
+
+* Extend Ruby version check to include RUBY_RELEASE_DATE >= '2005-12-25', the final Ruby 1.8.2 release #1674 *court3nay*
+
+* Improved documentation for environment config files #1625 *court3nay*
+
+
+## 0.13.0 (6 July, 2005) ##
+
+* Changed the default logging level in config/environment.rb to INFO for production (so SQL statements won't be logged)
+
+* Added migration generator: ./script/generate migration add_system_settings
+
+* Added "migrate" as rake task to execute all the pending migrations from db/migrate
+
+* Fixed that model generator would make fixtures plural, even if ActiveRecord::Base.pluralize_table_names was false #1185 *Marcel Molina Jr.*
+
+* Added a DOCTYPE of HTML transitional to the HTML files generated by Rails #1124 *Michael Koziarski*
+
+* SIGTERM also gracefully exits dispatch.fcgi. Ignore SIGUSR1 on Windows.
+
+* Add the option to manually manage garbage collection in the FastCGI dispatcher. Set the number of requests between GC runs in your public/dispatch.fcgi *Stefan Kaes*
+
+* Allow dynamic application reloading for dispatch.fcgi processes by sending a SIGHUP. If the process is currently handling a request, the request will be allowed to complete first. This allows production fcgi's to be reloaded without having to restart them.
+
+* RailsFCGIHandler (dispatch.fcgi) no longer tries to explicitly flush $stdout (CgiProcess#out always calls flush)
+
+* Fixed rakefile actions against PostgreSQL when the password is all numeric #1462 *Michael Schubert*
+
+* ActionMailer::Base subclasses are reloaded with the other rails components #1262
+
+* Made the WEBrick adapter not use a mutex around action performance if ActionController::Base.allow_concurrency is true (default is false)
+
+* Fixed that mailer generator generated fixtures/plural while units expected fixtures/singular #1457 *Scott Barron*
+
+* Added a 'whiny nil' that's aim to ensure that when users pass nil to methods where that isn't appropriate, instead of NoMethodError? and the name of some method used by the framework users will see a message explaining what type of object was expected. Only active in test and development environments by default #1209 *Michael Koziarski*
+
+* Fixed the test_helper.rb to be safe for requiring controllers from multiple spots, like app/controllers/article_controller.rb and app/controllers/admin/article_controller.rb, without reloading the environment twice #1390 *Nicholas Seckar*
+
+* Fixed Webrick to escape + characters in URL's the same way that lighttpd and apache do #1397 *Nicholas Seckar*
+
+* Added -e/--environment option to script/runner #1408 *François Beausoleil*
+
+* Modernize the scaffold generator to use the simplified render and test methods and to change style from @params["id"] to params[:id]. #1367
+
+* Added graceful exit from pressing CTRL-C during the run of the rails command #1150 *Caleb Tennis*
+
+* Allow graceful exits for dispatch.fcgi processes by sending a SIGUSR1. If the process is currently handling a request, the request will be allowed to complete and then will terminate itself. If a request is not being handled, the process is terminated immediately (via #exit). This basically works like restart graceful on Apache. *Jamis Buck*
+
+* Made dispatch.fcgi more robust by catching fluke errors and retrying unless its a permanent condition. *Jamis Buck*
+
+* Added console --profile for profiling an IRB session #1154 *Jeremy Kemper*
+
+* Changed console_sandbox into console --sandbox #1154 *Jeremy Kemper*
+
+
+## 0.12.1 (20th April, 2005) ##
+
+* Upgraded to Active Record 1.10.1, Action Pack 1.8.1, Action Mailer 0.9.1, Action Web Service 0.7.1
+
+
+## 0.12.0 (19th April, 2005) ##
+
+* Fixed that purge_test_database would use database settings from the development environment when recreating the test database #1122 *rails@cogentdude.com*
+
+* Added script/benchmarker to easily benchmark one or more statement a number of times from within the environment. Examples:
+
+ # runs the one statement 10 times
+ script/benchmarker 10 'Person.expensive_method(10)'
+
+ # pits the two statements against each other with 50 runs each
+ script/benchmarker 50 'Person.expensive_method(10)' 'Person.cheap_method(10)'
+
+* Added script/profiler to easily profile a single statement from within the environment. Examples:
+
+ script/profiler 'Person.expensive_method(10)'
+ script/profiler 'Person.expensive_method(10)' 10 # runs the statement 10 times
+
+* Added Rake target clear_logs that'll truncate all the *.log files in log/ to zero #1079 *Lucas Carlson*
+
+* Added lazy typing for generate, such that ./script/generate cn == ./script/generate controller and the likes #1051 *k@v2studio.com*
+
+* Fixed that ownership is brought over in pg_dump during tests for PostgreSQL #1060 *pburleson*
+
+* Upgraded to Active Record 1.10.0, Action Pack 1.8.0, Action Mailer 0.9.0, Action Web Service 0.7.0, Active Support 1.0.4
+
+
+## 0.11.1 (27th March, 2005) ##
+
+* Fixed the dispatch.fcgi use of a logger
+
+* Upgraded to Active Record 1.9.1, Action Pack 1.7.0, Action Mailer 0.8.1, Action Web Service 0.6.2, Active Support 1.0.3
+
+
+## 0.11.0 (22th March, 2005) ##
+
+* Removed SCRIPT_NAME from the WEBrick environment to prevent conflicts with PATH_INFO #896 *Nicholas Seckar*
+
+* Removed ?$1 from the dispatch.f/cgi redirect line to get rid of 'complete/path/from/request.html' => nil being in the @params now that the ENV["REQUEST_URI"] is used to determine the path #895 *dblack/Nicholas Seckar*
+
+* Added additional error handling to the FastCGI dispatcher to catch even errors taking down the entire process
+
+* Improved the generated scaffold code a lot to take advantage of recent Rails developments #882 *Tobias Lütke*
+
+* Combined the script/environment.rb used for gems and regular files version. If vendor/rails/* has all the frameworks, then files version is used, otherwise gems #878 *Nicholas Seckar*
+
+* Changed .htaccess to allow dispatch.* to be called from a sub-directory as part of the push with Action Pack to make Rails work on non-vhost setups #826 *Nicholas Seckar/Tobias Lütke*
+
+* Added script/runner which can be used to run code inside the environment by eval'ing the first parameter. Examples:
+
+ ./script/runner 'ReminderService.deliver'
+ ./script/runner 'Mailer.receive(STDIN.read)'
+
+ This makes it easier to do CRON and postfix scripts without actually making a script just to trigger 1 line of code.
+
+* Fixed webrick_server cookie handling to allow multiple cookes to be set at once #800, #813 *dave@cherryville.org*
+
+* Fixed the Rakefile's interaction with postgresql to:
+
+ 1. Use PGPASSWORD and PGHOST in the environment to fix prompting for
+ passwords when connecting to a remote db and local socket connections.
+ 2. Add a '-x' flag to pg_dump which stops it dumping privileges #807 [rasputnik]
+ 3. Quote the user name and use template0 when dumping so the functions doesn't get dumped too #855 [pburleson]
+ 4. Use the port if available #875 [madrobby]
+
+* Upgraded to Active Record 1.9.0, Action Pack 1.6.0, Action Mailer 0.8.0, Action Web Service 0.6.1, Active Support 1.0.2
+
+
+## 0.10.1 (7th March, 2005) ##
+
+* Fixed rake stats to ignore editor backup files like model.rb~ #791 *Sebastian Kanthak*
+
+* Added exception shallowing if the DRb server can't be started (not worth making a fuss about to distract new users) #779 *Tobias Lütke*
+
+* Added an empty favicon.ico file to the public directory of new applications (so the logs are not spammed by its absence)
+
+* Fixed that scaffold generator new template should use local variable instead of instance variable #778 *Dan Peterson*
+
+* Allow unit tests to run on a remote server for PostgreSQL #781 *adamm@galacticasoftware.com*
+
+* Added web_service generator (run ./script/generate web_service for help) #776 *Leon Bredt*
+
+* Added app/apis and components to code statistics report #729 *Scott Barron*
+
+* Fixed WEBrick server to use ABSOLUTE_RAILS_ROOT instead of working_directory #687 *Nicholas Seckar*
+
+* Fixed rails_generator to be usable without RubyGems #686 *Cristi BALAN*
+
+* Fixed -h/--help for generate and destroy generators #331
+
+* Added begin/rescue around the FCGI dispatcher so no uncaught exceptions can bubble up to kill the process (logs to log/fastcgi.crash.log)
+
+* Fixed that association#count would produce invalid sql when called sequentialy #659 *kanis@comcard.de*
+
+* Fixed test/mocks/testing to the correct test/mocks/test #740
+
+* Added early failure if the Ruby version isn't 1.8.2 or above #735
+
+* Removed the obsolete -i/--index option from the WEBrick servlet #743
+
+* Upgraded to Active Record 1.8.0, Action Pack 1.5.1, Action Mailer 0.7.1, Action Web Service 0.6.0, Active Support 1.0.1
+
+
+## 0.10.0 (24th February, 2005) ##
+
+* Changed default IP binding for WEBrick from 127.0.0.1 to 0.0.0.0 so that the server is accessible both locally and remotely #696 *Marcel Molina Jr.*
+
+* Fixed that script/server -d was broken so daemon mode couldn't be used #687 *Nicholas Seckar*
+
+* Upgraded to breakpoint 92 which fixes:
+
+ * overload IRB.parse_opts(), fixes #443
+ => breakpoints in tests work even when running them via rake
+ * untaint handlers, might fix an issue discussed on the Rails ML
+ * added verbose mode to breakpoint_client
+ * less noise caused by breakpoint_client by default
+ * ignored TerminateLineInput exception in signal handler
+ => quiet exit on Ctrl-C
+
+* Added support for independent components residing in /components. Example:
+
+ Controller: components/list/items_controller.rb
+ (holds a List::ItemsController class with uses_component_template_root called)
+
+ Model : components/list/item.rb
+ (namespace is still shared, so an Item model in app/models will take precedence)
+
+ Views : components/list/items/show.rhtml
+
+
+* Added --sandbox option to script/console that'll roll back all changes made to the database when you quit #672 *Jeremy Kemper*
+
+* Added 'recent' as a rake target that'll run tests for files that changed in the last 10 minutes #612 *Jeremy Kemper*
+
+* Changed script/console to default to development environment and drop --no-inspect #650 *Jeremy Kemper*
+
+* Added that the 'fixture :posts' syntax can be used for has_and_belongs_to_many fixtures where a model doesn't exist #572 *Jeremy Kemper*
+
+* Added that running test_units and test_functional now performs the clone_structure_to_test as well #566 *rasputnik*
+
+* Added new generator framework that informs about its doings on generation and enables updating and destruction of generated artifacts. See the new script/destroy and script/update for more details #487 *Jeremy Kemper*
+
+* Added Action Web Service as a new add-on framework for Action Pack *Leon Bredt*
+
+* Added Active Support as an independent utility and standard library extension bundle
+
+* Upgraded to Active Record 1.7.0, Action Pack 1.5.0, Action Mailer 0.7.0
+
+
+## 0.9.5 (January 25th, 2005) ##
+
+* Fixed dependency reloading by switching to a remove_const approach where all Active Records, Active Record Observers, and Action Controllers are reloading by undefining their classes. This enables you to remove methods in all three types and see the change reflected immediately and it fixes #539. This also means that only those three types of classes will benefit from the const_missing and reloading approach. If you want other classes (like some in lib/) to reload, you must use require_dependency to do it.
+
+* Added Florian Gross' latest version of Breakpointer and friends that fixes a variaty of bugs #441 *Florian Gross*
+
+* Fixed skeleton Rakefile to work with sqlite3 out of the box #521 *rasputnik*
+
+* Fixed that script/breakpointer didn't get the Ruby path rewritten as the other scripts #523 *brandt@kurowski.net*
+
+* Fixed handling of syntax errors in models that had already been succesfully required once in the current interpreter
+
+* Fixed that models that weren't referenced in associations weren't being reloaded in the development mode by reinstating the reload
+
+* Fixed that generate scaffold would produce bad functional tests
+
+* Fixed that FCGI can also display SyntaxErrors
+
+* Upgraded to Active Record 1.6.0, Action Pack 1.4.0
+
+
+## 0.9.4.1 (January 18th, 2005) ##
+
+* Added 5-second timeout to WordNet alternatives on creating reserved-word models #501 *Marcel Molina Jr.*
+
+* Fixed binding of caller #496 *Alexey*
+
+* Upgraded to Active Record 1.5.1, Action Pack 1.3.1, Action Mailer 0.6.1
+
+
+## 0.9.4 (January 17th, 2005) ##
+
+* Added that ApplicationController will catch a ControllerNotFound exception if someone attempts to access a url pointing to an unexisting controller *Tobias Lütke*
+
+* Flipped code-to-test ratio around to be more readable #468 *Scott Baron*
+
+* Fixed log file permissions to be 666 instead of 777 (so they're not executable) #471 *Lucas Carlson*
+
+* Fixed that auto reloading would some times not work or would reload the models twice #475 *Tobias Lütke*
+
+* Added rewrite rules to deal with caching to public/.htaccess
+
+* Added the option to specify a controller name to "generate scaffold" and made the default controller name the plural form of the model.
+
+* Added that rake clone_structure_to_test, db_structure_dump, and purge_test_database tasks now pick up the source database to use from
+ RAILS_ENV instead of just forcing development #424 *Tobias Lütke*
+
+* Fixed script/console to work with Windows (that requires the use of irb.bat) #418 *Chris McGrath*
+
+* Fixed WEBrick servlet slowdown over time by restricting the load path reloading to mod_ruby
+
+* Removed Fancy Indexing as a default option on the WEBrick servlet as it made it harder to use various caching schemes
+
+* Upgraded to Active Record 1.5, Action Pack 1.3, Action Mailer 0.6
+
+
+## 0.9.3 (January 4th, 2005) ##
+
+* Added support for SQLite in the auto-dumping/importing of schemas for development -> test #416
+
+* Added automated rewriting of the shebang lines on installs through the gem rails command #379 *Manfred Stienstra*
+
+* Added ActionMailer::Base.deliver_method = :test to the test environment so that mail objects are available in ActionMailer::Base.deliveries
+ for functional testing.
+
+* Added protection for creating a model through the generators with a name of an existing class, like Thread or Date.
+ It'll even offer you a synonym using wordnet.princeton.edu as a look-up. No, I'm not kidding :) *Florian Gross*
+
+* Fixed dependency management to happen in a unified fashion for Active Record and Action Pack using the new Dependencies module. This means that
+ the environment options needs to change from:
+
+ Before in development.rb:
+ ActionController::Base.reload_dependencies = true  
+ ActiveRecord::Base.reload_associations     = true
+
+ Now in development.rb:
+ Dependencies.mechanism = :load
+
+ Before in production.rb and test.rb:
+ ActionController::Base.reload_dependencies = false
+ ActiveRecord::Base.reload_associations     = false
+
+ Now in production.rb and test.rb:
+ Dependencies.mechanism = :require
+
+* Fixed problems with dependency caching and controller hierarchies on Ruby 1.8.2 in development mode #351
+
+* Fixed that generated action_mailers doesnt need to require the action_mailer since thats already done in the environment #382 *Lucas Carlson*
+
+* Upgraded to Action Pack 1.2.0 and Active Record 1.4.0
+
+
+## 0.9.2 ##
+
+* Fixed CTRL-C exists from the Breakpointer to be a clean affair without error dumping *Kent Sibilev*
+
+* Fixed "rake stats" to work with sub-directories in models and controllers and to report the code to test ration *Scott Baron*
+
+* Added that Active Record associations are now reloaded instead of cleared to work with the new const_missing hook in Active Record.
+
+* Added graceful handling of an inaccessible log file by redirecting output to STDERR with a warning #330 *rainmkr*
+
+* Added support for a -h/--help parameter in the generator #331 *Ulysses*
+
+* Fixed that File.expand_path in config/environment.rb would fail when dealing with symlinked public directories *mjobin*
+
+* Upgraded to Action Pack 1.1.0 and Active Record 1.3.0
+
+
+## 0.9.1 ##
+
+* Upgraded to Action Pack 1.0.1 for important bug fix
+
+* Updated gem dependencies
+
+
+## 0.9.0 ##
+
+* Renamed public/dispatch.servlet to script/server -- it wasn't really dispatching anyway as its delegating calls to public/dispatch.rb
+
+* Renamed AbstractApplicationController and abstract_application.rb to ApplicationController and application.rb, so that it will be possible
+ for the framework to automatically pick up on app/views/layouts/application.rhtml and app/helpers/application.rb
+
+* Added script/console that makes it even easier to start an IRB session for interacting with the domain model. Run with no-args to
+ see help.
+
+* Added breakpoint support through the script/breakpointer client. This means that you can break out of execution at any point in
+ the code, investigate and change the model, AND then resume execution! Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find_all
+ breakpoint "Breaking out from the list"
+ end
+ end
+
+ So the controller will accept the action, run the first line, then present you with a IRB prompt in the breakpointer window.
+ Here you can do things like:
+
+ Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint'
+
+ >> @posts.inspect
+ => "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
+ #<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
+ >> @posts.first.title = "hello from a breakpoint"
+ => "hello from a breakpoint"
+
+ ...and even better is that you can examine how your runtime objects actually work:
+
+ >> f = @posts.first
+ => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
+ >> f.
+ Display all 152 possibilities? (y or n)
+
+ Finally, when you're ready to resume execution, you press CTRL-D
+
+* Changed environments to be configurable through an environment variable. By default, the environment is "development", but you
+ can change that and set your own by configuring the Apache vhost with a string like (mod_env must be available on the server):
+
+ SetEnv RAILS_ENV production
+
+ ...if you're using WEBrick, you can pick the environment to use with the command-line parameters -e/--environment, like this:
+
+ ruby public/dispatcher.servlet -e production
+
+* Added a new default environment called "development", which leaves the production environment to be tuned exclusively for that.
+
+* Added a start_server in the root of the Rails application to make it even easier to get started
+
+* Fixed public/.htaccess to use RewriteBase and share the same rewrite rules for all the dispatch methods
+
+* Fixed webrick_server to handle requests in a serialized manner (the Rails reloading infrastructure is not thread-safe)
+
+* Added support for controllers in directories. So you can have:
+
+ app/controllers/account_controller.rb # URL: /account/
+ app/controllers/admin/account_controller.rb # URL: /admin/account/
+
+ NOTE: You need to update your public/.htaccess with the new rules to pick it up
+
+* Added reloading for associations and dependencies under cached environments like FastCGI and mod_ruby. This makes it possible to use
+ those environments for development. This is turned on by default, but can be turned off with
+ ActiveRecord::Base.reload_associations = false and ActionController::Base.reload_dependencies = false in production environments.
+
+* Added support for sub-directories in app/models. So now you can have something like Basecamp with:
+
+ app/models/accounting
+ app/models/project
+ app/models/participants
+ app/models/settings
+
+ It's poor man's namespacing, but only for file-system organization. You still require files just like before.
+ Nothing changes inside the files themselves.
+
+
+* Fixed a few references in the tests generated by new_mailer *Jeremy Kemper*
+
+* Added support for mocks in testing with test/mocks
+
+* Cleaned up the environments a bit and added global constant RAILS_ROOT
+
+
+## 0.8.5 (9) ##
+
+* Made dev-util available to all tests, so you can insert breakpoints in any test case to get an IRB prompt at that point [Jeremy Kemper]:
+
+ def test_complex_stuff
+ @david.projects << @new_project
+ breakpoint "Let's have a closer look at @david"
+ end
+
+ You need to install dev-utils yourself for this to work ("gem install dev-util").
+
+* Added shared generator behavior so future upgrades should be possible without manually copying over files *Jeremy Kemper*
+
+* Added the new helper style to both controller and helper templates *Jeremy Kemper*
+
+* Added new_crud generator for creating a model and controller at the same time with explicit scaffolding *Jeremy Kemper*
+
+* Added configuration of Test::Unit::TestCase.fixture_path to test_helper to concide with the new AR fixtures style
+
+* Fixed that new_model was generating singular table/fixture names
+
+* Upgraded to Action Mailer 0.4.0
+
+* Upgraded to Action Pack 0.9.5
+
+* Upgraded to Active Record 1.1.0
+
+
+## 0.8.0 (15) ##
+
+* Removed custom_table_name option for new_model now that the Inflector is as powerful as it is
+
+* Changed the default rake action to just do testing and separate API generation and coding statistics into a "doc" task.
+
+* Fixed WEBrick dispatcher to handle missing slashes in the URLs gracefully *alexey*
+
+* Added user option for all postgresql tool calls in the rakefile *elvstone*
+
+* Fixed problem with running "ruby public/dispatch.servlet" instead of "cd public; ruby dispatch.servlet" *alexey*
+
+* Fixed WEBrick server so that it no longer hardcodes the ruby interpreter used to "ruby" but will get the one used based
+ on the Ruby runtime configuration. *Marcel Molina Jr.*
+
+* Fixed Dispatcher so it'll route requests to magic_beans to MagicBeansController/magic_beans_controller.rb *Caio Chassot*
+
+* "new_controller MagicBeans" and "new_model SubscriptionPayments" will now both behave properly as they use the new Inflector.
+
+* Fixed problem with MySQL foreign key constraint checks in Rake :clone_production_structure_to_test target *Andreas Schwarz*
+
+* Changed WEBrick server to by default be auto-reloading, which is slower but makes source changes instant.
+ Class compilation cache can be turned on with "-c" or "--cache-classes".
+
+* Added "-b/--binding" option to WEBrick dispatcher to bind the server to a specific IP address (default: 127.0.0.1) *Kevin Temp*
+
+* dispatch.fcgi now DOESN'T set FCGI_PURE_RUBY as it was slowing things down for now reason *Andreas Schwarz*
+
+* Added new_mailer generator to work with Action Mailer
+
+* Included new framework: Action Mailer 0.3
+
+* Upgraded to Action Pack 0.9.0
+
+* Upgraded to Active Record 1.0.0
+
+
+## 0.7.0 ##
+
+* Added an optional second argument to the new_model script that allows the programmer to specify the table name,
+ which will used to generate a custom table_name method in the model and will also be used in the creation of fixtures.
+ *Kevin Radloff*
+
+* script/new_model now turns AccountHolder into account_holder instead of accountholder *Kevin Radloff*
+
+* Fixed the faulty handleing of static files with WEBrick *Andreas Schwarz*
+
+* Unified function_test_helper and unit_test_helper into test_helper
+
+* Fixed bug with the automated production => test database dropping on PostgreSQL *dhawkins*
+
+* create_fixtures in both the functional and unit test helper now turns off the log during fixture generation
+ and can generate more than one fixture at a time. Which makes it possible for assignments like:
+
+ @people, @projects, @project_access, @companies, @accounts =
+ create_fixtures "people", "projects", "project_access", "companies", "accounts"
+
+* Upgraded to Action Pack 0.8.5 (locally-scoped variables, partials, advanced send_file)
+
+* Upgraded to Active Record 0.9.5 (better table_name guessing, cloning, find_all_in_collection)
+
+
+## 0.6.5 ##
+
+* No longer specifies a template for rdoc, so it'll use whatever is default (you can change it in the rakefile)
+
+* The new_model generator will now use the same rules for plural wordings as Active Record
+ (so Category will give categories, not categorys) *Kevin Radloff*
+
+* dispatch.fcgi now sets FCGI_PURE_RUBY to true to ensure that it's the Ruby version that's loaded *danp*
+
+* Made the GEM work with Windows
+
+* Fixed bug where mod_ruby would "forget" the load paths added when switching between controllers
+
+* PostgreSQL are now supported for the automated production => test database dropping *Kevin Radloff*
+
+* Errors thrown by the dispatcher are now properly handled in FCGI.
+
+* Upgraded to Action Pack 0.8.0 (lots and lots and lots of fixes)
+
+* Upgraded to Active Record 0.9.4 (a bunch of fixes)
+
+
+## 0.6.0 ##
+
+* Added AbstractionApplicationController as a superclass for all controllers generated. This class can be used
+ to carry filters and methods that are to be shared by all. It has an accompanying ApplicationHelper that all
+ controllers will also automatically have available.
+
+* Added environments that can be included from any script to get the full Active Record and Action Controller
+ context running. This can be used by maintenance scripts or to interact with the model through IRB. Example:
+
+ require 'config/environments/production'
+
+ for account in Account.find_all
+ account.recalculate_interests
+ end
+
+ A short migration script for an account model that had it's interest calculation strategy changed.
+
+* Accessing the index of a controller with "/weblog" will now redirect to "/weblog/" (only on Apache, not WEBrick)
+
+* Simplified the default Apache config so even remote requests are served off CGI as a default.
+ You'll now have to do something specific to activate mod_ruby and FCGI (like using the force urls).
+ This should make it easier for new comers that start on an external server.
+
+* Added more of the necessary Apache options to .htaccess to make it easier to setup
+
+* Upgraded to Action Pack 0.7.9 (lots of fixes)
+
+* Upgraded to Active Record 0.9.3 (lots of fixes)
+
+
+## 0.5.7 ##
+
+* Fixed bug in the WEBrick dispatcher that prevented it from getting parameters from the URL
+ (through GET requests or otherwise)
+
+* Added lib in root as a place to store app specific libraries
+
+* Added lib and vendor to load_path, so anything store within can be loaded directly.
+ Hence lib/redcloth.rb can be loaded with require "redcloth"
+
+* Upgraded to Action Pack 0.7.8 (lots of fixes)
+
+* Upgraded to Active Record 0.9.2 (minor upgrade)
+
+
+## 0.5.6 ##
+
+* Upgraded to Action Pack 0.7.7 (multipart form fix)
+
+* Updated the generated template stubs to valid XHTML files
+
+* Ensure that controllers generated are capitalized, so "new_controller TodoLists"
+ gives the same as "new_controller Todolists" and "new_controller todolists".
+
+
+## 0.5.5 ##
+
+* Works on Windows out of the box! (Dropped symlinks)
+
+* Added webrick dispatcher: Try "ruby public/dispatch.servlet --help" *Florian Gross*
+
+* Report errors about initialization to browser (instead of attempting to use uninitialized logger)
+
+* Upgraded to Action Pack 0.7.6
+
+* Upgraded to Active Record 0.9.1
+
+* Added distinct 500.html instead of reusing 404.html
+
+* Added MIT license
+
+
+## 0.5.0 ##
+
+* First public release
diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE
index c73d1af096..03bde18130 100644
--- a/railties/MIT-LICENSE
+++ b/railties/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2011 David Heinemeier Hansson
+Copyright (c) 2004-2012 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/railties/README.rdoc b/railties/README.rdoc
index 95c43045b0..6248b5feed 100644
--- a/railties/README.rdoc
+++ b/railties/README.rdoc
@@ -15,12 +15,23 @@ The latest version of Railties can be installed with RubyGems:
* gem install railties
-Documentation can be found at
-
-* http://api.rubyonrails.org
+Source code can be downloaded as part of the Rails project on GitHub
+* https://github.com/rails/rails/tree/master/railties
== License
-Railties is released under the MIT license.
+Railties is released under the MIT license:
+
+* http://www.opensource.org/licenses/MIT
+
+== Support
+
+API documentation is at
+
+* http://api.rubyonrails.org
+
+Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+
+* https://github.com/rails/rails/issues
diff --git a/railties/Rakefile b/railties/Rakefile
index 25e515e016..993ba840ff 100755..100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
require 'rake/testtask'
require 'rubygems/package_task'
@@ -21,7 +20,8 @@ namespace :test do
'test',
'lib',
"#{File.dirname(__FILE__)}/../activesupport/lib",
- "#{File.dirname(__FILE__)}/../actionpack/lib"
+ "#{File.dirname(__FILE__)}/../actionpack/lib",
+ "#{File.dirname(__FILE__)}/../activemodel/lib"
]
ruby "-I#{dash_i.join ':'}", file
end
@@ -44,18 +44,6 @@ task :update_readme do
cp "./README.rdoc", readme
end
-desc 'Generate guides (for authors), use ONLY=foo to process just "foo.textile"'
-task :generate_guides do
- ENV["WARN_BROKEN_LINKS"] = "1" # authors can't disable this
- ruby "guides/rails_guides.rb"
-end
-
-# Validate guides -------------------------------------------------------------------------
-desc 'Validate guides, use ONLY=foo to process just "foo.html"'
-task :validate_guides do
- ruby "guides/w3c_validator.rb"
-end
-
# Generate GEM ----------------------------------------------------------------------------
spec = eval(File.read('railties.gemspec'))
@@ -72,12 +60,3 @@ task :release => :package do
Rake::Gemcutter::Tasks.new(spec).define
Rake::Task['gem:push'].invoke
end
-
-desc "Publish the guides"
-task :pguides => :generate_guides do
- require 'rake/contrib/sshpublisher'
- mkdir_p 'pkg'
- `tar -czf pkg/guides.gz guides/output`
- Rake::SshFilePublisher.new("web.rubyonrails.org", "/u/sites/guides.rubyonrails.org/public", "pkg", "guides.gz").upload
- `ssh web.rubyonrails.org 'cd /u/sites/guides.rubyonrails.org/public/ && tar -xvzf guides.gz && mv guides/output/* . && rm -rf guides*'`
-end
diff --git a/railties/guides/assets/images/posts_index.png b/railties/guides/assets/images/posts_index.png
deleted file mode 100644
index f6cd2f9b80..0000000000
--- a/railties/guides/assets/images/posts_index.png
+++ /dev/null
Binary files differ
diff --git a/railties/guides/code/getting_started/Gemfile b/railties/guides/code/getting_started/Gemfile
deleted file mode 100644
index 51774934cd..0000000000
--- a/railties/guides/code/getting_started/Gemfile
+++ /dev/null
@@ -1,32 +0,0 @@
-source 'http://rubygems.org'
-
-gem 'rails', '3.1.0'
-# Bundle edge Rails instead:
-# gem 'rails', :git => 'git://github.com/rails/rails.git'
-
-gem 'sqlite3'
-
-
-# Gems used only for assets and not required
-# in production environments by default.
-group :assets do
- gem 'sass-rails', " ~> 3.1.0"
- gem 'coffee-rails', "~> 3.1.0"
- gem 'uglifier'
-end
-
-gem 'jquery-rails'
-
-# Use unicorn as the web server
-# gem 'unicorn'
-
-# Deploy with Capistrano
-# gem 'capistrano'
-
-# To use debugger
-# gem 'ruby-debug19', :require => 'ruby-debug'
-
-group :test do
- # Pretty printed test output
- gem 'turn', :require => false
-end
diff --git a/railties/guides/code/getting_started/app/assets/javascripts/application.js b/railties/guides/code/getting_started/app/assets/javascripts/application.js
deleted file mode 100644
index 37c7bfcdb5..0000000000
--- a/railties/guides/code/getting_started/app/assets/javascripts/application.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// This is a manifest file that'll be compiled into including all the files listed below.
-// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
-// be included in the compiled file accessible from http://example.com/assets/application.js
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// the compiled file.
-//
-//= require jquery
-//= require jquery_ujs
-//= require_tree .
diff --git a/railties/guides/code/getting_started/app/assets/javascripts/comments.js.coffee b/railties/guides/code/getting_started/app/assets/javascripts/comments.js.coffee
deleted file mode 100644
index 761567942f..0000000000
--- a/railties/guides/code/getting_started/app/assets/javascripts/comments.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# 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/guides/code/getting_started/app/assets/javascripts/home.js.coffee b/railties/guides/code/getting_started/app/assets/javascripts/home.js.coffee
deleted file mode 100644
index 761567942f..0000000000
--- a/railties/guides/code/getting_started/app/assets/javascripts/home.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# 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/guides/code/getting_started/app/assets/javascripts/posts.js.coffee b/railties/guides/code/getting_started/app/assets/javascripts/posts.js.coffee
deleted file mode 100644
index 761567942f..0000000000
--- a/railties/guides/code/getting_started/app/assets/javascripts/posts.js.coffee
+++ /dev/null
@@ -1,3 +0,0 @@
-# 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/guides/code/getting_started/app/assets/stylesheets/application.css b/railties/guides/code/getting_started/app/assets/stylesheets/application.css
deleted file mode 100644
index fc25b5723f..0000000000
--- a/railties/guides/code/getting_started/app/assets/stylesheets/application.css
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
- * This is a manifest file that'll automatically include all the stylesheets available in this directory
- * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
- * the top of the compiled file, but it's generally better to create a new file per style scope.
- *= require_self
- *= require_tree .
-*/ \ No newline at end of file
diff --git a/railties/guides/code/getting_started/app/assets/stylesheets/comments.css.scss b/railties/guides/code/getting_started/app/assets/stylesheets/comments.css.scss
deleted file mode 100644
index e730912783..0000000000
--- a/railties/guides/code/getting_started/app/assets/stylesheets/comments.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the Comments controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/railties/guides/code/getting_started/app/assets/stylesheets/home.css.scss b/railties/guides/code/getting_started/app/assets/stylesheets/home.css.scss
deleted file mode 100644
index f0ddc6846a..0000000000
--- a/railties/guides/code/getting_started/app/assets/stylesheets/home.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the home controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/railties/guides/code/getting_started/app/assets/stylesheets/posts.css.scss b/railties/guides/code/getting_started/app/assets/stylesheets/posts.css.scss
deleted file mode 100644
index ed4dfd10f2..0000000000
--- a/railties/guides/code/getting_started/app/assets/stylesheets/posts.css.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-// Place all the styles related to the Posts controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/railties/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss b/railties/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss
deleted file mode 100644
index 05188f08ed..0000000000
--- a/railties/guides/code/getting_started/app/assets/stylesheets/scaffolds.css.scss
+++ /dev/null
@@ -1,56 +0,0 @@
-body {
- background-color: #fff;
- color: #333;
- font-family: verdana, arial, helvetica, sans-serif;
- font-size: 13px;
- line-height: 18px; }
-
-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, &.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; } }
diff --git a/railties/guides/code/getting_started/app/controllers/home_controller.rb b/railties/guides/code/getting_started/app/controllers/home_controller.rb
deleted file mode 100644
index 6cc31c1ca3..0000000000
--- a/railties/guides/code/getting_started/app/controllers/home_controller.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-class HomeController < ApplicationController
- def index
- end
-
-end
diff --git a/railties/guides/code/getting_started/app/controllers/posts_controller.rb b/railties/guides/code/getting_started/app/controllers/posts_controller.rb
deleted file mode 100644
index 7e903c984c..0000000000
--- a/railties/guides/code/getting_started/app/controllers/posts_controller.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-class PostsController < ApplicationController
- http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
- # GET /posts
- # GET /posts.json
- def index
- @posts = Post.all
-
- respond_to do |format|
- format.html # index.html.erb
- format.json { render json: @posts }
- end
- end
-
- # GET /posts/1
- # GET /posts/1.json
- def show
- @post = Post.find(params[:id])
-
- respond_to do |format|
- format.html # show.html.erb
- format.json { render json: @post }
- end
- end
-
- # GET /posts/new
- # GET /posts/new.json
- def new
- @post = Post.new
-
- respond_to do |format|
- format.html # new.html.erb
- format.json { render json: @post }
- end
- end
-
- # GET /posts/1/edit
- def edit
- @post = Post.find(params[:id])
- end
-
- # POST /posts
- # POST /posts.json
- def create
- @post = Post.new(params[:post])
-
- respond_to do |format|
- if @post.save
- format.html { redirect_to @post, notice: 'Post was successfully created.' }
- format.json { render json: @post, status: :created, location: @post }
- else
- format.html { render action: "new" }
- format.json { render json: @post.errors, status: :unprocessable_entity }
- end
- end
- end
-
- # PUT /posts/1
- # PUT /posts/1.json
- def update
- @post = Post.find(params[:id])
-
- respond_to do |format|
- if @post.update_attributes(params[:post])
- format.html { redirect_to @post, notice: 'Post was successfully updated.' }
- format.json { head :ok }
- else
- format.html { render action: "edit" }
- format.json { render json: @post.errors, status: :unprocessable_entity }
- end
- end
- end
-
- # DELETE /posts/1
- # DELETE /posts/1.json
- def destroy
- @post = Post.find(params[:id])
- @post.destroy
-
- respond_to do |format|
- format.html { redirect_to posts_url }
- format.json { head :ok }
- end
- end
-end
diff --git a/railties/guides/code/getting_started/app/helpers/home_helper.rb b/railties/guides/code/getting_started/app/helpers/home_helper.rb
deleted file mode 100644
index 23de56ac60..0000000000
--- a/railties/guides/code/getting_started/app/helpers/home_helper.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-module HomeHelper
-end
diff --git a/railties/guides/code/getting_started/app/helpers/posts_helper.rb b/railties/guides/code/getting_started/app/helpers/posts_helper.rb
deleted file mode 100644
index b6e8e67894..0000000000
--- a/railties/guides/code/getting_started/app/helpers/posts_helper.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module PostsHelper
- def join_tags(post)
- post.tags.map { |t| t.name }.join(", ")
- end
-end
diff --git a/railties/guides/code/getting_started/app/models/post.rb b/railties/guides/code/getting_started/app/models/post.rb
deleted file mode 100644
index 61c2b5ae44..0000000000
--- a/railties/guides/code/getting_started/app/models/post.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class Post < ActiveRecord::Base
- validates :name, :presence => true
- validates :title, :presence => true,
- :length => { :minimum => 5 }
-
- has_many :comments, :dependent => :destroy
- has_many :tags
-
- accepts_nested_attributes_for :tags, :allow_destroy => :true,
- :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
-end
diff --git a/railties/guides/code/getting_started/app/models/tag.rb b/railties/guides/code/getting_started/app/models/tag.rb
deleted file mode 100644
index 30992e8ba9..0000000000
--- a/railties/guides/code/getting_started/app/models/tag.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class Tag < ActiveRecord::Base
- belongs_to :post
-end
diff --git a/railties/guides/code/getting_started/app/views/home/index.html.erb b/railties/guides/code/getting_started/app/views/home/index.html.erb
deleted file mode 100644
index bb4f3dcd1f..0000000000
--- a/railties/guides/code/getting_started/app/views/home/index.html.erb
+++ /dev/null
@@ -1,2 +0,0 @@
-<h1>Hello, Rails!</h1>
-<%= link_to "My Blog", posts_path %>
diff --git a/railties/guides/code/getting_started/app/views/posts/_form.html.erb b/railties/guides/code/getting_started/app/views/posts/_form.html.erb
deleted file mode 100644
index e27da7f413..0000000000
--- a/railties/guides/code/getting_started/app/views/posts/_form.html.erb
+++ /dev/null
@@ -1,32 +0,0 @@
-<% @post.tags.build %>
-<%= form_for(@post) do |post_form| %>
- <% if @post.errors.any? %>
- <div id="errorExplanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
- <ul>
- <% @post.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
-
- <div class="field">
- <%= post_form.label :name %><br />
- <%= post_form.text_field :name %>
- </div>
- <div class="field">
- <%= post_form.label :title %><br />
- <%= post_form.text_field :title %>
- </div>
- <div class="field">
- <%= post_form.label :content %><br />
- <%= post_form.text_area :content %>
- </div>
- <h2>Tags</h2>
- <%= render :partial => 'tags/form',
- :locals => {:form => post_form} %>
- <div class="actions">
- <%= post_form.submit %>
- </div>
-<% end %>
diff --git a/railties/guides/code/getting_started/app/views/posts/edit.html.erb b/railties/guides/code/getting_started/app/views/posts/edit.html.erb
deleted file mode 100644
index 720580236b..0000000000
--- a/railties/guides/code/getting_started/app/views/posts/edit.html.erb
+++ /dev/null
@@ -1,6 +0,0 @@
-<h1>Editing post</h1>
-
-<%= render 'form' %>
-
-<%= link_to 'Show', @post %> |
-<%= link_to 'Back', posts_path %>
diff --git a/railties/guides/code/getting_started/app/views/posts/index.html.erb b/railties/guides/code/getting_started/app/views/posts/index.html.erb
deleted file mode 100644
index 45dee1b25f..0000000000
--- a/railties/guides/code/getting_started/app/views/posts/index.html.erb
+++ /dev/null
@@ -1,27 +0,0 @@
-<h1>Listing posts</h1>
-
-<table>
- <tr>
- <th>Name</th>
- <th>Title</th>
- <th>Content</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
-
-<% @posts.each do |post| %>
- <tr>
- <td><%= post.name %></td>
- <td><%= post.title %></td>
- <td><%= post.content %></td>
- <td><%= link_to 'Show', post %></td>
- <td><%= link_to 'Edit', edit_post_path(post) %></td>
- <td><%= link_to 'Destroy', post, confirm: 'Are you sure?', method: :delete %></td>
- </tr>
-<% end %>
-</table>
-
-<br />
-
-<%= link_to 'New Post', new_post_path %>
diff --git a/railties/guides/code/getting_started/app/views/posts/show.html.erb b/railties/guides/code/getting_started/app/views/posts/show.html.erb
deleted file mode 100644
index da78a9527b..0000000000
--- a/railties/guides/code/getting_started/app/views/posts/show.html.erb
+++ /dev/null
@@ -1,31 +0,0 @@
-<p class="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-<p>
- <b>Tags:</b>
- <%= join_tags(@post) %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= render "comments/form" %>
-
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
diff --git a/railties/guides/code/getting_started/app/views/tags/_form.html.erb b/railties/guides/code/getting_started/app/views/tags/_form.html.erb
deleted file mode 100644
index 7e424b0e20..0000000000
--- a/railties/guides/code/getting_started/app/views/tags/_form.html.erb
+++ /dev/null
@@ -1,12 +0,0 @@
-<%= form.fields_for :tags do |tag_form| %>
- <div class="field">
- <%= tag_form.label :name, 'Tag:' %>
- <%= tag_form.text_field :name %>
- </div>
- <% unless tag_form.object.nil? || tag_form.object.new_record? %>
- <div class="field">
- <%= tag_form.label :_destroy, 'Remove:' %>
- <%= tag_form.check_box :_destroy %>
- </div>
- <% end %>
-<% end %>
diff --git a/railties/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb b/railties/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb
deleted file mode 100644
index cf95b1c3d0..0000000000
--- a/railties/guides/code/getting_started/db/migrate/20110901013701_create_tags.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-class CreateTags < ActiveRecord::Migration
- def change
- create_table :tags do |t|
- t.string :name
- t.references :post
-
- t.timestamps
- end
- add_index :tags, :post_id
- end
-end
diff --git a/railties/guides/code/getting_started/test/fixtures/tags.yml b/railties/guides/code/getting_started/test/fixtures/tags.yml
deleted file mode 100644
index 8485668908..0000000000
--- a/railties/guides/code/getting_started/test/fixtures/tags.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-# Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html
-
-one:
- name: MyString
- post:
-
-two:
- name: MyString
- post:
diff --git a/railties/guides/code/getting_started/vendor/plugins/.gitkeep b/railties/guides/code/getting_started/vendor/plugins/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
--- a/railties/guides/code/getting_started/vendor/plugins/.gitkeep
+++ /dev/null
diff --git a/railties/guides/source/active_resource_basics.textile b/railties/guides/source/active_resource_basics.textile
deleted file mode 100644
index 851aac1a3f..0000000000
--- a/railties/guides/source/active_resource_basics.textile
+++ /dev/null
@@ -1,120 +0,0 @@
-h2. Active Resource Basics
-
-This guide should provide you with all you need to get started managing the connection between business objects and RESTful web services. It implements a way to map web-based resources to local objects with CRUD semantics.
-
-endprologue.
-
-WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails.
-
-h3. Introduction
-
-Active Resource allows you to connect with RESTful web services. So, in Rails, Resource classes inherited from +ActiveResource::Base+ and live in +app/models+.
-
-h3. Configuration and Usage
-
-Putting Active Resource to use is very similar to Active Record. It's as simple as creating a model class
-that inherits from ActiveResource::Base and providing a <tt>site</tt> class variable to it:
-
-<ruby>
-class Person < ActiveResource::Base
- self.site = "http://api.people.com:3000/"
-end
-</ruby>
-
-Now the Person class is REST enabled and can invoke REST services very similarly to how Active Record invokes
-life cycle methods that operate against a persistent store.
-
-h3. Reading and Writing Data
-
-Active Resource make request over HTTP using a standard JSON format. It mirrors the RESTful routing built into Action Controller but will also work with any other REST service that properly implements the protocol.
-
-h4. Read
-
-Read requests use the GET method and expect the JSON form of whatever resource/resources is/are being requested.
-
-<ruby>
-# Find a person with id = 1
-person = Person.find(1)
-# Check if a person exists with id = 1
-Person.exists?(1) # => true
-# Get all resources of Person class
-Person.all
-</ruby>
-
-h4. Create
-
-Creating a new resource submits the JSON form of the resource as the body of the request with HTTP POST method and parse the response into Active Resource object.
-
-<ruby>
-person = Person.create(:name => 'Vishnu')
-person.id # => 1
-</ruby>
-
-h4. Update
-
-To update an existing resource, 'save' method is used. This method make a HTTP PUT request in JSON format.
-
-<ruby>
-person = Person.find(1)
-person.name = 'Atrai'
-person.save
-</ruby>
-
-h4. Delete
-
-'destroy' method makes a HTTP DELETE request for an existing resource in JSON format to delete that resource.
-
-<ruby>
-person = Person.find(1)
-person.destroy
-</ruby>
-
-h3. Validations
-
-Module to support validation and errors with Active Resource objects. The module overrides Base#save to rescue ActiveResource::ResourceInvalid exceptions and parse the errors returned in the web service response. The module also adds an errors collection that mimics the interface of the errors provided by ActiveRecord::Errors.
-
-h4. Validating client side resources by overriding validation methods in base class
-
-<ruby>
-class Person < ActiveResource::Base
- self.site = "http://api.people.com:3000/"
-
- protected
-
- def validate
- errors.add("last", "has invalid characters") unless last =~ /[a-zA-Z]*/
- end
-end
-</ruby>
-
-h4. Validating client side resources
-
-Consider a Person resource on the server requiring both a first_name and a last_name with a validates_presence_of :first_name, :last_name declaration in the model:
-
-<ruby>
-person = Person.new(:first_name => "Jim", :last_name => "")
-person.save # => false (server returns an HTTP 422 status code and errors)
-person.valid? # => false
-person.errors.empty? # => false
-person.errors.count # => 1
-person.errors.full_messages # => ["Last name can't be empty"]
-person.errors[:last_name] # => ["can't be empty"]
-person.last_name = "Halpert"
-person.save # => true (and person is now saved to the remote service)
-</ruby>
-
-h4. Public instance methods
-
-ActiveResource::Validations have three public instance methods
-
-h5. errors()
-
-This will return errors object that holds all information about attribute error messages
-
-h5. save_with_validation(options=nil)
-
-This validates the resource with any local validations written in base class and then it will try to POST if there are no errors.
-
-h5. valid?
-
-Runs all the local validations and will return true if no errors.
diff --git a/railties/guides/source/engines.textile b/railties/guides/source/engines.textile
deleted file mode 100644
index 126d09ab87..0000000000
--- a/railties/guides/source/engines.textile
+++ /dev/null
@@ -1,393 +0,0 @@
-h2. Getting Started with Engines
-
-In this guide you will learn about engines and how they can be used to provide additional functionality to their host applications through a clean and very easy-to-use interface. You will learn the following things in this guide:
-
-* What makes an engine
-* How to generate an engine
-* Building features for the engine
-* Hooking the engine into an application
-* Overriding engine functionality in the application
-
-endprologue.
-
-h3. What are engines?
-
-Engines can be considered miniature applications that provide functionality to their host applications. A Rails application is actually just a "supercharged" engine, with the +Rails::Application+ class inheriting from +Rails::Engine+. Therefore, engines and applications share common functionality but are at the same time two separate beasts. Engines and applications also share a common structure, as you'll see throughout this guide.
-
-Engines are also closely related to plugins where the two share a common +lib+ directory structure and are both generated using the +rails plugin new+ generator.
-
-The engine that will be generated for this guide will be called "blorgh". The engine will provide blogging functionality to its host applications, allowing for new posts and comments to be created. For now, you will be working solely within the engine itself and in later sections you'll see how to hook it into an application.
-
-Engines can also be isolated from their host applications. This means that an application is able to have a path provided by a routing helper such as +posts_path+ and use an engine also that provides a path also called +posts_path+, and the two would not clash. Along with this, controllers, models and table names are also namespaced. You'll see how to do this later in this guide.
-
-To see demonstrations of other engines, check out "Devise":https://github.com/plataformatec/devise, an engine that provides authentication for its parent applications, or "Forem":https://github.com/radar/forem, an engine that provides forum functionality.
-
-Finally, engines would not have be possible without the work of James Adam, Piotr Sarnacki, the Rails Core Team, and a number of other people. If you ever meet them, don't forget to say thanks!
-
-h3. Generating an engine
-
-To generate an engine with Rails 3.1, you will need to run the plugin generator and pass it the +--mountable+ option. To generate the beginnings of the "blorgh" engine you will need to run this command in a terminal:
-
-<shell>
-$ rails plugin new blorgh --mountable
-</shell>
-
-The +--mountable+ option tells the plugin generator that you want to create an engine (which is a mountable plugin, hence the option name), creating the basic directory structure of an engine by providing things such as the foundations of an +app+ folder, as well a +config/routes.rb+ file. This generator also provides a file at +lib/blorgh/engine.rb+ which is identical in function to an application's +config/application.rb+ file.
-
-h4. Inside an engine
-
-h5. Critical files
-
-At the root of the engine's directory, lives a +blorgh.gemspec+ file. When you include the engine into the application later on, you will do so with this line in a Rails application's +Gemfile+:
-
-<ruby>
- gem 'blorgh', :path => "vendor/engines/blorgh"
-</ruby>
-
-By specifying it as a gem within the +Gemfile+, Bundler will load it as such, parsing this +blorgh.gemspec+ file and requiring a file within the +lib+ directory called +lib/blorgh.rb+. This file requires the +blorgh/engine.rb+ file (located at +lib/blorgh/engine.rb+) and defines a base module called +Blorgh+.
-
-<ruby>
-require "blorgh/engine"
-
-module Blorgh
-end
-</ruby>
-
-Within +lib/blorgh/engine.rb+ is the base class for the engine:
-
-<ruby>
-module Blorgh
- class Engine < Rails::Engine
- isolate_namespace Blorgh
- end
-end
-</ruby>
-
-By inheriting from the +Rails::Engine+ class, this engine gains all the functionality it needs, such as being able to serve requests to its controllers.
-
-The +isolate_namespace+ method here deserves special notice. This call is responsible for isolating the controllers, models, routes and other things into their own namespace. Without this, there is a possibility that the engine's components could "leak" into the application, causing unwanted disruption. It is recommended that this line be left within this file.
-
-h5. +app+ directory
-
-Inside the +app+ directory there lives the standard +assets+, +controllers+, +helpers+, +mailers+, +models+ and +views+ directories that you should be familiar with from an application. The +helpers+, +mailers+ and +models+ directories are empty and so aren't described in this section. We'll look more into models in a future section.
-
-Within the +app/assets+ directory, there is the +images+, +javascripts+ and +stylesheets+ directories which, again, you should be familiar with due to their similarities of an application. One difference here however is that each directory contains a sub-directory with the engine name. Because this engine is going to be namespaced, its assets should be too.
-
-Within the +app/controllers+ directory there is a +blorgh+ directory and inside that a file called +application_controller.rb+. This file will provide any common functionality for the controllers of the engine. The +blorgh+ directory is where the other controllers for the engine will go. By placing them within this namespaced directory, you prevent them from possibly clashing with identically-named controllers within other engines or even within the application.
-
-Lastly, the +app/views+ directory contains a +layouts+ folder which contains file at +blorgh/application.html.erb+ which allows you to specify a layout for the engine. If this engine is to be used as a stand-alone engine, then you would add any customization to its layout in this file, rather than the applications +app/views/layouts/application.html.erb+ file.
-
-h5. +script+ directory
-
-This directory contains one file, +script/rails+, which allows you to use the +rails+ sub-commands and generators just like you would within an application. This means that you will very easily be able to generate new controllers and models for this engine.
-
-h5. +test+ directory
-
-The +test+ directory is where tests for the engine will go. To test the engine, there is a cut-down version of a Rails application embedded within it at +test/dummy+. This application will mount the engine in the +test/dummy/config/routes.rb+ file:
-
-<ruby>
-Rails.application.routes.draw do
-
- mount Blorgh::Engine => "/blorgh"
-end
-</ruby>
-
-This line mounts the engine at the path of +/blorgh+, which will make it accessible through the application only at that path. We will look more into mounting an engine after some features have been developed.
-
-Also in the test directory is the +test/integration+ directory, where integration tests for the engine should be placed.
-
-h3. Providing engine functionality
-
-The engine that this guide covers will provide posting and commenting functionality.
-
-h4. Generating a post resource
-
-The first thing to generate for a blog engine is the +Post+ model and related controller. To quickly generate this, you can use the Rails scaffold generator.
-
-<shell>
-$ rails generate scaffold post title:string text:text
-</shell>
-
-This command will output this information:
-
-<shell>
-invoke active_record
-create db/migrate/[timestamp]_create_blorgh_posts.rb
-create app/models/blorgh/post.rb
-invoke test_unit
-create test/unit/blorgh/post_test.rb
-create test/fixtures/blorgh/posts.yml
- route resources :posts
-invoke scaffold_controller
-create app/controllers/blorgh/posts_controller.rb
-invoke erb
-create app/views/blorgh/posts
-create app/views/blorgh/posts/index.html.erb
-create app/views/blorgh/posts/edit.html.erb
-create app/views/blorgh/posts/show.html.erb
-create app/views/blorgh/posts/new.html.erb
-create app/views/blorgh/posts/_form.html.erb
-invoke test_unit
-create test/functional/blorgh/posts_controller_test.rb
-invoke helper
-create app/helpers/blorgh/posts_helper.rb
-invoke test_unit
-create test/unit/helpers/blorgh/posts_helper_test.rb
-invoke assets
-invoke js
-create app/assets/javascripts/blorgh/posts.js
-invoke css
-create app/assets/stylesheets/blorgh/posts.css
-invoke css
-create app/assets/stylesheets/scaffold.css
-</shell>
-
-The first thing that the scaffold generator does is invoke the +active_record+ generator, which generates a migration and a model for the resource. Note here, however, that the migration is called +create_blorgh_posts+ rather than the usual +create_posts+. This is due to the +isolate_namespace+ method called in the +Blorgh::Engine+ class's definition. The model here is also namespaced, being placed at +app/models/blorgh/post.rb+ rather than +app/models/post.rb+.
-
-Next, the +test_unit+ generator is invoked for this model, generating a unit test at +test/unit/blorgh/post_test.rb+ (rather than +test/unit/post_test.rb+) and a fixture at +test/fixtures/blorgh/posts.yml+ (rather than +test/fixtures/posts.yml+).
-
-After that, a line for the resource is inserted into the +config/routes.rb+ file for the engine. This line is simply +resources :posts+, turning the +config/routes.rb+ file into this:
-
-<ruby>
-Blorgh::Engine.routes.draw do
- resources :posts
-
-end
-</ruby>
-
-Note here that the routes are drawn upon the +Blorgh::Engine+ object rather than the +YourApp::Application+ class. This is so that the engine routes are confined to the engine itself and can be mounted at a specific point as shown in the "test directory":#test-directory section.
-
-Next, the +scaffold_controller+ generator is invoked, generating a controlled called +Blorgh::PostsController+ (at +app/controllers/blorgh/posts_controller.rb+) and its related views at +app/views/blorgh/posts+. This generator also generates a functional test for the controller (+test/functional/blorgh/posts_controller_test.rb+) and a helper (+app/helpers/blorgh/posts_controller.rb+).
-
-Everything this generator has generated is neatly namespaced. The controller's class is defined within the +Blorgh+ module:
-
-<ruby>
-module Blorgh
- class PostsController < ApplicationController
- ...
- end
-end
-</ruby>
-
-NOTE: The +ApplicationController+ class being inherited from here is the +Blorgh::ApplicationController+, not an application's +ApplicationController+.
-
-The helper is also namespaced:
-
-<ruby>
-module Blorgh
- class PostsHelper
- ...
- end
-end
-</ruby>
-
-This helps prevent conflicts with any other engine or application that may have a post resource also.
-
-Finally, two files that are the assets for this resource are generated, +app/assets/javascripts/blorgh/posts.js+ and +app/assets/javascripts/blorgh/posts.css+. You'll see how to use these a little later.
-
-By default, the scaffold styling is not applied to the engine as the engine's layout file, +app/views/blorgh/application.html.erb+ doesn't load it. To make this apply, insert this line into the +<head>+ tag of this layout:
-
-<erb>
-<%= stylesheet_link_tag "scaffold" %>
-</erb>
-
-You can see what the engine has so far by running +rake db:migrate+ at the root of our engine to run the migration generated by the scaffold generator, and then running +rails server+. When you open +http://localhost:3000/blorgh/posts+ you will see the default scaffold that has been generated.
-
-!images/engines_scaffold.png(Blank engine scaffold)!
-
-Click around! You've just generated your first engine's first functions.
-
-If you'd rather play around in the console, +rails console+ will also work just like a Rails application. Remember: the +Post+ model is namespaced, so to reference it you must call it as +Blorgh::Post+.
-
-<ruby>
- >> Blorgh::Post.find(1)
- => #<Blorgh::Post id: 1 ...>
-</ruby>
-
-One final thing is that the +posts+ resource for this engine should be the root of the engine. Whenever someone goes to the root path where the engine is mounted, they should be shown a list of posts. This can be made to happen if this line is inserted into the +config/routes.rb+ file inside the engine:
-
-<ruby>
-root :to => "posts#index"
-</ruby>
-
-Now people will only need to go to the root of the engine to see all the posts, rather than visiting +/posts+.
-
-h4. Generating a comments resource
-
-Now that the engine has the ability to create new blog posts, it only makes sense to add commenting functionality as well. To do get this, you'll need to generate a comment model, a comment controller and then modify the posts scaffold to display comments and allow people to create new ones.
-
-Run the model generator and tell it to generate a +Comment+ model, with the related table having two columns: a +post_id+ integer and +text+ text column.
-
-<shell>
-$ rails generate model Comment post_id:integer text:text
-</shell>
-
-This will output the following:
-
-<shell>
-invoke active_record
-create db/migrate/[timestamp]_create_blorgh_comments.rb
-create app/models/blorgh/comment.rb
-invoke test_unit
-create test/unit/blorgh/comment_test.rb
-create test/fixtures/blorgh/comments.yml
-</shell>
-
-This generator call will generate just the necessary model files it needs, namespacing the files under a +blorgh+ directory and creating a model class called +Blorgh::Comment+.
-
-To show the comments on a post, edit +app/views/posts/show.html.erb+ and add this line before the "Edit" link:
-
-<erb>
-<h3>Comments</h3>
-<%= render @post.comments %>
-</erb>
-
-This line will require there to be a +has_many+ association for comments defined on the +Blorgh::Post+ model, which there isn't right now. To define one, open +app/models/blorgh/post.rb+ and add this line into the model:
-
-<ruby>
-has_many :comments
-</ruby>
-
-Turning the model into this:
-
-<ruby>
-module Blorgh
- class Post < ActiveRecord::Base
- has_many :comments
- end
-end
-</ruby>
-
-Because the +has_many+ is defined inside a class that is inside the +Blorgh+ module, Rails will know that you want to use the +Blorgh::Comment+ model for these objects.
-
-Next, there needs to be a form so that comments can be created on a post. To add this, put this line underneath the call to +render @post.comments+ in +app/views/blorgh/posts/show.html.erb+:
-
-<erb>
-<%= render "blorgh/comments/form" %>
-</erb>
-
-Next, the partial that this line will render needs to exist. Create a new directory at +app/views/blorgh/comments+ and in it a new file called +_form.html.erb+ which has this content to create the required partial:
-
-<erb>
-<h3>New comment</h3>
-<%= form_for [@post, @post.comments.build] do |f| %>
- <p>
- <%= f.label :text %><br />
- <%= f.text_area :text %>
- </p>
- <%= f.submit %>
-<% end %>
-</erb>
-
-This form, when submitted, is going to attempt to post to a route of +posts/:post_id/comments+ within the engine. This route doesn't exist at the moment, but can be created by changing the +resources :posts+ line inside +config/routes.rb+ into these lines:
-
-<ruby>
-resources :posts do
- resources :comments
-end
-</ruby>
-
-The route now will exist, but the controller that this route goes to does not. To create it, run this command:
-
-<shell>
-$ rails g controller comments
-</shell>
-
-This will generate the following things:
-
-<shell>
-create app/controllers/blorgh/comments_controller.rb
-invoke erb
- exist app/views/blorgh/comments
-invoke test_unit
-create test/functional/blorgh/comments_controller_test.rb
-invoke helper
-create app/helpers/blorgh/comments_helper.rb
-invoke test_unit
-create test/unit/helpers/blorgh/comments_helper_test.rb
-invoke assets
-invoke js
-create app/assets/javascripts/blorgh/comments.js
-invoke css
-create app/assets/stylesheets/blorgh/comments.css
-</shell>
-
-The form will be making a +POST+ request to +/posts/:post_id/comments+, which will correspond with the +create+ action in +Blorgh::CommentsController+. This action needs to be created and can be done by putting the following lines inside the class definition in +app/controllers/blorgh/comments_controller.rb+:
-
-<ruby>
-def create
- @post = Post.find(params[:post_id])
- @comment = @post.comments.build(params[:comment])
- flash[:notice] = "Comment has been created!"
- redirect_to post_path
-end
-</ruby>
-
-This is the final part required to get the new comment form working. Displaying the comments however, is not quite right yet. If you were to create a comment right now you would see this error:
-
-<text>
- Missing partial blorgh/comments/comment with {:handlers=>[:erb, :builder], :formats=>[:html], :locale=>[:en, :en]}. Searched in:
- * "/Users/ryan/Sites/side_projects/blorgh/test/dummy/app/views"
- * "/Users/ryan/Sites/side_projects/blorgh/app/views"
-</text>
-
-The engine is unable to find the partial required for rendering the comments. Rails has looked firstly in the application's (+test/dummy+) +app/views+ directory and then in the engine's +app/views+ directory. When it can't find it, it will throw this error. The engine knows to look for +blorgh/comments/comment+ because the model object it is receiving is from the +Blorgh::Comment+ class.
-
-This partial will be responsible for rendering just the comment text, for now. Create a new file at +app/views/blorgh/comments/_comment.html.erb+ and put this line inside it:
-
-<erb>
-<%= comment_counter + 1 %>. <%= comment.text %>
-</erb>
-
-The +comment_counter+ local variable is given to us by the +<%= render @post.comments %>+ call, as it will define this automatically and increment the counter as it iterates through each comment. It's used in this example to display a small number next to each comment when it's created.
-
-That completes the comment function of the blogging engine. Now it's time to use it within an application.
-
-h3. Hooking into application
-
-Using an engine within an application is very easy. First, the engine needs to be specified inside the application's +Gemfile+. If there isn't an application handy to test this out in, generate one using the +rails new+ command outside of the engine directory like this:
-
-<shell>
-$ rails new unicorn
-</shell>
-
-Usually, specifying the engine inside the Gemfile would be done by specifying it as a normal, everyday gem.
-
-<ruby>
-gem 'devise'
-</ruby>
-
-Because the +blorgh+ engine is still under development, it will need to have a +:path+ option for its +Gemfile+ specification:
-
-<ruby>
-gem 'blorgh', :path => "/path/to/blorgh"
-</ruby>
-
-If the whole +blorgh+ engine directory is copied to +vendor/engines/blorgh+ then it could be specified in the +Gemfile+ like this:
-
-<ruby>
-gem 'blorgh', :path => "vendor/engines/blorgh"
-</ruby>
-
-As described earlier, by placing the gem in the +Gemfile+ it will be loaded when Rails is loaded, as it will first require +lib/blorgh.rb+ in the engine and then +lib/blorgh/engine.rb+, which is the file that defines the major pieces of functionality for the engine.
-
-To make the engine's functionality accessible from within an application, it needs to be mounted in that application's +config/routes.rb+ file:
-
-<ruby>
- mount Blorgh::Engine, :at => "blog"
-</ruby>
-
-NOTE: Other engines, such as Devise, handle this a little differently by making you specify custom helpers such as +devise_for+ in the routes. These helpers do exactly the same thing, mounting pieces of the engines's functionality at a pre-defined path which may be customizable.
-
-
-
-This line will mount the engine
-TODO: Application will provide a User foundation class which the engine hooks into through a configuration setting, configurable in the application's initializers. The engine will be mounted at the +/blog+ path in the application.
-
-h3. Overriding engine functionality
-
-TODO: Cover how to override engine functionality in the engine, such as controllers and views.
-
-IDEA: I like Devise's +devise :controllers => { "sessions" => "sessions" }+ idea. Perhaps we could incorporate that into the guide?
-TODO: Mention how to use assets within an engine?
-TODO: Mention how to depend on external gems, like RedCarpet.
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
deleted file mode 100644
index bf6104b96b..0000000000
--- a/railties/guides/source/getting_started.textile
+++ /dev/null
@@ -1,1892 +0,0 @@
-h2. Getting Started with Rails
-
-This guide covers getting up and running with Ruby on Rails. After reading it,
-you should be familiar with:
-
-* Installing Rails, creating a new Rails application, and connecting your application to a database
-* The general layout of a Rails application
-* The basic principles of MVC (Model, View Controller) and RESTful design
-* How to quickly generate the starting pieces of a Rails application
-
-endprologue.
-
-WARNING. This Guide is based on Rails 3.1. Some of the code shown here will not
-work in earlier versions of Rails.
-
-h3. Guide Assumptions
-
-This guide is designed for beginners who want to get started with a Rails
-application from scratch. It does not assume that you have any prior experience
-with Rails. However, to get the most out of it, you need to have some
-prerequisites installed:
-
-* The "Ruby":http://www.ruby-lang.org/en/downloads language version 1.8.7 or higher
-
-TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails
-3.0. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02
-though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults
-on Rails 3.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 for smooth
-sailing.
-
-* The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system
- ** If you want to learn more about RubyGems, please read the "RubyGems User Guide":http://docs.rubygems.org/read/book/1
-* A working installation of the "SQLite3 Database":http://www.sqlite.org
-
-Rails is a web application framework running on the Ruby programming language.
-If you have no prior experience with Ruby, you will find a very steep learning
-curve diving straight into Rails. There are some good free resources on the
-internet for learning Ruby, including:
-
-* "Mr. Neighborly's Humble Little Ruby Book":http://www.humblelittlerubybook.com
-* "Programming Ruby":http://www.ruby-doc.org/docs/ProgrammingRuby/
-* "Why's (Poignant) Guide to Ruby":http://mislav.uniqpath.com/poignant-guide/
-
-Also, the example code for this guide is available in the rails github:https://github.com/rails/rails repository
-in rails/railties/guides/code/getting_started.
-
-h3. What is Rails?
-
-Rails is a web application development framework written in the Ruby language.
-It is designed to make programming web applications easier by making assumptions
-about what every developer needs to get started. It allows you to write less
-code while accomplishing more than many other languages and frameworks.
-Experienced Rails developers also report that it makes web application
-development more fun.
-
-Rails is opinionated software. It makes the assumption that there is a "best"
-way to do things, and it's designed to encourage that way - and in some cases to
-discourage alternatives. If you learn "The Rails Way" you'll probably discover a
-tremendous increase in productivity. If you persist in bringing old habits from
-other languages to your Rails development, and trying to use patterns you
-learned elsewhere, you may have a less happy experience.
-
-The Rails philosophy includes several guiding principles:
-
-* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and over again is a bad thing.
-* Convention Over Configuration - means that Rails makes assumptions about what you want to do and how you're going to
-do it, rather than requiring you to specify every little thing through endless configuration files.
-* REST is the best pattern for web applications - organizing your application around resources and standard HTTP verbs
-is the fastest way to go.
-
-h4. The MVC Architecture
-
-At the core of Rails is the Model, View, Controller architecture, usually just
-called MVC. MVC benefits include:
-
-* Isolation of business logic from the user interface
-* Ease of keeping code DRY
-* Making it clear where different types of code belong for easier maintenance
-
-h5. Models
-
-A model represents the information (data) of the application and the rules to
-manipulate that data. In the case of Rails, models are primarily used for
-managing the rules of interaction with a corresponding database table. In most
-cases, each table in your database will correspond to one model in your
-application. The bulk of your application's business logic will be concentrated
-in the models.
-
-h5. Views
-
-Views represent the user interface of your application. In Rails, views are
-often HTML files with embedded Ruby code that perform tasks related solely to
-the presentation of the data. Views handle the job of providing data to the web
-browser or other tool that is used to make requests from your application.
-
-h5. Controllers
-
-Controllers provide the "glue" between models and views. In Rails, controllers
-are responsible for processing the incoming requests from the web browser,
-interrogating the models for data, and passing that data on to the views for
-presentation.
-
-h4. The Components of Rails
-
-Rails ships as many individual components. Each of these components are briefly
-explained below. If you are new to Rails, as you read this section, don't get
-hung up on the details of each component, as they will be explained in further
-detail later. For instance, we will bring up Rack applications, but you don't
-need to know anything about them to continue with this guide.
-
-* Action Pack
- ** Action Controller
- ** Action Dispatch
- ** Action View
-* Action Mailer
-* Active Model
-* Active Record
-* Active Resource
-* Active Support
-* Railties
-
-h5. Action Pack
-
-Action Pack is a single gem that contains Action Controller, Action View and
-Action Dispatch. The "VC" part of "MVC".
-
-h6. Action Controller
-
-Action Controller is the component that manages the controllers in a Rails
-application. The Action Controller framework processes incoming requests to a
-Rails application, extracts parameters, and dispatches them to the intended
-action. Services provided by Action Controller include session management,
-template rendering, and redirect management.
-
-h6. Action View
-
-Action View manages the views of your Rails application. It can create both HTML
-and XML output by default. Action View manages rendering templates, including
-nested and partial templates, and includes built-in AJAX support. View
-templates are covered in more detail in another guide called "Layouts and
-Rendering":layouts_and_rendering.html.
-
-h6. Action Dispatch
-
-Action Dispatch handles routing of web requests and dispatches them as you want,
-either to your application or any other Rack application. Rack applications are
-a more advanced topic and are covered in a separate guide called "Rails on
-Rack":rails_on_rack.html.
-
-h5. Action Mailer
-
-Action Mailer is a framework for building e-mail services. You can use Action
-Mailer to receive and process incoming email and send simple plain text or
-complex multipart emails based on flexible templates.
-
-h5. Active Model
-
-Active Model provides a defined interface between the Action Pack gem services
-and Object Relationship Mapping gems such as Active Record. Active Model allows
-Rails to utilize other ORM frameworks in place of Active Record if your
-application needs this.
-
-h5. Active Record
-
-Active Record is the base for the models in a Rails application. It provides
-database independence, basic CRUD functionality, advanced finding capabilities,
-and the ability to relate models to one another, among other services.
-
-h5. Active Resource
-
-Active Resource provides a framework for managing the connection between
-business objects and RESTful web services. It implements a way to map web-based
-resources to local objects with CRUD semantics.
-
-h5. Active Support
-
-Active Support is an extensive collection of utility classes and standard Ruby
-library extensions that are used in Rails, both by the core code and by your
-applications.
-
-h5. Railties
-
-Railties is the core Rails code that builds new Rails applications and glues the
-various frameworks and plugins together in any Rails application.
-
-h4. REST
-
-Rest stands for Representational State Transfer and is the foundation of the
-RESTful architecture. This is generally considered to be Roy Fielding's doctoral
-thesis, "Architectural Styles and the Design of Network-based Software
-Architectures":http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm. While
-you can read through the thesis, REST in terms of Rails boils down to two main
-principles:
-
-* Using resource identifiers such as URLs to represent resources.
-* Transferring representations of the state of that resource between system components.
-
-For example, the following HTTP request:
-
-<tt>DELETE /photos/17</tt>
-
-would be understood to refer to a photo resource with the ID of 17, and to
-indicate a desired action - deleting that resource. REST is a natural style for
-the architecture of web applications, and Rails hooks into this shielding you
-from many of the RESTful complexities and browser quirks.
-
-If you'd like more details on REST as an architectural style, these resources
-are more approachable than Fielding's thesis:
-
-* "A Brief Introduction to REST":http://www.infoq.com/articles/rest-introduction by Stefan Tilkov
-* "An Introduction to REST":http://bitworking.org/news/373/An-Introduction-to-REST (video tutorial) by Joe Gregorio
-* "Representational State Transfer":http://en.wikipedia.org/wiki/Representational_State_Transfer article in Wikipedia
-* "How to GET a Cup of Coffee":http://www.infoq.com/articles/webber-rest-workflow by Jim Webber, Savas Parastatidis &
-Ian Robinson
-
-h3. Creating a New Rails Project
-
-If you follow this guide, you'll create a Rails project called <tt>blog</tt>, a
-(very) simple weblog. Before you can start building the application, you need to
-make sure that you have Rails itself installed.
-
-TIP: The examples below use # and $ to denote terminal prompts. If you are using Windows, your prompt will look something like c:\source_code>
-
-h4. Installing Rails
-
-In most cases, the easiest way to install Rails is to take advantage of RubyGems:
-
-<shell>
-Usually run this as the root user:
-# gem install rails
-</shell>
-
-TIP. If you're working on Windows, you can quickly install Ruby and Rails with
-"Rails Installer":http://railsinstaller.org.
-
-h4. Creating the Blog Application
-
-The best way to use this guide is to follow each step as it happens, no code or
-step needed to make this example application has been left out, so you can
-literally follow along step by step. If you need to see the completed code, you
-can download it from "Getting Started
-Code":https://github.com/mikel/getting-started-code.
-
-To begin, open a terminal, navigate to a folder where you have rights to create
-files, and type:
-
-<shell>
-$ rails new blog
-</shell>
-
-This will create a Rails application called Blog in a directory called blog.
-
-TIP: You can see all of the switches that the Rails application builder accepts
-by running
-<tt>rails new -h</tt>.
-
-After you create the blog application, switch to its folder to continue work
-directly in that application:
-
-<shell>
-$ cd blog
-</shell>
-
-In any case, Rails will create a folder in your working directory called
-<tt>blog</tt>. Open up that folder and explore its contents. Most of the work in
-this tutorial will happen in the <tt>app/</tt> folder, but here's a basic
-rundown on the function of each folder that Rails creates in a new application
-by default:
-
-|_.File/Folder|_.Purpose|
-|Gemfile|This file allows you to specify what gem dependencies are needed for your Rails application. See section on Bundler, below.|
-|README|This is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.|
-|Rakefile|This file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.|
-|app/|Contains the controllers, models, views and assets for your application. You'll focus on this folder for the remainder of this guide.|
-|config/|Configure your application's runtime rules, routes, database, and more.|
-|config.ru|Rack configuration for Rack based servers used to start the application.|
-|db/|Shows your current database schema, as well as the database migrations. You'll learn about migrations shortly.|
-|doc/|In-depth documentation for your application.|
-|lib/|Extended modules for your application (not covered in this guide).|
-|log/|Application log files.|
-|public/|The only folder seen to the world as-is. Contains the static files and compiled assets.|
-|script/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.|
-|test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html|
-|tmp/|Temporary files|
-|vendor/|A place for all third-party code. In a typical Rails application, this includes Ruby Gems, the Rails source code (if you install it into your project) and plugins containing additional prepackaged functionality.|
-
-h4. Configuring a Database
-
-Just about every Rails application will interact with a database. The database
-to use is specified in a configuration file, +config/database.yml+. If you open
-this file in a new Rails application, you'll see a default database
-configuration using SQLite3. The file contains sections for three different
-environments in which Rails can run by default:
-
-* The +development+ environment is used on your development computer as you interact manually with the application.
-* The +test+ environment is used to run automated tests.
-* The +production+ environment is used when you deploy your application for the world to use.
-
-h5. Configuring an SQLite3 Database
-
-Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is
-a lightweight serverless database application. While a busy production
-environment may overload SQLite, it works well for development and testing.
-Rails defaults to using an SQLite database when creating a new project, but you
-can always change it later.
-
-Here's the section of the default configuration file
-(<tt>config/database.yml</tt>) with connection information for the development
-environment:
-
-<yaml>
-development:
- adapter: sqlite3
- database: db/development.sqlite3
- pool: 5
- timeout: 5000
-</yaml>
-
-NOTE: In this guide we are using an SQLite3 database for data storage, because
-it is a zero configuration database that just works. Rails also supports MySQL
-and PostgreSQL "out of the box", and has plugins for many database systems. If
-you are using a database in a production environment Rails most likely has an
-adapter for it.
-
-h5. Configuring a MySQL Database
-
-If you choose to use MySQL instead of the shipped SQLite3 database, your
-+config/database.yml+ will look a little different. Here's the development
-section:
-
-<yaml>
-development:
- adapter: mysql2
- encoding: utf8
- database: blog_development
- pool: 5
- username: root
- password:
- socket: /tmp/mysql.sock
-</yaml>
-
-If your development computer's MySQL installation includes a root user with an
-empty password, this configuration should work for you. Otherwise, change the
-username and password in the +development+ section as appropriate.
-
-h5. Configuring a PostgreSQL Database
-
-If you choose to use PostgreSQL, your +config/database.yml+ will be customized
-to use PostgreSQL databases:
-
-<yaml>
-development:
- adapter: postgresql
- encoding: unicode
- database: blog_development
- pool: 5
- username: blog
- password:
-</yaml>
-
-h5. Configuring an SQLite3 Database for JRuby Platform
-
-If you choose to use SQLite3 and are using JRuby, your +config/database.yml+ will
-look a little different. Here's the development section:
-
-<yaml>
-development:
- adapter: jdbcsqlite3
- database: db/development.sqlite3
-</yaml>
-
-h5. Configuring a MySQL Database for JRuby Platform
-
-If you choose to use MySQL and are using JRuby, your +config/database.yml+ will look
-a little different. Here's the development section:
-
-<yaml>
-development:
- adapter: jdbcmysql
- database: blog_development
- username: root
- password:
-</yaml>
-
-h5. Configuring a PostgreSQL Database for JRuby Platform
-
-Finally if you choose to use PostgreSQL and are using JRuby, your
-+config/database.yml+ will look a little different. Here's the development
-section:
-
-<yaml>
-development:
- adapter: jdbcpostgresql
- encoding: unicode
- database: blog_development
- username: blog
- password:
-</yaml>
-
-Change the username and password in the +development+ section as appropriate.
-
-TIP: You don't have to update the database configurations manually. If you look at the
-options of the application generator, you will see that one of the options
-is named <tt>--database</tt>. This option allows you to choose an adapter from a
-list of the most used relational databases. You can even run the generator
-repeatedly: <tt>cd .. && rails new blog --database=mysql</tt>. When you confirm the overwriting
- of the +config/database.yml+ file, your application will be configured for MySQL
-instead of SQLite.
-
-h4. Creating the Database
-
-Now that you have your database configured, it's time to have Rails create an
-empty database for you. You can do this by running a rake command:
-
-<shell>
-$ rake db:create
-</shell>
-
-This will create your development and test SQLite3 databases inside the
-<tt>db/</tt> folder.
-
-TIP: Rake is a general-purpose command-runner that Rails uses for many things.
-You can see the list of available rake commands in your application by running
-+rake -T+.
-
-h3. Hello, Rails!
-
-One of the traditional places to start with a new language is by getting some
-text up on screen quickly. To do this, you need to get your Rails application
-server running.
-
-h4. Starting up the Web Server
-
-You actually have a functional Rails application already. To see it, you need to
-start a web server on your development machine. You can do this by running:
-
-<shell>
-$ rails server
-</shell>
-
-This will fire up an instance of the WEBrick web server by default (Rails can
-also use several other web servers). To see your application in action, open a
-browser window and navigate to "http://localhost:3000":http://localhost:3000.
-You should see Rails' default information page:
-
-!images/rails_welcome.png(Welcome Aboard screenshot)!
-
-TIP: To stop the web server, hit Ctrl+C in the terminal window where it's
-running. In development mode, Rails does not generally require you to stop the
-server; changes you make in files will be automatically picked up by the server.
-
-The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it
-makes sure that you have your software configured correctly enough to serve a
-page. You can also click on the _About your application’s environment_ link to
-see a summary of your application's environment.
-
-h4. Say "Hello", Rails
-
-To get Rails saying "Hello", you need to create at minimum a controller and a
-view. Fortunately, you can do that in a single command. Enter this command in
-your terminal:
-
-<shell>
-$ rails generate controller home index
-</shell>
-
-TIP: If you get a command not found error when running this command, you
-need to explicitly pass Rails +rails+ commands to Ruby: <tt>ruby
-\path\to\your\application\script\rails generate controller home index</tt>.
-
-Rails will create several files for you, including
-+app/views/home/index.html.erb+. This is the template that will be used to
-display the results of the +index+ action (method) in the +home+ controller.
-Open this file in your text editor and edit it to contain a single line of code:
-
-<code class="html">
-<h1>Hello, Rails!</h1>
-</code>
-
-h4. Setting the Application Home Page
-
-Now that we have made the controller and view, we need to tell Rails when we
-want "Hello Rails" to show up. In our case, we want it to show up when we
-navigate to the root URL of our site,
-"http://localhost:3000":http://localhost:3000, instead of the "Welcome Aboard"
-smoke test.
-
-The first step to doing this is to delete the default page from your
-application:
-
-<shell>
-$ rm public/index.html
-</shell>
-
-We need to do this as Rails will deliver any static file in the +public+
-directory in preference to any dynamic content we generate from the controllers.
-
-Now, you have to tell Rails where your actual home page is located. Open the
-file +config/routes.rb+ in your editor. This is your application's _routing
-file_ which holds entries in a special DSL (domain-specific language) that tells
-Rails how to connect incoming requests to controllers and actions. This file
-contains many sample routes on commented lines, and one of them actually shows
-you how to connect the root of your site to a specific controller and action.
-Find the line beginning with +root :to+, uncomment it and change it like the
-following:
-
-<ruby>
-Blog::Application.routes.draw do
-
- #...
- # You can have the root of your site routed with "root"
- # just remember to delete public/index.html.
- root :to => "home#index"
-</ruby>
-
-The +root :to => "home#index"+ tells Rails to map the root action to the home
-controller's index action.
-
-Now if you navigate to "http://localhost:3000":http://localhost:3000 in your
-browser, you'll see +Hello, Rails!+.
-
-NOTE. For more information about routing, refer to "Rails Routing from the
-Outside In":routing.html.
-
-h3. Getting Up and Running Quickly with Scaffolding
-
-Rails _scaffolding_ is a quick way to generate some of the major pieces of an
-application. If you want to create the models, views, and controllers for a new
-resource in a single operation, scaffolding is the tool for the job.
-
-h3. Creating a Resource
-
-In the case of the blog application, you can start by generating a scaffolded
-Post resource: this will represent a single blog posting. To do this, enter this
-command in your terminal:
-
-<shell>
-$ rails generate scaffold Post name:string title:string content:text
-</shell>
-
-The scaffold generator will build several files in your application, along with some
-folders, and edit <tt>config/routes.rb</tt>. Here's a quick overview of what it creates:
-
-|_.File |_.Purpose|
-|db/migrate/20100207214725_create_posts.rb |Migration to create the posts table in your database (your name will include a different timestamp)|
-|app/models/post.rb |The Post model|
-|test/fixtures/posts.yml |Dummy posts for use in testing|
-|app/controllers/posts_controller.rb |The Posts controller|
-|app/views/posts/index.html.erb |A view to display an index of all posts |
-|app/views/posts/edit.html.erb |A view to edit an existing post|
-|app/views/posts/show.html.erb |A view to display a single post|
-|app/views/posts/new.html.erb |A view to create a new post|
-|app/views/posts/_form.html.erb |A partial to control the overall look and feel of the form used in edit and new views|
-|app/helpers/posts_helper.rb |Helper functions to be used from the post views|
-|app/assets/stylesheets/scaffolds.css.scss |Cascading style sheet to make the scaffolded views look better|
-|app/assets/stylesheets/posts.css.scss |Cascading style sheet for the posts controller|
-|app/assets/javascripts/posts.js.coffee |CoffeeScript for the posts controller|
-|test/unit/post_test.rb |Unit testing harness for the posts model|
-|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|
-
-NOTE. While scaffolding will get you up and running quickly, the code it
-generates is unlikely to be a perfect fit for your application. You'll most
-probably want to customize the generated code. Many experienced Rails developers
-avoid scaffolding entirely, preferring to write all or most of their source code
-from scratch. Rails, however, makes it really simple to customize templates for
-generated models, controllers, views and other source files. You'll find more
-information in the "Creating and Customizing Rails Generators &
-Templates":generators.html guide.
-
-h4. Running a Migration
-
-One of the products of the +rails generate scaffold+ command is a _database
-migration_. Migrations are Ruby classes that are designed to make it simple to
-create and modify database tables. Rails uses rake commands to run migrations,
-and it's possible to undo a migration after it's been applied to your database.
-Migration filenames include a timestamp to ensure that they're processed in the
-order that they were created.
-
-If you look in the +db/migrate/20100207214725_create_posts.rb+ file (remember,
-yours will have a slightly different name), here's what you'll find:
-
-<ruby>
-class CreatePosts < ActiveRecord::Migration
- def change
- create_table :posts do |t|
- t.string :name
- t.string :title
- t.text :content
-
- t.timestamps
- end
- end
-end
-</ruby>
-
-The above migration creates a method named +change+ which will be called when you
-run this migration. The action defined in that method is also reversible, which
-means Rails knows how to reverse the change made by this migration, in case you
-want to reverse it at later date. By default, when you run this migration it
-creates a +posts+ table with two string columns and a text column. It also
-creates two timestamp fields to track record creation and updating. More
-information about Rails migrations can be found in the "Rails Database
-Migrations":migrations.html guide.
-
-At this point, you can use a rake command to run the migration:
-
-<shell>
-$ rake db:migrate
-</shell>
-
-Rails will execute this migration command and tell you it created the Posts
-table.
-
-<shell>
-== CreatePosts: migrating ====================================================
--- create_table(:posts)
- -> 0.0019s
-== CreatePosts: migrated (0.0020s) ===========================================
-</shell>
-
-NOTE. Because by default you're working in the development environment, this
-command will apply to the database defined in the +development+ section of your
-+config/database.yml+ file. If you would like to execute migrations in another
-environment, for instance in production, you must explicitly pass it when
-invoking the command: <tt>rake db:migrate RAILS_ENV=production</tt>.
-
-h4. Adding a Link
-
-To hook the posts up to the home page you've already created, you can add a link
-to the home page. Open +app/views/home/index.html.erb+ and modify it as follows:
-
-<ruby>
-<h1>Hello, Rails!</h1>
-<%= link_to "My Blog", posts_path %>
-</ruby>
-
-The +link_to+ method is one of Rails' built-in view helpers. It creates a
-hyperlink based on text to display and where to go - in this case, to the path
-for posts.
-
-h4. Working with Posts in the Browser
-
-Now you're ready to start working with posts. To do that, navigate to
-"http://localhost:3000":http://localhost:3000/ and then click the "My Blog"
-link:
-
-!images/posts_index.png(Posts Index screenshot)!
-
-This is the result of Rails rendering the +index+ view of your posts. There
-aren't currently any posts in the database, but if you click the +New Post+ link
-you can create one. After that, you'll find that you can edit posts, look at
-their details, or destroy them. All of the logic and HTML to handle this was
-built by the single +rails generate scaffold+ command.
-
-TIP: In development mode (which is what you're working in by default), Rails
-reloads your application with every browser request, so there's no need to stop
-and restart the web server.
-
-Congratulations, you're riding the rails! Now it's time to see how it all works.
-
-h4. The Model
-
-The model file, +app/models/post.rb+ is about as simple as it can get:
-
-<ruby>
-class Post < ActiveRecord::Base
-end
-</ruby>
-
-There isn't much to this file - but note that the +Post+ class inherits from
-+ActiveRecord::Base+. Active Record supplies a great deal of functionality to
-your Rails models for free, including basic database CRUD (Create, Read, Update,
-Destroy) operations, data validation, as well as sophisticated search support
-and the ability to relate multiple models to one another.
-
-h4. Adding Some Validation
-
-Rails includes methods to help you validate the data that you send to models.
-Open the +app/models/post.rb+ file and edit it:
-
-<ruby>
-class Post < ActiveRecord::Base
- validates :name, :presence => true
- validates :title, :presence => true,
- :length => { :minimum => 5 }
-end
-</ruby>
-
-These changes will ensure that all posts have a name and a title, and that the
-title is at least five characters long. Rails can validate a variety of
-conditions in a model, including the presence or uniqueness of columns, their
-format, and the existence of associated objects.
-
-h4. Using the Console
-
-To see your validations in action, you can use the console. The console is a
-command-line tool that lets you execute Ruby code in the context of your
-application:
-
-<shell>
-$ rails console
-</shell>
-
-TIP: The default console will make changes to your database. You can instead
-open a console that will roll back any changes you make by using <tt>rails console
---sandbox</tt>.
-
-After the console loads, you can use it to work with your application's models:
-
-<shell>
->> p = Post.new(:content => "A new post")
-=> #<Post id: nil, name: nil, title: nil,
- content: "A new post", created_at: nil,
- updated_at: nil>
->> p.save
-=> false
->> p.errors
-=> #<OrderedHash { :title=>["can't be blank",
- "is too short (minimum is 5 characters)"],
- :name=>["can't be blank"] }>
-</shell>
-
-This code shows creating a new +Post+ instance, attempting to save it and
-getting +false+ for a return value (indicating that the save failed), and
-inspecting the +errors+ of the post.
-
-When you're finished, type +exit+ and hit +return+ to exit the console.
-
-TIP: Unlike the development web server, the console does not automatically load
-your code afresh for each line. If you make changes to your models while the
-console is open, type +reload!+ at the console prompt to load them.
-
-h4. Listing All Posts
-
-The easiest place to start looking at functionality is with the code that lists
-all posts. Open the file +app/controllers/posts_controller.rb+ and look at the
-+index+ action:
-
-<ruby>
-def index
- @posts = Post.all
-
- respond_to do |format|
- format.html # index.html.erb
- format.json { render :json => @posts }
- end
-end
-</ruby>
-
-+Post.all+ calls the +Post+ model to return all of the posts currently in the
-database. The result of this call is an array of posts that we store in an
-instance variable called +@posts+.
-
-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 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>
-
-<table>
- <tr>
- <th>Name</th>
- <th>Title</th>
- <th>Content</th>
- <th></th>
- <th></th>
- <th></th>
- </tr>
-
-<% @posts.each do |post| %>
- <tr>
- <td><%= post.name %></td>
- <td><%= post.title %></td>
- <td><%= post.content %></td>
- <td><%= link_to 'Show', post %></td>
- <td><%= link_to 'Edit', edit_post_path(post) %></td>
- <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?',
- :method => :delete %></td>
- </tr>
-<% end %>
-</table>
-
-<br />
-
-<%= link_to 'New post', new_post_path %>
-</erb>
-
-This view iterates over the contents of the +@posts+ array to display content
-and links. A few things to note in the view:
-
-* +link_to+ builds a hyperlink to a particular destination
-* +edit_post_path+ and +new_post_path+ are helpers that Rails provides as part of RESTful routing. You'll see a variety of these helpers for the different actions that the controller includes.
-
-NOTE. In previous versions of Rails, you had to use +&lt;%=h post.name %&gt;+ so
-that any HTML would be escaped before being inserted into the page. In Rails
-3.0, this is now the default. To get unescaped HTML, you now use +&lt;%= raw
-post.name %&gt;+.
-
-TIP: For more details on the rendering process, see "Layouts and Rendering in
-Rails":layouts_and_rendering.html.
-
-h4. Customizing the Layout
-
-The view is only part of the story of how HTML is displayed in your web browser.
-Rails also has the concept of +layouts+, which are containers for views. When
-Rails renders a view to the browser, it does so by putting the view's HTML into
-a layout's HTML. In previous versions of Rails, the +rails generate scaffold+
-command would automatically create a controller specific layout, like
-+app/views/layouts/posts.html.erb+, for the posts controller. However this has
-been changed in Rails 3.0. An application specific +layout+ is used for all the
-controllers and can be found in +app/views/layouts/application.html.erb+. Open
-this layout in your editor and modify the +body+ tag:
-
-<erb>
-<!DOCTYPE html>
-<html>
-<head>
- <title>Blog</title>
- <%= stylesheet_link_tag "application" %>
- <%= javascript_include_tag "application" %>
- <%= csrf_meta_tags %>
-</head>
-<body style="background: #EEEEEE;">
-
-<%= yield %>
-
-</body>
-</html>
-</erb>
-
-Now when you refresh the +/posts+ page, you'll see a gray background to the
-page. This same gray background will be used throughout all the views for posts.
-
-h4. Creating New Posts
-
-Creating a new post involves two actions. The first is the +new+ action, which
-instantiates an empty +Post+ object:
-
-<ruby>
-def new
- @post = Post.new
-
- respond_to do |format|
- format.html # new.html.erb
- format.json { render :json => @post }
- end
-end
-</ruby>
-
-The +new.html.erb+ view displays this empty Post to the user:
-
-<erb>
-<h1>New post</h1>
-
-<%= render 'form' %>
-
-<%= link_to 'Back', posts_path %>
-</erb>
-
-The +&lt;%= render 'form' %&gt;+ line is our first introduction to _partials_ in
-Rails. A partial is a snippet of HTML and Ruby code that can be reused in
-multiple locations. In this case, the form used to make a new post is basically
-identical to the form used to edit a post, both having text fields for the name and
-title, a text area for the content, and a button to create the new post or to update
-the existing one.
-
-If you take a look at +views/posts/_form.html.erb+ file, you will see the
-following:
-
-<erb>
-<%= form_for(@post) do |f| %>
- <% if @post.errors.any? %>
- <div id="errorExplanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited
- this post from being saved:</h2>
- <ul>
- <% @post.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
-
- <div class="field">
- <%= f.label :name %><br />
- <%= f.text_field :name %>
- </div>
- <div class="field">
- <%= f.label :title %><br />
- <%= f.text_field :title %>
- </div>
- <div class="field">
- <%= f.label :content %><br />
- <%= f.text_area :content %>
- </div>
- <div class="actions">
- <%= f.submit %>
- </div>
-<% end %>
-</erb>
-
-This partial receives all the instance variables defined in the calling view
-file. In this case, the controller assigned the new +Post+ object to +@post+,
-which will thus be available in both the view and the partial as +@post+.
-
-For more information on partials, refer to the "Layouts and Rendering in
-Rails":layouts_and_rendering.html#using-partials guide.
-
-The +form_for+ block is used to create an HTML form. Within this block, you have
-access to methods to build various controls on the form. For example,
-+f.text_field :name+ tells Rails to create a text input on the form and to hook
-it up to the +name+ attribute of the instance being displayed. You can only use
-these methods with attributes of the model that the form is based on (in this
-case +name+, +title+, and +content+). Rails uses +form_for+ in preference to
-having you write raw HTML because the code is more succinct, and because it
-explicitly ties the form to a particular model instance.
-
-The +form_for+ block is also smart enough to work out if you are doing a _New
-Post_ or an _Edit Post_ action, and will set the form +action+ tags and submit
-button names appropriately in the HTML output.
-
-TIP: If you need to create an HTML form that displays arbitrary fields, not tied
-to a model, you should use the +form_tag+ method, which provides shortcuts for
-building forms that are not necessarily tied to a model instance.
-
-When the user clicks the +Create Post+ button on this form, the browser will
-send information back to the +create+ action of the controller (Rails knows to
-call the +create+ action because the form is sent with an HTTP POST request;
-that's one of the conventions that were mentioned earlier):
-
-<ruby>
-def create
- @post = Post.new(params[:post])
-
- respond_to do |format|
- if @post.save
- format.html { redirect_to(@post,
- :notice => 'Post was successfully created.') }
- format.json { render :json => @post,
- :status => :created, :location => @post }
- else
- format.html { render :action => "new" }
- format.json { render :json => @post.errors,
- :status => :unprocessable_entity }
- end
- end
-end
-</ruby>
-
-The +create+ action instantiates a new Post object from the data supplied by the
-user on the form, which Rails makes available in the +params+ hash. After
-successfully saving the new post, +create+ returns the appropriate format that
-the user has requested (HTML in our case). It then redirects the user to the
-resulting post +show+ action and sets a notice to the user that the Post was
-successfully created.
-
-If the post was not successfully saved, due to a validation error, then the
-controller returns the user back to the +new+ action with any error messages so
-that the user has the chance to fix the error and try again.
-
-The "Post was successfully created." message is stored in the Rails
-+flash+ hash (usually just called _the flash_), so that messages can be carried
-over to another action, providing the user with useful information on the status
-of their request. In the case of +create+, the user never actually sees any page
-rendered during the post creation process, because it immediately redirects to
-the new +Post+ as soon as Rails saves the record. The Flash carries over a message to
-the next action, so that when the user is redirected back to the +show+ action,
-they are presented with a message saying "Post was successfully created."
-
-h4. Showing an Individual Post
-
-When you click the +show+ link for a post on the index page, it will bring you
-to a URL like +http://localhost:3000/posts/1+. Rails interprets this as a call
-to the +show+ action for the resource, and passes in +1+ as the +:id+ parameter.
-Here's the +show+ action:
-
-<ruby>
-def show
- @post = Post.find(params[:id])
-
- respond_to do |format|
- format.html # show.html.erb
- format.json { render :json => @post }
- end
-end
-</ruby>
-
-The +show+ action uses +Post.find+ to search for a single record in the database
-by its id value. After finding the record, Rails displays it by using
-+show.html.erb+:
-
-<erb>
-<p class="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-
-<%= link_to 'Edit', edit_post_path(@post) %> |
-<%= link_to 'Back', posts_path %>
-</erb>
-
-h4. Editing Posts
-
-Like creating a new post, editing a post is a two-part process. The first step
-is a request to +edit_post_path(@post)+ with a particular post. This calls the
-+edit+ action in the controller:
-
-<ruby>
-def edit
- @post = Post.find(params[:id])
-end
-</ruby>
-
-After finding the requested post, Rails uses the +edit.html.erb+ view to display
-it:
-
-<erb>
-<h1>Editing post</h1>
-
-<%= render 'form' %>
-
-<%= link_to 'Show', @post %> |
-<%= link_to 'Back', posts_path %>
-</erb>
-
-Again, as with the +new+ action, the +edit+ action is using the +form+ partial.
-This time, however, the form will do a PUT action to the +PostsController+ and the
-submit button will display "Update Post".
-
-Submitting the form created by this view will invoke the +update+ action within
-the controller:
-
-<ruby>
-def update
- @post = Post.find(params[:id])
-
- respond_to do |format|
- if @post.update_attributes(params[:post])
- format.html { redirect_to(@post,
- :notice => 'Post was successfully updated.') }
- format.json { render :json => {}, :status => :ok }
- else
- format.html { render :action => "edit" }
- format.json { render :json => @post.errors,
- :status => :unprocessable_entity }
- end
- end
-end
-</ruby>
-
-In the +update+ action, Rails first uses the +:id+ parameter passed back from
-the edit view to locate the database record that's being edited. The
-+update_attributes+ call then takes the +post+ parameter (a hash) from the request
-and applies it to this record. If all goes well, the user is redirected to the
-post's +show+ action. If there are any problems, it redirects back to the +edit+ action to
-correct them.
-
-h4. Destroying a Post
-
-Finally, clicking one of the +destroy+ links sends the associated id to the
-+destroy+ action:
-
-<ruby>
-def destroy
- @post = Post.find(params[:id])
- @post.destroy
-
- respond_to do |format|
- format.html { redirect_to posts_url }
- format.json { head :ok }
- end
-end
-</ruby>
-
-The +destroy+ method of an Active Record model instance removes the
-corresponding record from the database. After that's done, there isn't any
-record to display, so Rails redirects the user's browser to the index action of
-the controller.
-
-h3. Adding a Second Model
-
-Now that you've seen how a model built with scaffolding looks like, it's time to
-add a second model to the application. The second model will handle comments on
-blog posts.
-
-h4. Generating a Model
-
-Models in Rails use a singular name, and their corresponding database tables use
-a plural name. For the model to hold comments, the convention is to use the name
-+Comment+. Even if you don't want to use the entire apparatus set up by
-scaffolding, most Rails developers still use generators to make things like
-models and controllers. To create the new model, run this command in your
-terminal:
-
-<shell>
-$ rails generate model Comment commenter:string body:text post:references
-</shell>
-
-This command will generate four files:
-
-* +app/models/comment.rb+ - The model.
-* +db/migrate/20100207235629_create_comments.rb+ - The migration.
-* +test/unit/comment_test.rb+ and +test/fixtures/comments.yml+ - The test harness.
-
-First, take a look at +comment.rb+:
-
-<ruby>
-class Comment < ActiveRecord::Base
- belongs_to :post
-end
-</ruby>
-
-This is very similar to the +post.rb+ model that you saw earlier. The difference
-is the line +belongs_to :post+, which sets up an Active Record _association_.
-You'll learn a little about associations in the next section of this guide.
-
-In addition to the model, Rails has also made a migration to create the
-corresponding database table:
-
-<ruby>
-class CreateComments < ActiveRecord::Migration
- def change
- create_table :comments do |t|
- t.string :commenter
- t.text :body
- t.references :post
-
- t.timestamps
- end
-
- add_index :comments, :post_id
- end
-end
-</ruby>
-
-The +t.references+ line sets up a foreign key column for the association between
-the two models. And the +add_index+ line sets up an index for this association
-column. Go ahead and run the migration:
-
-<shell>
-$ rake db:migrate
-</shell>
-
-Rails is smart enough to only execute the migrations that have not already been
-run against the current database, so in this case you will just see:
-
-<shell>
-== CreateComments: migrating =================================================
--- create_table(:comments)
- -> 0.0017s
-== CreateComments: migrated (0.0018s) ========================================
-</shell>
-
-h4. Associating Models
-
-Active Record associations let you easily declare the relationship between two
-models. In the case of comments and posts, you could write out the relationships
-this way:
-
-* Each comment belongs to one post.
-* One post can have many comments.
-
-In fact, this is very close to the syntax that Rails uses to declare this
-association. You've already seen the line of code inside the Comment model that
-makes each comment belong to a Post:
-
-<ruby>
-class Comment < ActiveRecord::Base
- belongs_to :post
-end
-</ruby>
-
-You'll need to edit the +post.rb+ file to add the other side of the association:
-
-<ruby>
-class Post < ActiveRecord::Base
- validates :name, :presence => true
- validates :title, :presence => true,
- :length => { :minimum => 5 }
-
- has_many :comments
-end
-</ruby>
-
-These two declarations enable a good bit of automatic behavior. For example, if
-you have an instance variable +@post+ containing a post, you can retrieve all
-the comments belonging to that post as an array using +@post.comments+.
-
-TIP: For more information on Active Record associations, see the "Active Record
-Associations":association_basics.html guide.
-
-h4. Adding a Route for Comments
-
-As with the +home+ controller, we will need to add a route so that Rails knows
-where we would like to navigate to see +comments+. Open up the
-+config/routes.rb+ file again. Near the top, you will see the entry for +posts+
-that was added automatically by the scaffold generator: <tt>resources
-:posts</tt>. Edit it as follows:
-
-<ruby>
-resources :posts do
- resources :comments
-end
-</ruby>
-
-This creates +comments+ as a _nested resource_ within +posts+. This is another
-part of capturing the hierarchical relationship that exists between posts and
-comments.
-
-TIP: For more information on routing, see the "Rails Routing from the Outside
-In":routing.html guide.
-
-h4. Generating a Controller
-
-With the model in hand, you can turn your attention to creating a matching
-controller. Again, there's a generator for this:
-
-<shell>
-$ rails generate controller Comments
-</shell>
-
-This creates six files and one empty directory:
-
-* +app/controllers/comments_controller.rb+ - The controller.
-* +app/helpers/comments_helper.rb+ - A view helper file.
-* +test/functional/comments_controller_test.rb+ - The functional tests for the controller.
-* +test/unit/helpers/comments_helper_test.rb+ - The unit tests for the helper.
-* +app/views/comments/+ - Views of the controller are stored here.
-* +app/assets/stylesheets/comment.css.scss+ - Cascading style sheet for the controller.
-* +app/assets/javascripts/comment.js.coffee+ - CoffeeScript for the controller.
-
-Like with any blog, our readers will create their comments directly after
-reading the post, and once they have added their comment, will be sent back to
-the post show page to see their comment now listed. Due to this, our
-+CommentsController+ is there to provide a method to create comments and delete
-spam comments when they arrive.
-
-So first, we'll wire up the Post show template
-(+/app/views/posts/show.html.erb+) to let us make a new comment:
-
-<erb>
-<p class="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-<h2>Add a comment:</h2>
-<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
- <%= f.label :commenter %><br />
- <%= f.text_field :commenter %>
- </div>
- <div class="field">
- <%= f.label :body %><br />
- <%= f.text_area :body %>
- </div>
- <div class="actions">
- <%= f.submit %>
- </div>
-<% end %>
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
-</erb>
-
-This adds a form on the +Post+ show page that creates a new comment by
-calling the +CommentsController+ +create+ action. Let's wire that up:
-
-<ruby>
-class CommentsController < ApplicationController
- def create
- @post = Post.find(params[:post_id])
- @comment = @post.comments.create(params[:comment])
- redirect_to post_path(@post)
- end
-end
-</ruby>
-
-You'll see a bit more complexity here than you did in the controller for posts.
-That's a side-effect of the nesting that you've set up. Each request for a
-comment has to keep track of the post to which the comment is attached, thus the
-initial call to the +find+ method of the +Post+ model to get the post in question.
-
-In addition, the code takes advantage of some of the methods available for an
-association. We use the +create+ method on +@post.comments+ to create and save
-the comment. This will automatically link the comment so that it belongs to that
-particular post.
-
-Once we have made the new comment, we send the user back to the original post
-using the +post_path(@post)+ helper. As we have already seen, this calls the
-+show+ action of the +PostsController+ which in turn renders the +show.html.erb+
-template. This is where we want the comment to show, so let's add that to the
-+app/views/posts/show.html.erb+.
-
-<erb>
-<p class="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-<h2>Comments</h2>
-<% @post.comments.each do |comment| %>
- <p>
- <b>Commenter:</b>
- <%= comment.commenter %>
- </p>
-
- <p>
- <b>Comment:</b>
- <%= comment.body %>
- </p>
-<% end %>
-
-<h2>Add a comment:</h2>
-<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
- <%= f.label :commenter %><br />
- <%= f.text_field :commenter %>
- </div>
- <div class="field">
- <%= f.label :body %><br />
- <%= f.text_area :body %>
- </div>
- <div class="actions">
- <%= f.submit %>
- </div>
-<% end %>
-
-<br />
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
-</erb>
-
-Now you can add posts and comments to your blog and have them show up in the
-right places.
-
-h3. Refactoring
-
-Now that we have posts and comments working, take a look at the
-+app/views/posts/show.html.erb+ template. It is getting long and awkward. We can
-use partials to clean it up.
-
-h4. Rendering Partial Collections
-
-First we will make a comment partial to extract showing all the comments for the
-post. Create the file +app/views/comments/_comment.html.erb+ and put the
-following into it:
-
-<erb>
-<p>
- <b>Commenter:</b>
- <%= comment.commenter %>
-</p>
-
-<p>
- <b>Comment:</b>
- <%= comment.body %>
-</p>
-</erb>
-
-Then you can change +app/views/posts/show.html.erb+ to look like the
-following:
-
-<erb>
-<p class="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
- <%= f.label :commenter %><br />
- <%= f.text_field :commenter %>
- </div>
- <div class="field">
- <%= f.label :body %><br />
- <%= f.text_area :body %>
- </div>
- <div class="actions">
- <%= f.submit %>
- </div>
-<% end %>
-
-<br />
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
-</erb>
-
-This will now render the partial in +app/views/comments/_comment.html.erb+ once
-for each comment that is in the +@post.comments+ collection. As the +render+
-method iterates over the <tt>@post.comments</tt> collection, it assigns each
-comment to a local variable named the same as the partial, in this case
-+comment+ which is then available in the partial for us to show.
-
-h4. Rendering a Partial Form
-
-Let us also move that new comment section out to its own partial. Again, you
-create a file +app/views/comments/_form.html.erb+ containing:
-
-<erb>
-<%= form_for([@post, @post.comments.build]) do |f| %>
- <div class="field">
- <%= f.label :commenter %><br />
- <%= f.text_field :commenter %>
- </div>
- <div class="field">
- <%= f.label :body %><br />
- <%= f.text_area :body %>
- </div>
- <div class="actions">
- <%= f.submit %>
- </div>
-<% end %>
-</erb>
-
-Then you make the +app/views/posts/show.html.erb+ look like the following:
-
-<erb>
-<p class="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= render "comments/form" %>
-
-<br />
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
-</erb>
-
-The second render just defines the partial template we want to render,
-<tt>comments/form</tt>. Rails is smart enough to spot the forward slash in that
-string and realize that you want to render the <tt>_form.html.erb</tt> file in
-the <tt>app/views/comments</tt> directory.
-
-The +@post+ object is available to any partials rendered in the view because we
-defined it as an instance variable.
-
-h3. Deleting Comments
-
-Another important feature of a blog is being able to delete spam comments. To do
-this, we need to implement a link of some sort in the view and a +DELETE+ action
-in the +CommentsController+.
-
-So first, let's add the delete link in the
-+app/views/comments/_comment.html.erb+ partial:
-
-<erb>
-<p>
- <b>Commenter:</b>
- <%= comment.commenter %>
-</p>
-
-<p>
- <b>Comment:</b>
- <%= comment.body %>
-</p>
-
-<p>
- <%= link_to 'Destroy Comment', [comment.post, comment],
- :confirm => 'Are you sure?',
- :method => :delete %>
-</p>
-</erb>
-
-Clicking this new "Destroy Comment" link will fire off a <tt>DELETE
-/posts/:id/comments/:id</tt> to our +CommentsController+, which can then use
-this to find the comment we want to delete, so let's add a destroy action to our
-controller:
-
-<ruby>
-class CommentsController < ApplicationController
-
- def create
- @post = Post.find(params[:post_id])
- @comment = @post.comments.create(params[:comment])
- redirect_to post_path(@post)
- end
-
- def destroy
- @post = Post.find(params[:post_id])
- @comment = @post.comments.find(params[:id])
- @comment.destroy
- redirect_to post_path(@post)
- end
-
-end
-</ruby>
-
-The +destroy+ action will find the post we are looking at, locate the comment
-within the <tt>@post.comments</tt> collection, and then remove it from the
-database and send us back to the show action for the post.
-
-
-h4. Deleting Associated Objects
-
-If you delete a post then its associated comments will also need to be deleted.
-Otherwise they would simply occupy space in the database. Rails allows you to
-use the +dependent+ option of an association to achieve this. Modify the Post
-model, +app/models/post.rb+, as follows:
-
-<ruby>
-class Post < ActiveRecord::Base
- validates :name, :presence => true
- validates :title, :presence => true,
- :length => { :minimum => 5 }
- has_many :comments, :dependent => :destroy
-end
-</ruby>
-
-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.
-
-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 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
-
- http_basic_authenticate_with :name => "dhh", :password => "secret", :except => [:index, :show]
-
- # GET /posts
- # GET /posts.json
- def index
- @posts = Post.all
- respond_to do |format|
-# snipped for brevity
-</ruby>
-
-We also only want to allow authenticated users to delete comments, so in the
-+CommentsController+ we write:
-
-<ruby>
-class CommentsController < ApplicationController
-
- http_basic_authenticate_with :name => "dhh", :password => "secret", :only => :destroy
-
- def create
- @post = Post.find(params[:post_id])
-# snipped for brevity
-</ruby>
-
-Now if you try to create a new post, you will be greeted with a basic HTTP
-Authentication challenge
-
-!images/challenge.png(Basic HTTP Authentication Challenge)!
-
-h3. Building a Multi-Model Form
-
-Another feature of your average blog is the ability to tag posts. To implement
-this feature your application needs to interact with more than one model on a
-single form. Rails offers support for nested forms.
-
-To demonstrate this, we will add support for giving each post multiple tags,
-right in the form where you create the post. First, create a new model to hold
-the tags:
-
-<shell>
-$ rails generate model tag name:string post:references
-</shell>
-
-Again, run the migration to create the database table:
-
-<shell>
-$ rake db:migrate
-</shell>
-
-Next, edit the +post.rb+ file to create the other side of the association, and
-to tell Rails (via the +accepts_nested_attributes_for+ macro) that you intend to
-edit tags via posts:
-
-<ruby>
-class Post < ActiveRecord::Base
- validates :name, :presence => true
- validates :title, :presence => true,
- :length => { :minimum => 5 }
-
- has_many :comments, :dependent => :destroy
- has_many :tags
-
- accepts_nested_attributes_for :tags, :allow_destroy => :true,
- :reject_if => proc { |attrs| attrs.all? { |k, v| v.blank? } }
-end
-</ruby>
-
-The +:allow_destroy+ option on the nested attribute declaration tells Rails to
-display a "remove" checkbox on the view that you'll build shortly. The
-+:reject_if+ option prevents saving new tags that do not have any attributes
-filled in.
-
-We will modify +views/posts/_form.html.erb+ to render a partial to make a tag:
-
-<erb>
-<% @post.tags.build %>
-<%= form_for(@post) do |post_form| %>
- <% if @post.errors.any? %>
- <div id="errorExplanation">
- <h2><%= pluralize(@post.errors.count, "error") %> prohibited this post from being saved:</h2>
- <ul>
- <% @post.errors.full_messages.each do |msg| %>
- <li><%= msg %></li>
- <% end %>
- </ul>
- </div>
- <% end %>
-
- <div class="field">
- <%= post_form.label :name %><br />
- <%= post_form.text_field :name %>
- </div>
- <div class="field">
- <%= post_form.label :title %><br />
- <%= post_form.text_field :title %>
- </div>
- <div class="field">
- <%= post_form.label :content %><br />
- <%= post_form.text_area :content %>
- </div>
- <h2>Tags</h2>
- <%= render :partial => 'tags/form',
- :locals => {:form => post_form} %>
- <div class="actions">
- <%= post_form.submit %>
- </div>
-<% end %>
-</erb>
-
-Note that we have changed the +f+ in +form_for(@post) do |f|+ to +post_form+ to
-make it easier to understand what is going on.
-
-This example shows another option of the render helper, being able to pass in
-local variables, in this case, we want the local variable +form+ in the partial
-to refer to the +post_form+ object.
-
-We also add a <tt>@post.tags.build</tt> at the top of this form. This is to make
-sure there is a new tag ready to have its name filled in by the user. If you do
-not build the new tag, then the form will not appear as there is no new Tag
-object ready to create.
-
-Now create the folder <tt>app/views/tags</tt> and make a file in there called
-<tt>_form.html.erb</tt> which contains the form for the tag:
-
-<erb>
-<%= form.fields_for :tags do |tag_form| %>
- <div class="field">
- <%= tag_form.label :name, 'Tag:' %>
- <%= tag_form.text_field :name %>
- </div>
- <% unless tag_form.object.nil? || tag_form.object.new_record? %>
- <div class="field">
- <%= tag_form.label :_destroy, 'Remove:' %>
- <%= tag_form.check_box :_destroy %>
- </div>
- <% end %>
-<% end %>
-</erb>
-
-Finally, we will edit the <tt>app/views/posts/show.html.erb</tt> template to
-show our tags.
-
-<erb>
-<p class="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-<p>
- <b>Tags:</b>
- <%= @post.tags.map { |t| t.name }.join(", ") %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= render "comments/form" %>
-
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
-</erb>
-
-With these changes in place, you'll find that you can edit a post and its tags
-directly on the same view.
-
-However, that method call <tt>@post.tags.map { |t| t.name }.join(", ")</tt> is
-awkward, we could handle this by making a helper method.
-
-h3. View Helpers
-
-View Helpers live in <tt>app/helpers</tt> and provide small snippets of reusable
-code for views. In our case, we want a method that strings a bunch of objects
-together using their name attribute and joining them with a comma. As this is
-for the Post show template, we put it in the PostsHelper.
-
-Open up <tt>app/helpers/posts_helper.rb</tt> and add the following:
-
-<erb>
-module PostsHelper
- def join_tags(post)
- post.tags.map { |t| t.name }.join(", ")
- end
-end
-</erb>
-
-Now you can edit the view in <tt>app/views/posts/show.html.erb</tt> to look like
-this:
-
-<erb>
-<p class="notice"><%= notice %></p>
-
-<p>
- <b>Name:</b>
- <%= @post.name %>
-</p>
-
-<p>
- <b>Title:</b>
- <%= @post.title %>
-</p>
-
-<p>
- <b>Content:</b>
- <%= @post.content %>
-</p>
-
-<p>
- <b>Tags:</b>
- <%= join_tags(@post) %>
-</p>
-
-<h2>Comments</h2>
-<%= render @post.comments %>
-
-<h2>Add a comment:</h2>
-<%= render "comments/form" %>
-
-
-<%= link_to 'Edit Post', edit_post_path(@post) %> |
-<%= link_to 'Back to Posts', posts_path %> |
-</erb>
-
-h3. What's Next?
-
-Now that you've seen your first Rails application, you should feel free to
-update it and experiment on your own. But you don't have to do everything
-without help. As you need assistance getting up and running with Rails, feel
-free to consult these support resources:
-
-* The "Ruby on Rails guides":index.html
-* The "Ruby on Rails Tutorial":http://railstutorial.org/book
-* The "Ruby on Rails mailing list":http://groups.google.com/group/rubyonrails-talk
-* The "#rubyonrails":irc://irc.freenode.net/#rubyonrails channel on irc.freenode.net
-* The "Rails Wiki":http://wiki.rubyonrails.org/
-
-Rails also comes with built-in help that you can generate using the rake command-line utility:
-
-* Running +rake doc:guides+ will put a full copy of the Rails Guides in the +doc/guides+ folder of your application. Open +doc/guides/index.html+ in your web browser to explore the Guides.
-* Running +rake doc:rails+ will put a full copy of the API documentation for Rails in the +doc/api+ folder of your application. Open +doc/api/index.html+ in your web browser to explore the API documentation.
-
-h3. Configuration Gotchas
-
-The easiest way to work with Rails is to store all external data as UTF-8. If
-you don't, Ruby libraries and Rails will often be able to convert your native
-data into UTF-8, but this doesn't always work reliably, so you're better off
-ensuring that all external data is UTF-8.
-
-If you have made a mistake in this area, the most common symptom is a black
-diamond with a question mark inside appearing in the browser. Another common
-symptom is characters like "ü" appearing instead of "ü". Rails takes a number
-of internal steps to mitigate common causes of these problems that can be
-automatically detected and corrected. However, if you have external data that is
-not stored as UTF-8, it can occasionally result in these kinds of issues that
-cannot be automatically detected by Rails and corrected.
-
-Two very common sources of data that are not UTF-8:
-* Your text editor: Most text editors (such as Textmate), default to saving files as
- UTF-8. If your text editor does not, this can result in special characters that you
- enter in your templates (such as é) to appear as a diamond with a question mark inside
- in the browser. This also applies to your I18N translation files.
- Most editors that do not already default to UTF-8 (such as some versions of
- Dreamweaver) offer a way to change the default to UTF-8. Do so.
-* Your database. Rails defaults to converting data from your database into UTF-8 at
- the boundary. However, if your database is not using UTF-8 internally, it may not
- be able to store all characters that your users enter. For instance, if your database
- is using Latin-1 internally, and your user enters a Russian, Hebrew, or Japanese
- character, the data will be lost forever once it enters the database. If possible,
- use UTF-8 as the internal storage of your database.
diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb
deleted file mode 100644
index c9a8c4fa5c..0000000000
--- a/railties/guides/source/index.html.erb
+++ /dev/null
@@ -1,191 +0,0 @@
-<% content_for :page_title do %>
-Ruby on Rails Guides
-<% end %>
-
-<% content_for :header_section do %>
-<h2>Ruby on Rails Guides (<%= ENV['RAILS_VERSION'] || 'edge' %>)</h2>
-
-<% if @edge %>
-<p>
- These are <b>Edge Guides</b>, based on the current
- <a href="https://github.com/rails/rails/tree/master">master branch</a>.
-</p>
-<p>
- If you are looking for the ones for the stable version please check
- <a href="http://guides.rubyonrails.org">http://guides.rubyonrails.org</a> instead.
-</p>
-<% else %>
-<p>
- These are the new guides for Rails 3. The guides for Rails 2.3 are still available
- at <a href="http://guides.rubyonrails.org/v2.3.11/">http://guides.rubyonrails.org/v2.3.11/</a>.
-</p>
-<% end %>
-<p>
- These guides are designed to make you immediately productive with Rails,
- and to help you understand how all of the pieces fit together.
-</p>
-
-<% end %>
-
-<% content_for :index_section do %>
-<div id="subCol">
- <dl>
- <dd class="work-in-progress">Guides marked with this icon are currently being worked on. While they might still be useful to you, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections to the author.</dd>
- </dl>
-</div>
-<% end %>
-
-<h3>Start Here</h3>
-
-<dl>
-<%= guide('Getting Started with Rails', 'getting_started.html') do %>
- <p>Everything you need to know to install Rails and create your first application.</p>
-<% end %>
-</dl>
-
-<h3>Models</h3>
-
-<dl>
-<%= guide("Rails Database Migrations", 'migrations.html') do %>
- <p>This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner.</p>
-<% end %>
-
-<%= guide("Active Record Validations and Callbacks", 'active_record_validations_callbacks.html') do %>
- <p>This guide covers how you can use Active Record validations and callbacks.</p>
-<% end %>
-
-<%= guide("Active Record Associations", 'association_basics.html') do %>
- <p>This guide covers all the associations provided by Active Record.</p>
-<% end %>
-
-<%= guide("Active Record Query Interface", 'active_record_querying.html') do %>
- <p>This guide covers the database query interface provided by Active Record.</p>
-<% end %>
-</dl>
-
-<h3>Views</h3>
-
-<dl>
-<%= guide("Layouts and Rendering in Rails", 'layouts_and_rendering.html') do %>
- <p>This guide covers the basic layout features of Action Controller and Action View, including rendering and redirecting, using content_for blocks, and working with partials.</p>
-<% end %>
-
-<%= guide("Action View Form Helpers", 'form_helpers.html', :work_in_progress => true) do %>
- <p>Guide to using built-in Form helpers.</p>
-<% end %>
-</dl>
-
-<h3>Controllers</h3>
-
-<dl>
-<%= guide("Action Controller Overview", 'action_controller_overview.html') do %>
- <p>This guide covers how controllers work and how they fit into the request cycle in your application. It includes sessions, filters, and cookies, data streaming, and dealing with exceptions raised by a request, among other topics.</p>
-<% end %>
-
-<%= guide("Rails Routing from the Outside In", 'routing.html') do %>
- <p>This guide covers the user-facing features of Rails routing. If you want to understand how to use routing in your own Rails applications, start here.</p>
-<% end %>
-</dl>
-
-<h3>Digging Deeper</h3>
-
-<dl>
-
-<%= guide("Active Support Core Extensions", 'active_support_core_extensions.html') do %>
- <p>This guide documents the Ruby core extensions defined in Active Support.</p>
-<% end %>
-
-<%= guide("Rails Internationalization API", 'i18n.html') do %>
- <p>This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on.</p>
-<% end %>
-
-<%= guide("Action Mailer Basics", 'action_mailer_basics.html', :work_in_progress => true) do %>
- <p>This guide describes how to use Action Mailer to send and receive emails.</p>
-<% end %>
-
-<%= guide("Testing Rails Applications", 'testing.html', :work_in_progress => true) do %>
- <p>This is a rather comprehensive guide to doing both unit and functional tests in Rails. It covers everything from &quot;What is a test?&quot; to the testing APIs. Enjoy.</p>
-<% end %>
-
-<%= guide("Securing Rails Applications", 'security.html') do %>
- <p>This guide describes common security problems in web applications and how to avoid them with Rails.</p>
-<% end %>
-
-<%= guide("Debugging Rails Applications", 'debugging_rails_applications.html') do %>
- <p>This guide describes how to debug Rails applications. It covers the different ways of achieving this and how to understand what is happening "behind the scenes" of your code.</p>
-<% end %>
-
-<%= guide("Performance Testing Rails Applications", 'performance_testing.html') do %>
- <p>This guide covers the various ways of performance testing a Ruby on Rails application.</p>
-<% end %>
-
-<%= guide("Configuring Rails Applications", 'configuring.html') do %>
- <p>This guide covers the basic configuration settings for a Rails application.</p>
-<% end %>
-
-<%= guide("Rails Command Line Tools and Rake tasks", 'command_line.html') do %>
- <p>This guide covers the command line tools and rake tasks provided by Rails.</p>
-<% end %>
-
-<%= guide("Caching with Rails", 'caching_with_rails.html', :work_in_progress => true) do %>
- <p>Various caching techniques provided by Rails.</p>
-<% end %>
-
-<%= guide('Asset Pipeline', 'asset_pipeline.html') do %>
- <p>This guide documents the asset pipeline.</p>
-<% end %>
-</dl>
-
-<h3>Extending Rails</h3>
-
-<dl>
- <%= guide("The Basics of Creating Rails Plugins", 'plugins.html', :work_in_progress => true) do %>
- <p>This guide covers how to build a plugin to extend the functionality of Rails.</p>
- <% end %>
-
- <%= guide("Rails on Rack", 'rails_on_rack.html') do %>
- <p>This guide covers Rails integration with Rack and interfacing with other Rack components.</p>
- <% end %>
-
- <%= guide("Creating and Customizing Rails Generators", 'generators.html') do %>
- <p>This guide covers the process of adding a brand new generator to your extension
- or providing an alternative to an element of a built-in Rails generator (such as
- providing alternative test stubs for the scaffold generator).</p>
- <% end %>
-</dl>
-
-<h3>Contributing to Ruby on Rails</h3>
-
-<dl>
- <%= guide("Contributing to Ruby on Rails", 'contributing_to_ruby_on_rails.html') do %>
- <p>Rails is not &quot;somebody else's framework.&quot; This guide covers a variety of ways that you can get involved in the ongoing development of Rails.</p>
- <% end %>
-
- <%= guide('API Documentation Guidelines', 'api_documentation_guidelines.html') do %>
- <p>This guide documents the Ruby on Rails API documentation guidelines.</p>
- <% end %>
-
- <%= guide('Ruby on Rails Guides Guidelines', 'ruby_on_rails_guides_guidelines.html') do %>
- <p>This guide documents the Ruby on Rails guides guidelines.</p>
- <% end %>
-</dl>
-
-<h3>Release Notes</h3>
-
-<dl>
-<%= guide("Ruby on Rails 3.1 Release Notes", '3_1_release_notes.html') do %>
- <p>Release notes for Rails 3.1.</p>
-<% end %>
-
-<%= guide("Ruby on Rails 3.0 Release Notes", '3_0_release_notes.html') do %>
- <p>Release notes for Rails 3.0.</p>
-<% end %>
-
-<%= guide("Ruby on Rails 2.3 Release Notes", '2_3_release_notes.html') do %>
- <p>Release notes for Rails 2.3.</p>
-<% end %>
-
-<%= guide("Ruby on Rails 2.2 Release Notes", '2_2_release_notes.html') do %>
- <p>Release notes for Rails 2.2.</p>
-<% end %>
-</dl>
diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile
deleted file mode 100644
index 23e36b39f9..0000000000
--- a/railties/guides/source/migrations.textile
+++ /dev/null
@@ -1,673 +0,0 @@
-h2. Migrations
-
-Migrations are a convenient way for you to alter your database in a structured and organized manner. You could edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run them. You'd also have to keep track of which changes need to be run against the production machines next time you deploy.
-
-Active Record tracks which migrations have already been run so all you have to do is update your source and run +rake db:migrate+. Active Record will work out which migrations should be run. It will also update your +db/schema.rb+ file to match the structure of your database.
-
-Migrations also allow you to describe these transformations using Ruby. The great thing about this is that (like most of Active Record's functionality) it is database independent: you don't need to worry about the precise syntax of +CREATE TABLE+ any more than you worry about variations on +SELECT *+ (you can drop down to raw SQL for database specific features). For example you could use SQLite3 in development, but MySQL in production.
-
-You'll learn all about migrations including:
-
-* The generators you can use to create them
-* The methods Active Record provides to manipulate your database
-* The Rake tasks that manipulate them
-* How they relate to +schema.rb+
-
-endprologue.
-
-h3. Anatomy of a Migration
-
-Before we dive into the details of a migration, here are a few examples of the sorts of things you can do:
-
-<ruby>
-class CreateProducts < ActiveRecord::Migration
- def up
- create_table :products do |t|
- t.string :name
- t.text :description
-
- t.timestamps
- end
- end
-
- def down
- drop_table :products
- end
-end
-</ruby>
-
-This migration adds a table called +products+ with a string column called +name+ and a text column called +description+. A primary key column called +id+ will also be added, however since this is the default we do not need to ask for this. The timestamp columns +created_at+ and +updated_at+ which Active Record populates automatically will also be added. Reversing this migration is as simple as dropping the table.
-
-Migrations are not limited to changing the schema. You can also use them to fix bad data in the database or populate new fields:
-
-<ruby>
-class AddReceiveNewsletterToUsers < ActiveRecord::Migration
- def up
- change_table :users do |t|
- t.boolean :receive_newsletter, :default => false
- end
- User.update_all ["receive_newsletter = ?", true]
- end
-
- def down
- remove_column :users, :receive_newsletter
- end
-end
-</ruby>
-
-NOTE: Some "caveats":#using-models-in-your-migrations apply to using models in your migrations.
-
-This migration adds a +receive_newsletter+ column to the +users+ table. We want it to default to +false+ for new users, but existing users are considered
-to have already opted in, so we use the User model to set the flag to +true+ for existing users.
-
-Rails 3.1 makes migrations smarter by providing a new <tt>change</tt> method. This method is preferred for writing constructive migrations (adding columns or tables). The migration knows how to migrate your database and reverse it when the migration is rolled back without the need to write a separate +down+ method.
-
-<ruby>
-class CreateProducts < ActiveRecord::Migration
- def change
- create_table :products do |t|
- t.string :name
- t.text :description
-
- t.timestamps
- end
- end
-end
-</ruby>
-
-h4. Migrations are Classes
-
-A migration is a subclass of <tt>ActiveRecord::Migration</tt> that implements two methods: +up+ (perform the required transformations) and +down+ (revert them).
-
-Active Record provides methods that perform common data definition tasks in a database independent way (you'll read about them in detail later):
-
-* +create_table+
-* +change_table+
-* +drop_table+
-* +add_column+
-* +change_column+
-* +rename_column+
-* +remove_column+
-* +add_index+
-* +remove_index+
-
-If you need to perform tasks specific to your database (for example create a "foreign key":#active-record-and-referential-integrity constraint) then the +execute+ method allows you to execute arbitrary SQL. A migration is just a regular Ruby class so you're not limited to these functions. For example after adding a column you could write code to set the value of that column for existing records (if necessary using your models).
-
-On databases that support transactions with statements that change the schema (such as PostgreSQL or SQLite3), migrations are wrapped in a transaction. If the database does not support this (for example MySQL) then when a migration fails the parts of it that succeeded will not be rolled back. You will have to unpick the changes that were made by hand.
-
-h4. What's in a Name
-
-Migrations are stored in files in +db/migrate+, one for each migration class. The name of the file is of the form +YYYYMMDDHHMMSS_create_products.rb+, that is to say a UTC timestamp identifying the migration followed by an underscore followed by the name of the migration. The name of the migration class (CamelCased version) should match the latter part of the file name. For example +20080906120000_create_products.rb+ should define class +CreateProducts+ and +20080906120001_add_details_to_products.rb+ should define +AddDetailsToProducts+. If you do feel the need to change the file name then you <em>have to</em> update the name of the class inside or Rails will complain about a missing class.
-
-Internally Rails only uses the migration's number (the timestamp) to identify them. Prior to Rails 2.1 the migration number started at 1 and was incremented each time a migration was generated. With multiple developers it was easy for these to clash requiring you to rollback migrations and renumber them. With Rails 2.1 this is largely avoided by using the creation time of the migration to identify them. You can revert to the old numbering scheme by adding the following line to +config/application.rb+.
-
-<ruby>
-config.active_record.timestamped_migrations = false
-</ruby>
-
-The combination of timestamps and recording which migrations have been run allows Rails to handle common situations that occur with multiple developers.
-
-For example Alice adds migrations +20080906120000+ and +20080906123000+ and Bob adds +20080906124500+ and runs it. Alice finishes her changes and checks in her migrations and Bob pulls down the latest changes. Rails knows that it has not run Alice's two migrations so +rake db:migrate+ would run them (even though Bob's migration with a later timestamp has been run), and similarly migrating down would not run their +down+ methods.
-
-Of course this is no substitution for communication within the team. For example, if Alice's migration removed a table that Bob's migration assumed to exist, then trouble would certainly strike.
-
-h4. Changing Migrations
-
-Occasionally you will make a mistake when writing a migration. If you have already run the migration then you cannot just edit the migration and run the migration again: Rails thinks it has already run the migration and so will do nothing when you run +rake db:migrate+. You must rollback the migration (for example with +rake db:rollback+), edit your migration and then run +rake db:migrate+ to run the corrected version.
-
-In general editing existing migrations is not a good idea: you will be creating extra work for yourself and your co-workers and cause major headaches if the existing version of the migration has already been run on production machines. Instead, you should write a new migration that performs the changes you require. Editing a freshly generated migration that has not yet been committed to source control (or, more generally, which has not been propagated beyond your development machine) is relatively harmless.
-
-h4. Supported Types
-
-Active Record supports the following types:
-
-* +:primary_key+
-* +:string+
-* +:text+
-* +:integer+
-* +:float+
-* +:decimal+
-* +:datetime+
-* +:timestamp+
-* +:time+
-* +:date+
-* +:binary+
-* +:boolean+
-
-These will be mapped onto an appropriate underlying database type. For example, with MySQL the type +:string+ is mapped to +VARCHAR(255)+. You can create columns of types not supported by Active Record when using the non-sexy syntax, for example
-
-<ruby>
-create_table :products do |t|
- t.column :name, 'polygon', :null => false
-end
-</ruby>
-
-This may however hinder portability to other databases.
-
-h3. Creating a Migration
-
-h4. Creating a Model
-
-The model and scaffold generators will create migrations appropriate for adding a new model. This migration will already contain instructions for creating the relevant table. If you tell Rails what columns you want, then statements for adding these columns will also be created. For example, running
-
-<shell>
-$ rails generate model Product name:string description:text
-</shell>
-
-will create a migration that looks like this
-
-<ruby>
-class CreateProducts < ActiveRecord::Migration
- def change
- create_table :products do |t|
- t.string :name
- t.text :description
-
- t.timestamps
- end
- end
-end
-</ruby>
-
-You can append as many column name/type pairs as you want. By default +t.timestamps+ (which creates the +updated_at+ and +created_at+ columns that
-are automatically populated by Active Record) will be added for you.
-
-h4. Creating a Standalone Migration
-
-If you are creating migrations for other purposes (for example to add a column to an existing table) then you can use the migration generator:
-
-<shell>
-$ rails generate migration AddPartNumberToProducts
-</shell>
-
-This will create an empty but appropriately named migration:
-
-<ruby>
-class AddPartNumberToProducts < ActiveRecord::Migration
- def change
- end
-end
-</ruby>
-
-If the migration name is of the form "AddXXXToYYY" or "RemoveXXXFromYYY" and is followed by a list of column names and types then a migration containing the appropriate +add_column+ and +remove_column+ statements will be created.
-
-<shell>
-$ rails generate migration AddPartNumberToProducts part_number:string
-</shell>
-
-will generate
-
-<ruby>
-class AddPartNumberToProducts < ActiveRecord::Migration
- def change
- add_column :products, :part_number, :string
- end
-end
-</ruby>
-
-Similarly,
-
-<shell>
-$ rails generate migration RemovePartNumberFromProducts part_number:string
-</shell>
-
-generates
-
-<ruby>
-class RemovePartNumberFromProducts < ActiveRecord::Migration
- def up
- remove_column :products, :part_number
- end
-
- def down
- add_column :products, :part_number, :string
- end
-end
-</ruby>
-
-You are not limited to one magically generated column, for example
-
-<shell>
-$ rails generate migration AddDetailsToProducts part_number:string price:decimal
-</shell>
-
-generates
-
-<ruby>
-class AddDetailsToProducts < ActiveRecord::Migration
- def change
- add_column :products, :part_number, :string
- add_column :products, :price, :decimal
- end
-end
-</ruby>
-
-As always, what has been generated for you is just a starting point. You can add or remove from it as you see fit.
-
-NOTE: The generated migration file for destructive migrations will still be old-style using the +up+ and +down+ methods. This is because Rails doesn't know the original data types defined when you made the original changes.
-
-h3. Writing a Migration
-
-Once you have created your migration using one of the generators it's time to get to work!
-
-h4. Creating a Table
-
-Migration method +create_table+ will be one of your workhorses. A typical use would be
-
-<ruby>
-create_table :products do |t|
- t.string :name
-end
-</ruby>
-
-which creates a +products+ table with a column called +name+ (and as discussed below, an implicit +id+ column).
-
-The object yielded to the block allows you to create columns on the table. There are two ways of doing it. The first (traditional) form looks like
-
-<ruby>
-create_table :products do |t|
- t.column :name, :string, :null => false
-end
-</ruby>
-
-The second form, the so called "sexy" migration, drops the somewhat redundant +column+ method. Instead, the +string+, +integer+, etc. methods create a column of that type. Subsequent parameters are the same.
-
-<ruby>
-create_table :products do |t|
- t.string :name, :null => false
-end
-</ruby>
-
-By default, +create_table+ will create a primary key called +id+. You can change the name of the primary key with the +:primary_key+ option (don't forget to update the corresponding model) or, if you don't want a primary key at all (for example for a HABTM join table), you can pass the option +:id => false+. If you need to pass database specific options you can place an SQL fragment in the +:options+ option. For example,
-
-<ruby>
-create_table :products, :options => "ENGINE=BLACKHOLE" do |t|
- t.string :name, :null => false
-end
-</ruby>
-
-will append +ENGINE=BLACKHOLE+ to the SQL statement used to create the table (when using MySQL, the default is +ENGINE=InnoDB+).
-
-h4. Changing Tables
-
-A close cousin of +create_table+ is +change_table+, used for changing existing tables. It is used in a similar fashion to +create_table+ but the object yielded to the block knows more tricks. For example
-
-<ruby>
-change_table :products do |t|
- t.remove :description, :name
- t.string :part_number
- t.index :part_number
- t.rename :upccode, :upc_code
-end
-</ruby>
-
-removes the +description+ and +name+ columns, creates a +part_number+ column and adds an index on it. Finally it renames the +upccode+ column. This is the same as doing
-
-<ruby>
-remove_column :products, :description
-remove_column :products, :name
-add_column :products, :part_number, :string
-add_index :products, :part_number
-rename_column :products, :upccode, :upc_code
-</ruby>
-
-You don't have to keep repeating the table name and it groups all the statements related to modifying one particular table. The individual transformation names are also shorter, for example +remove_column+ becomes just +remove+ and +add_index+ becomes just +index+.
-
-h4. Special Helpers
-
-Active Record provides some shortcuts for common functionality. It is for example very common to add both the +created_at+ and +updated_at+ columns and so there is a method that does exactly that:
-
-<ruby>
-create_table :products do |t|
- t.timestamps
-end
-</ruby>
-will create a new products table with those two columns (plus the +id+ column) whereas
-
-<ruby>
-change_table :products do |t|
- t.timestamps
-end
-</ruby>
-adds those columns to an existing table.
-
-The other helper is called +references+ (also available as +belongs_to+). In its simplest form it just adds some readability
-
-<ruby>
-create_table :products do |t|
- t.references :category
-end
-</ruby>
-
-will create a +category_id+ column of the appropriate type. Note that you pass the model name, not the column name. Active Record adds the +_id+ for you. If you have polymorphic +belongs_to+ associations then +references+ will add both of the columns required:
-
-<ruby>
-create_table :products do |t|
- t.references :attachment, :polymorphic => {:default => 'Photo'}
-end
-</ruby>
-will add an +attachment_id+ column and a string +attachment_type+ column with a default value of 'Photo'.
-
-NOTE: The +references+ helper does not actually create foreign key constraints for you. You will need to use +execute+ or a plugin that adds "foreign key support":#active-record-and-referential-integrity.
-
-If the helpers provided by Active Record aren't enough you can use the +execute+ method to execute arbitrary SQL.
-
-For more details and examples of individual methods, check the API documentation, in particular the documentation for "<tt>ActiveRecord::ConnectionAdapters::SchemaStatements</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "<tt>ActiveRecord::ConnectionAdapters::TableDefinition</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "<tt>ActiveRecord::ConnectionAdapters::Table</tt>":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+).
-
-h4. Writing Your +change+ Method
-
-The +change+ method removes the need to write both +up+ and +down+ methods in those cases that Rails know how to revert the changes automatically. Currently, the +change+ method supports only these migration definitions:
-
-* +add_column+
-* +add_index+
-* +add_timestamps+
-* +create_table+
-* +remove_timestamps+
-* +rename_column+
-* +rename_index+
-* +rename_table+
-
-If you're going to use other methods, you'll have to write the +up+ and +down+ methods normally.
-
-h4. Writing Your +down+ Method
-
-The +down+ method of your migration should revert the transformations done by the +up+ method. In other words, the database schema should be unchanged if you do an +up+ followed by a +down+. For example, if you create a table in the +up+ method, you should drop it in the +down+ method. It is wise to reverse the transformations in precisely the reverse order they were made in the +up+ method. For example,
-
-<ruby>
-class ExampleMigration < ActiveRecord::Migration
-
- def up
- create_table :products do |t|
- t.references :category
- end
- #add a foreign key
- execute <<-SQL
- ALTER TABLE products
- ADD CONSTRAINT fk_products_categories
- FOREIGN KEY (category_id)
- REFERENCES categories(id)
- SQL
-
- add_column :users, :home_page_url, :string
-
- rename_column :users, :email, :email_address
- end
-
- def down
- rename_column :users, :email_address, :email
- remove_column :users, :home_page_url
- execute "ALTER TABLE products DROP FOREIGN KEY fk_products_categories"
- drop_table :products
- end
-end
-</ruby>
-
-Sometimes your migration will do something which is just plain irreversible; for example, it might destroy some data. In such cases, you can raise +ActiveRecord::IrreversibleMigration+ from your +down+ method. If someone tries to revert your migration, an error message will be displayed saying that it can't be done.
-
-h3. Running Migrations
-
-Rails provides a set of rake tasks to work with migrations which boil down to running certain sets of migrations. The very first migration related rake task you will use will probably be +db:migrate+. In its most basic form it just runs the +up+ method for all the migrations that have not yet been run. If there are no such migrations, it exits.
-
-Note that running the +db:migrate+ also invokes the +db:schema:dump+ task, which will update your db/schema.rb file to match the structure of your database.
-
-If you specify a target version, Active Record will run the required migrations (up or down) until it has reached the specified version. The
-version is the numerical prefix on the migration's filename. For example, to migrate to version 20080906120000 run
-
-<shell>
-$ rake db:migrate VERSION=20080906120000
-</shell>
-
-If version 20080906120000 is greater than the current version (i.e., it is migrating upwards), this will run the +up+ method on all migrations up to and including 20080906120000. If migrating downwards, this will run the +down+ method on all the migrations down to, but not including, 20080906120000.
-
-h4. Rolling Back
-
-A common task is to rollback the last migration, for example if you made a mistake in it and wish to correct it. Rather than tracking down the version number associated with the previous migration you can run
-
-<shell>
-$ rake db:rollback
-</shell>
-
-This will run the +down+ method from the latest migration. If you need to undo several migrations you can provide a +STEP+ parameter:
-
-<shell>
-$ rake db:rollback STEP=3
-</shell>
-
-will run the +down+ method from the last 3 migrations.
-
-The +db:migrate:redo+ task is a shortcut for doing a rollback and then migrating back up again. As with the +db:rollback+ task, you can use the +STEP+ parameter if you need to go more than one version back, for example
-
-<shell>
-$ rake db:migrate:redo STEP=3
-</shell>
-
-Neither of these Rake tasks do anything you could not do with +db:migrate+. They are simply more convenient, since you do not need to explicitly specify the version to migrate to.
-
-Lastly, the +db:reset+ task will drop the database, recreate it and load the current schema into it.
-
-NOTE: This is not the same as running all the migrations - see the section on "schema.rb":#schema-dumping-and-you.
-
-h4. Being Specific
-
-If you need to run a specific migration up or down, the +db:migrate:up+ and +db:migrate:down+ tasks will do that. Just specify the appropriate version and the corresponding migration will have its +up+ or +down+ method invoked, for example,
-
-<shell>
-$ rake db:migrate:up VERSION=20080906120000
-</shell>
-
-will run the +up+ method from the 20080906120000 migration. These tasks check whether the migration has already run, so for example +db:migrate:up VERSION=20080906120000+ will do nothing if Active Record believes that 20080906120000 has already been run.
-
-h4. Being Talkative
-
-By default migrations tell you exactly what they're doing and how long it took. A migration creating a table and adding an index might produce output like this
-
-<shell>
-20080906170109 CreateProducts: migrating
--- create_table(:products)
- -> 0.0021s
--- add_index(:products, :name)
- -> 0.0026s
-20080906170109 CreateProducts: migrated (0.0059s)
-</shell>
-
-Several methods are provided that allow you to control all this:
-
-* +suppress_messages+ takes a block as an argument and suppresses any output generated by the block.
-* +say+ takes a message argument and outputs it as is. A second boolean argument can be passed to specify whether to indent or not.
-* +say_with_time+ outputs text along with how long it took to run its block. If the block returns an integer it assumes it is the number of rows affected.
-
-For example, this migration
-
-<ruby>
-class CreateProducts < ActiveRecord::Migration
- def change
- suppress_messages do
- create_table :products do |t|
- t.string :name
- t.text :description
- t.timestamps
- end
- end
- say "Created a table"
- suppress_messages {add_index :products, :name}
- say "and an index!", true
- say_with_time 'Waiting for a while' do
- sleep 10
- 250
- end
- end
-end
-</ruby>
-
-generates the following output
-
-<shell>
-20080906170109 CreateProducts: migrating
- Created a table
- -> and an index!
- Waiting for a while
- -> 10.0001s
- -> 250 rows
-20080906170109 CreateProducts: migrated (10.0097s)
-</shell>
-
-If you just want Active Record to shut up, then running +rake db:migrate VERBOSE=false+ will suppress all output.
-
-h3. Using Models in Your Migrations
-
-When creating or updating data in a migration it is often tempting to use one of your models. After all, they exist to provide easy access to the underlying data. This can be done, but some caution should be observed.
-
-For example, problems occur when the model uses database columns which are (1) not currently in the database and (2) will be created by this or a subsequent migration.
-
-Consider this example, where Alice and Bob are working on the same code base which contains a +Product+ model:
-
-Bob goes on vacation.
-
-Alice creates a migration for the +products+ table which adds a new column and initializes it.
-She also adds a validation to the +Product+ model for the new column.
-
-<ruby>
-# db/migrate/20100513121110_add_flag_to_product.rb
-
-class AddFlagToProduct < ActiveRecord::Migration
- def change
- add_column :products, :flag, :int
- Product.all.each { |f| f.update_attributes!(:flag => 'false') }
- end
-end
-</ruby>
-
-<ruby>
-# app/model/product.rb
-
-class Product < ActiveRecord::Base
- validates :flag, :presence => true
-end
-</ruby>
-
-Alice adds a second migration which adds and initializes another column to the +products+ table and also adds a validation to the +Product+ model for the new column.
-
-<ruby>
-# db/migrate/20100515121110_add_fuzz_to_product.rb
-
-class AddFuzzToProduct < ActiveRecord::Migration
- def change
- add_column :products, :fuzz, :string
- Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
- end
-end
-</ruby>
-
-<ruby>
-# app/model/product.rb
-
-class Product < ActiveRecord::Base
- validates :flag, :fuzz, :presence => true
-end
-</ruby>
-
-Both migrations work for Alice.
-
-Bob comes back from vacation and:
-
-# updates the source - which contains both migrations and the latests version of the Product model.
-# runs outstanding migrations with +rake db:migrate+, which includes the one that updates the +Product+ model.
-
-The migration crashes because when the model attempts to save, it tries to validate the second added column, which is not in the database when the _first_ migration runs:
-
-<plain>
-rake aborted!
-An error has occurred, this and all later migrations canceled:
-
-undefined method `fuzz' for #<Product:0x000001049b14a0>
-</plain>
-
-A fix for this is to create a local model within the migration. This keeps rails from running the validations, so that the migrations run to completion.
-
-When using a faux model, it's a good idea to call +Product.reset_column_information+ to refresh the +ActiveRecord+ cache for the +Product+ model prior to updating data in the database.
-
-If Alice had done this instead, there would have been no problem:
-
-<ruby>
-# db/migrate/20100513121110_add_flag_to_product.rb
-
-class AddFlagToProduct < ActiveRecord::Migration
- class Product < ActiveRecord::Base
- end
-
- def change
- add_column :products, :flag, :int
- Product.reset_column_information
- Product.all.each { |f| f.update_attributes!(:flag => false) }
- end
-end
-</ruby>
-
-<ruby>
-# db/migrate/20100515121110_add_fuzz_to_product.rb
-
-class AddFuzzToProduct < ActiveRecord::Migration
- class Product < ActiveRecord::Base
- end
- def change
- add_column :products, :fuzz, :string
- Product.reset_column_information
- Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
- end
-end
-</ruby>
-
-
-h3. Schema Dumping and You
-
-h4. What are Schema Files for?
-
-Migrations, mighty as they may be, are not the authoritative source for your database schema. That role falls to either +db/schema.rb+ or an SQL file which Active Record generates by examining the database. They are not designed to be edited, they just represent the current state of the database.
-
-There is no need (and it is error prone) to deploy a new instance of an app by replaying the entire migration history. It is much simpler and faster to just load into the database a description of the current schema.
-
-For example, this is how the test database is created: the current development database is dumped (either to +db/schema.rb+ or +db/development.sql+) and then loaded into the test database.
-
-Schema files are also useful if you want a quick look at what attributes an Active Record object has. This information is not in the model's code and is frequently spread across several migrations, but is summed up in the schema file. The "annotate_models":https://github.com/ctran/annotate_models gem automatically adds and updates comments at the top of each model summarizing the schema if you desire that functionality.
-
-h4. Types of Schema Dumps
-
-There are two ways to dump the schema. This is set in +config/application.rb+ by the +config.active_record.schema_format+ setting, which may be either +:sql+ or +:ruby+.
-
-If +:ruby+ is selected then the schema is stored in +db/schema.rb+. If you look at this file you'll find that it looks an awful lot like one very big migration:
-
-<ruby>
-ActiveRecord::Schema.define(:version => 20080906171750) do
- create_table "authors", :force => true do |t|
- t.string "name"
- t.datetime "created_at"
- t.datetime "updated_at"
- end
-
- create_table "products", :force => true do |t|
- t.string "name"
- t.text "description"
- t.datetime "created_at"
- t.datetime "updated_at"
- t.string "part_number"
- end
-end
-</ruby>
-
-In many ways this is exactly what it is. This file is created by inspecting the database and expressing its structure using +create_table+, +add_index+, and so on. Because this is database-independent, it could be loaded into any database that Active Record supports. This could be very useful if you were to distribute an application that is able to run against multiple databases.
-
-There is however a trade-off: +db/schema.rb+ cannot express database specific items such as foreign key constraints, triggers, or stored procedures. While in a migration you can execute custom SQL statements, the schema dumper cannot reconstitute those statements from the database. If you are using features like this, then you should set the schema format to +:sql+.
-
-Instead of using Active Record's schema dumper, the database's structure will be dumped using a tool specific to the database (via the +db:structure:dump+ Rake task) into +db/#{Rails.env}_structure.sql+. For example, for the PostgreSQL RDBMS, the +pg_dump+ utility is used. For MySQL, this file will contain the output of +SHOW CREATE TABLE+ for the various tables. Loading these schemas is simply a question of executing the SQL statements they contain. By definition, this will create a perfect copy of the database's structure. Using the +:sql+ schema format will, however, prevent loading the schema into a RDBMS other than the one used to create it.
-
-h4. Schema Dumps and Source Control
-
-Because schema dumps are the authoritative source for your database schema, it is strongly recommended that you check them into source control.
-
-h3. Active Record and Referential Integrity
-
-The Active Record way claims that intelligence belongs in your models, not in the database. As such, features such as triggers or foreign key constraints, which push some of that intelligence back into the database, are not heavily used.
-
-Validations such as +validates :foreign_key, :uniqueness => true+ are one way in which models can enforce data integrity. The +:dependent+ option on associations allows models to automatically destroy child objects when the parent is destroyed. Like anything which operates at the application level, these cannot guarantee referential integrity and so some people augment them with foreign key constraints.
-
-Although Active Record does not provide any tools for working directly with such features, the +execute+ method can be used to execute arbitrary SQL. There are also a number of plugins such as "foreign_key_migrations":https://github.com/harukizaemon/redhillonrails/tree/master/foreign_key_migrations/ which add foreign key support to Active Record (including support for dumping foreign keys in +db/schema.rb+).
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index 73bdd0b552..670477f91a 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -5,40 +5,32 @@ require 'pathname'
require 'active_support'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/logger'
require 'rails/application'
require 'rails/version'
+require 'rails/deprecation'
require 'active_support/railtie'
require 'action_dispatch/railtie'
-# For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the
-# multibyte safe operations. Plugin authors supporting other encodings
-# should override this behavior and set the relevant +default_charset+
-# on ActionController::Base.
-#
# For Ruby 1.9, UTF-8 is the default internal and external encoding.
-if RUBY_VERSION < '1.9'
- $KCODE='u'
-else
- silence_warnings do
- Encoding.default_external = Encoding::UTF_8
- Encoding.default_internal = Encoding::UTF_8
- end
+silence_warnings do
+ Encoding.default_external = Encoding::UTF_8
+ Encoding.default_internal = Encoding::UTF_8
end
module Rails
autoload :Info, 'rails/info'
autoload :InfoController, 'rails/info_controller'
+ autoload :Queueing, 'rails/queueing'
class << self
def application
- @@application ||= nil
+ @application ||= nil
end
def application=(application)
- @@application = application
+ @application = application
end
# The Configuration instance used to configure the Rails environment
@@ -46,28 +38,43 @@ module Rails
application.config
end
+ # Rails.queue is the application's queue. You can push a job onto
+ # the queue by:
+ #
+ # Rails.queue.push job
+ #
+ # A job is an object that responds to +run+. Queue consumers will
+ # pop jobs off of the queue and invoke the queue's +run+ method.
+ #
+ # Note that depending on your queue implementation, jobs may not
+ # be executed in the same process as they were created in, and
+ # are never executed in the same thread as they were created in.
+ #
+ # If necessary, a queue implementation may need to serialize your
+ # job for distribution to another process. The documentation of
+ # your queue will specify the requirements for that serialization.
+ def queue
+ application.queue
+ end
+
def initialize!
application.initialize!
end
def initialized?
- @@initialized || false
- end
-
- def initialized=(initialized)
- @@initialized ||= initialized
+ application.initialized?
end
def logger
- @@logger ||= nil
+ @logger ||= nil
end
def logger=(logger)
- @@logger = logger
+ @logger = logger
end
def backtrace_cleaner
- @@backtrace_cleaner ||= begin
+ @backtrace_cleaner ||= begin
# Relies on Active Support, so we have to lazy load to postpone definition until AS has been loaded
require 'rails/backtrace_cleaner'
Rails::BacktraceCleaner.new
@@ -87,7 +94,11 @@ module Rails
end
def cache
- RAILS_CACHE
+ @cache ||= nil
+ end
+
+ def cache=(cache)
+ @cache = cache
end
# Returns all rails groups for loading based on:
@@ -96,14 +107,11 @@ module Rails
# * The environment variable RAILS_GROUPS;
# * The optional envs given as argument and the hash with group dependencies;
#
- # == Examples
- #
# groups :assets => [:development, :test]
#
# # Returns
# # => [:default, :development, :assets] for Rails.env == "development"
# # => [:default, :production] for Rails.env == "production"
- #
def groups(*groups)
hash = groups.extract_options!
env = Rails.env
diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb
index 01ceb80972..eabe566829 100644
--- a/railties/lib/rails/all.rb
+++ b/railties/lib/rails/all.rb
@@ -4,9 +4,8 @@ require "rails"
active_record
action_controller
action_mailer
- active_resource
rails/test_unit
- sprockets
+ sprockets/rails
).each do |framework|
begin
require "#{framework}/railtie"
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index cbb2d23238..c4edbae55b 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,7 +1,4 @@
-require 'active_support/core_ext/hash/reverse_merge'
-require 'active_support/file_update_checker'
require 'fileutils'
-require 'rails/plugin'
require 'rails/engine'
module Rails
@@ -10,7 +7,7 @@ module Rails
#
# == Initialization
#
- # Rails::Application is responsible for executing all railties, engines and plugin
+ # Rails::Application is responsible for executing all railties and engines
# initializers. It also executes some bootstrap initializers (check
# Rails::Application::Bootstrap) and finishing initializers, after all the others
# are executed (check Rails::Application::Finisher).
@@ -20,7 +17,7 @@ module Rails
# Besides providing the same configuration as Rails::Engine and Rails::Railtie,
# the application object has several specific configurations, for example
# "allow_concurrency", "cache_classes", "consider_all_requests_local", "filter_parameters",
- # "logger", "reload_plugins" and so forth.
+ # "logger" and so forth.
#
# Check Rails::Application::Configuration to see them all.
#
@@ -33,6 +30,25 @@ module Rails
#
# The Application is also responsible for building the middleware stack.
#
+ # == Booting process
+ #
+ # The application is also responsible for setting up and executing the booting
+ # process. From the moment you require "config/application.rb" in your app,
+ # the booting process goes like this:
+ #
+ # 1) require "config/boot.rb" to setup load paths
+ # 2) require railties and engines
+ # 3) Define Rails.application as "class MyApp::Application < Rails::Application"
+ # 4) Run config.before_configuration callbacks
+ # 5) Load config/environments/ENV.rb
+ # 6) Run config.before_initialize callbacks
+ # 7) Run Railtie#initializer defined by railties, engines and application.
+ # One by one, each engine sets up its load paths, routes and runs its config/initializers/* files.
+ # 9) Custom Railtie#initializers added by railties, engines and applications are executed
+ # 10) Build the middleware stack and run to_prepare callbacks
+ # 11) Run config.before_eager_load and eager_load if cache classes is true
+ # 12) Run config.after_initialize callbacks
+ #
class Application < Engine
autoload :Bootstrap, 'rails/application/bootstrap'
autoload :Configuration, 'rails/application/configuration'
@@ -50,14 +66,17 @@ module Rails
end
end
- attr_accessor :assets, :sandbox
+ attr_accessor :assets, :sandbox, :queue_consumer
alias_method :sandbox?, :sandbox
+ attr_reader :reloaders
+ attr_writer :queue
delegate :default_url_options, :default_url_options=, :to => :routes
def initialize
super
@initialized = false
+ @reloaders = []
end
# This method is called just after an application inherits from Rails::Application,
@@ -83,68 +102,133 @@ module Rails
require environment if environment
end
+ # Reload application routes regardless if they changed or not.
def reload_routes!
routes_reloader.reload!
end
- def routes_reloader
+ def routes_reloader #:nodoc:
@routes_reloader ||= RoutesReloader.new
end
- def initialize!(group=:default)
+ # Returns an array of file paths appended with a hash of directories-extensions
+ # suitable for ActiveSupport::FileUpdateChecker API.
+ def watchable_args
+ files, dirs = config.watchable_files.dup, config.watchable_dirs.dup
+
+ ActiveSupport::Dependencies.autoload_paths.each do |path|
+ dirs[path.to_s] = [:rb]
+ end
+
+ [files, dirs]
+ end
+
+ # Initialize the application passing the given group. By default, the
+ # group is :default but sprockets precompilation passes group equals
+ # to assets if initialize_on_precompile is false to avoid booting the
+ # whole app.
+ def initialize!(group=:default) #:nodoc:
raise "Application has been already initialized." if @initialized
run_initializers(group, self)
@initialized = true
self
end
+ def initialized?
+ @initialized
+ end
+
+ # Load the application and its railties tasks and invoke the registered hooks.
+ # Check <tt>Rails::Railtie.rake_tasks</tt> for more info.
def load_tasks(app=self)
initialize_tasks
super
self
end
+ # Load the application console and invoke the registered hooks.
+ # Check <tt>Rails::Railtie.console</tt> for more info.
def load_console(app=self)
initialize_console
super
self
end
- # Rails.application.env_config stores some of the Rails initial environment parameters.
- # Currently stores:
- #
- # * action_dispatch.parameter_filter" => config.filter_parameters,
- # * action_dispatch.secret_token" => config.secret_token,
- # * action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
- #
- # These parameters will be used by middlewares and engines to configure themselves.
- #
+ # Stores some of the Rails initial environment parameters which
+ # will be used by middlewares and engines to configure themselves.
def env_config
@env_config ||= super.merge({
"action_dispatch.parameter_filter" => config.filter_parameters,
"action_dispatch.secret_token" => config.secret_token,
- "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions
+ "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions,
+ "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local,
+ "action_dispatch.logger" => Rails.logger,
+ "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner
})
end
- def initializers
+ # Returns the ordered railties for this application considering railties_order.
+ def ordered_railties #:nodoc:
+ @ordered_railties ||= begin
+ order = config.railties_order.map do |railtie|
+ if railtie == :main_app
+ self
+ elsif railtie.respond_to?(:instance)
+ railtie.instance
+ else
+ railtie
+ end
+ end
+
+ all = (railties.all - order)
+ all.push(self) unless all.include?(self)
+ order.push(:all) unless order.include?(:all)
+
+ index = order.index(:all)
+ order[index] = all
+ order.reverse.flatten
+ end
+ end
+
+ def initializers #:nodoc:
Bootstrap.initializers_for(self) +
super +
Finisher.initializers_for(self)
end
- def config
+ def config #:nodoc:
@config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd))
end
+ def queue #:nodoc:
+ @queue ||= build_queue
+ end
+
+ def build_queue # :nodoc:
+ config.queue.new
+ end
+
def to_app
self
end
+ def helpers_paths #:nodoc:
+ config.helpers_paths
+ end
+
+ def call(env)
+ env["ORIGINAL_FULLPATH"] = build_original_fullpath(env)
+ super(env)
+ end
+
protected
alias :build_middleware_stack :app
+ def reload_dependencies?
+ config.reload_classes_only_on_change != true || reloaders.map(&:updated?).any?
+ end
+
def default_middleware_stack
ActionDispatch::MiddlewareStack.new.tap do |middleware|
if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache
@@ -153,8 +237,11 @@ module Rails
end
if config.force_ssl
- require "rack/ssl"
- middleware.use ::Rack::SSL, config.ssl_options
+ middleware.use ::ActionDispatch::SSL, config.ssl_options
+ end
+
+ if config.action_dispatch.x_sendfile_header.present?
+ middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
end
if config.serve_static_assets
@@ -164,17 +251,24 @@ module Rails
middleware.use ::Rack::Lock unless config.allow_concurrency
middleware.use ::Rack::Runtime
middleware.use ::Rack::MethodOverride
- middleware.use ::Rails::Rack::Logger # must come after Rack::MethodOverride to properly log overridden methods
- middleware.use ::ActionDispatch::ShowExceptions, config.consider_all_requests_local
+ middleware.use ::ActionDispatch::RequestId
+ middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods
+ middleware.use ::ActionDispatch::ShowExceptions, config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path)
+ middleware.use ::ActionDispatch::DebugExceptions
middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies
- if config.action_dispatch.x_sendfile_header.present?
- middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header
+
+ unless config.cache_classes
+ app = self
+ middleware.use ::ActionDispatch::Reloader, lambda { app.reload_dependencies? }
end
- middleware.use ::ActionDispatch::Reloader unless config.cache_classes
+
middleware.use ::ActionDispatch::Callbacks
middleware.use ::ActionDispatch::Cookies
if config.session_store
+ if config.force_ssl && !config.session_options.key?(:secure)
+ config.session_options[:secure] = true
+ end
middleware.use config.session_store, config.session_options
middleware.use ::ActionDispatch::Flash
end
@@ -190,7 +284,7 @@ module Rails
end
end
- def initialize_tasks
+ def initialize_tasks #:nodoc:
self.class.rake_tasks do
require "rails/tasks"
task :environment do
@@ -200,10 +294,22 @@ module Rails
end
end
- def initialize_console
+ def initialize_console #:nodoc:
require "pp"
require "rails/console/app"
require "rails/console/helpers"
end
+
+ def build_original_fullpath(env)
+ path_info = env["PATH_INFO"]
+ query_string = env["QUERY_STRING"]
+ script_name = env["SCRIPT_NAME"]
+
+ if query_string.present?
+ "#{script_name}#{path_info}?#{query_string}"
+ else
+ "#{script_name}#{path_info}"
+ end
+ end
end
end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 0aff05b681..e567df7162 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -24,40 +24,41 @@ module Rails
initializer :initialize_logger, :group => :all do
Rails.logger ||= config.logger || begin
path = config.paths["log"].first
- logger = ActiveSupport::BufferedLogger.new(path)
- logger.level = ActiveSupport::BufferedLogger.const_get(config.log_level.to_s.upcase)
- logger.auto_flushing = false if Rails.env.production?
+ unless File.exist? File.dirname path
+ FileUtils.mkdir_p File.dirname path
+ end
+
+ f = File.open path, 'a'
+ f.binmode
+ f.sync = config.autoflush_log # if true make sure every write flushes
+
+ logger = ActiveSupport::Logger.new f
+ logger.formatter = config.log_formatter
+ logger = ActiveSupport::TaggedLogging.new(logger)
+ logger.level = ActiveSupport::Logger.const_get(config.log_level.to_s.upcase)
logger
rescue StandardError
- logger = ActiveSupport::BufferedLogger.new(STDERR)
- logger.level = ActiveSupport::BufferedLogger::WARN
+ logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDERR))
+ logger.level = ActiveSupport::Logger::WARN
logger.warn(
"Rails Error: Unable to access log file. Please ensure that #{path} exists and is chmod 0666. " +
"The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
)
logger
end
- at_exit { Rails.logger.flush if Rails.logger.respond_to?(:flush) }
end
# Initialize cache early in the stack so railties can make use of it.
initializer :initialize_cache, :group => :all do
- unless defined?(RAILS_CACHE)
- silence_warnings { Object.const_set "RAILS_CACHE", ActiveSupport::Cache.lookup_store(config.cache_store) }
+ unless Rails.cache
+ Rails.cache = ActiveSupport::Cache.lookup_store(config.cache_store)
- if RAILS_CACHE.respond_to?(:middleware)
- config.middleware.insert_before("Rack::Runtime", RAILS_CACHE.middleware)
+ if Rails.cache.respond_to?(:middleware)
+ config.middleware.insert_before("Rack::Runtime", Rails.cache.middleware)
end
end
end
- initializer :set_clear_dependencies_hook, :group => :all do
- ActionDispatch::Reloader.to_cleanup do
- ActiveSupport::DescendantsTracker.clear
- ActiveSupport::Dependencies.clear
- end
- end
-
# Sets the dependency loading mechanism.
# TODO: Remove files from the $" and always use require.
initializer :initialize_dependency_mechanism, :group => :all do
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 448521d2f0..a2e5dece16 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -1,17 +1,18 @@
-require 'active_support/core_ext/string/encoding'
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/file_update_checker'
require 'rails/engine/configuration'
module Rails
class Application
class Configuration < ::Rails::Engine::Configuration
- attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets,
- :cache_classes, :cache_store, :consider_all_requests_local,
- :dependency_loading, :filter_parameters,
- :force_ssl, :helpers_paths, :logger, :preload_frameworks,
- :reload_plugins, :secret_token, :serve_static_assets,
- :ssl_options, :static_cache_control, :session_options,
- :time_zone, :whiny_nils
+ attr_accessor :allow_concurrency, :asset_host, :asset_path, :assets, :autoflush_log,
+ :cache_classes, :cache_store, :consider_all_requests_local, :console,
+ :dependency_loading, :exceptions_app, :file_watcher, :filter_parameters,
+ :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags,
+ :preload_frameworks, :railties_order, :relative_url_root, :secret_token,
+ :serve_static_assets, :ssl_options, :static_cache_control, :session_options,
+ :time_zone, :reload_classes_only_on_change, :use_schema_cache_dump,
+ :queue, :queue_consumer
attr_writer :log_level
attr_reader :encoding
@@ -19,22 +20,32 @@ module Rails
def initialize(*)
super
self.encoding = "utf-8"
- @allow_concurrency = false
- @consider_all_requests_local = false
- @filter_parameters = []
- @helpers_paths = []
- @dependency_loading = true
- @serve_static_assets = true
- @static_cache_control = nil
- @force_ssl = false
- @ssl_options = {}
- @session_store = :cookie_store
- @session_options = {}
- @time_zone = "UTC"
- @log_level = nil
- @middleware = app_middleware
- @generators = app_generators
- @cache_store = [ :file_store, "#{root}/tmp/cache/" ]
+ @allow_concurrency = false
+ @consider_all_requests_local = false
+ @filter_parameters = []
+ @helpers_paths = []
+ @dependency_loading = true
+ @serve_static_assets = true
+ @static_cache_control = nil
+ @force_ssl = false
+ @ssl_options = {}
+ @session_store = :cookie_store
+ @session_options = {}
+ @time_zone = "UTC"
+ @log_level = nil
+ @middleware = app_middleware
+ @generators = app_generators
+ @cache_store = [ :file_store, "#{root}/tmp/cache/" ]
+ @railties_order = [:all]
+ @relative_url_root = ENV["RAILS_RELATIVE_URL_ROOT"]
+ @reload_classes_only_on_change = true
+ @file_watcher = ActiveSupport::FileUpdateChecker
+ @exceptions_app = nil
+ @autoflush_log = true
+ @log_formatter = ActiveSupport::Logger::SimpleFormatter.new
+ @use_schema_cache_dump = true
+ @queue = Rails::Queueing::Queue
+ @queue_consumer = Rails::Queueing::ThreadedConsumer
@assets = ActiveSupport::OrderedOptions.new
@assets.enabled = false
@@ -51,6 +62,7 @@ module Rails
@assets.js_compressor = nil
@assets.css_compressor = nil
@assets.initialize_on_precompile = true
+ @assets.logger = nil
end
def compiled_asset_path
@@ -59,17 +71,9 @@ module Rails
def encoding=(value)
@encoding = value
- if "ruby".encoding_aware?
- silence_warnings do
- Encoding.default_external = value
- Encoding.default_internal = value
- end
- else
- $KCODE = value
- if $KCODE == "NONE"
- raise "The value you specified for config.encoding is " \
- "invalid. The possible values are UTF8, SJIS, or EUC"
- end
+ silence_warnings do
+ Encoding.default_external = value
+ Encoding.default_internal = value
end
end
@@ -93,10 +97,10 @@ module Rails
# after boot, and disables reloading code on every request, as these are
# fundamentally incompatible with thread safety.
def threadsafe!
- self.preload_frameworks = true
- self.cache_classes = true
- self.dependency_loading = false
- self.allow_concurrency = true
+ @preload_frameworks = true
+ @cache_classes = true
+ @dependency_loading = false
+ @allow_concurrency = true
self
end
@@ -105,7 +109,7 @@ module Rails
# YAML::load.
def database_configuration
require 'erb'
- YAML::load(ERB.new(IO.read(paths["config/database"].first)).result)
+ YAML.load ERB.new(IO.read(paths["config/database"].first)).result
end
def log_level
@@ -113,11 +117,10 @@ module Rails
end
def colorize_logging
- @colorize_logging
+ ActiveSupport::LogSubscriber.colorize_logging
end
def colorize_logging=(val)
- @colorize_logging = val
ActiveSupport::LogSubscriber.colorize_logging = val
self.generators.colorize_logging = val
end
@@ -139,6 +142,11 @@ module Rails
@session_options = args.shift || {}
end
end
+
+ def whiny_nils=(*)
+ ActiveSupport::Deprecation.warn "config.whiny_nils option " \
+ "is deprecated and no longer works", caller
+ end
end
end
end
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index 028c8814c4..84f2601f28 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -19,16 +19,10 @@ module Rails
end
end
- initializer :add_to_prepare_blocks do
- config.to_prepare_blocks.each do |block|
- ActionDispatch::Reloader.to_prepare(&block)
- end
- end
-
initializer :add_builtin_route do |app|
if Rails.env.development?
app.routes.append do
- match '/rails/info/properties' => "rails/info#properties"
+ get '/rails/info/properties' => "rails/info#properties"
end
end
end
@@ -37,32 +31,60 @@ module Rails
build_middleware_stack
end
- initializer :run_prepare_callbacks do
- ActionDispatch::Reloader.prepare!
- end
-
initializer :define_main_app_helper do |app|
app.routes.define_mounted_helper(:main_app)
end
+ initializer :add_to_prepare_blocks do
+ config.to_prepare_blocks.each do |block|
+ ActionDispatch::Reloader.to_prepare(&block)
+ end
+ end
+
+ # This needs to happen before eager load so it happens
+ # in exactly the same point regardless of config.cache_classes
+ initializer :run_prepare_callbacks do
+ ActionDispatch::Reloader.prepare!
+ end
+
initializer :eager_load! do
- if config.cache_classes && !$rails_rake_task
+ if config.cache_classes && !(defined?($rails_rake_task) && $rails_rake_task)
ActiveSupport.run_load_hooks(:before_eager_load, self)
eager_load!
end
end
+ # All initialization is done, including eager loading in production
initializer :finisher_hook do
ActiveSupport.run_load_hooks(:after_initialize, self)
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
- ActionDispatch::Reloader.to_prepare(&reloader)
+ # Set app reload just after the finisher hook to ensure
+ # routes added in the hook are still loaded.
+ initializer :set_routes_reloader_hook do
+ reloader = routes_reloader
+ reloader.execute_if_updated
+ self.reloaders << reloader
+ ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
+ end
+
+ # Set app reload just after the finisher hook to ensure
+ # paths added in the hook are still loaded.
+ initializer :set_clear_dependencies_hook, :group => :all do
+ callback = lambda do
+ ActiveSupport::DescendantsTracker.clear
+ ActiveSupport::Dependencies.clear
+ end
+
+ if config.reload_classes_only_on_change
+ reloader = config.file_watcher.new(*watchable_args, &callback)
+ self.reloaders << reloader
+ # We need to set a to_prepare callback regardless of the reloader result, i.e.
+ # models should be reloaded if any of the reloaders (i18n, routes) were updated.
+ ActionDispatch::Reloader.to_prepare(:prepend => true){ reloader.execute }
+ else
+ ActionDispatch::Reloader.to_cleanup(&callback)
+ end
end
# Disable dependency loading during request cycle
@@ -71,6 +93,13 @@ module Rails
ActiveSupport::Dependencies.unhook!
end
end
+
+ initializer :activate_queue_consumer do |app|
+ if config.queue == Rails::Queueing::Queue
+ app.queue_consumer = config.queue_consumer.start(app.queue)
+ at_exit { app.queue_consumer.shutdown }
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb
index 8f3a3e8bbb..f20a9689de 100644
--- a/railties/lib/rails/application/railties.rb
+++ b/railties/lib/rails/application/railties.rb
@@ -4,7 +4,7 @@ module Rails
class Application < Engine
class Railties < Rails::Engine::Railties
def all(&block)
- @all ||= railties + engines + plugins
+ @all ||= railties + engines
@all.each(&block) if block
@all
end
diff --git a/railties/lib/rails/application/route_inspector.rb b/railties/lib/rails/application/route_inspector.rb
index 8c6911e6bb..b23fb3e920 100644
--- a/railties/lib/rails/application/route_inspector.rb
+++ b/railties/lib/rails/application/route_inspector.rb
@@ -1,35 +1,113 @@
+require 'delegate'
+
module Rails
class Application
+ class RouteWrapper < SimpleDelegator
+ def endpoint
+ rack_app ? rack_app.inspect : "#{controller}##{action}"
+ end
+
+ def constraints
+ requirements.except(:controller, :action)
+ end
+
+ def rack_app(app = self.app)
+ @rack_app ||= begin
+ class_name = app.class.name.to_s
+ if class_name == "ActionDispatch::Routing::Mapper::Constraints"
+ rack_app(app.app)
+ elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
+ app
+ end
+ end
+ end
+
+ def verb
+ super.source.gsub(/[$^]/, '')
+ end
+
+ def path
+ super.spec.to_s
+ end
+
+ def name
+ super.to_s
+ end
+
+ def reqs
+ @reqs ||= begin
+ reqs = endpoint
+ reqs += " #{constraints.inspect}" unless constraints.empty?
+ reqs
+ end
+ end
+
+ def controller
+ requirements[:controller] || ':controller'
+ end
+
+ def action
+ requirements[:action] || ':action'
+ end
+
+ def internal?
+ path =~ %r{/rails/info/properties|^#{Rails.application.config.assets.prefix}}
+ end
+
+ def engine?
+ rack_app && rack_app.respond_to?(:routes)
+ end
+ end
+
##
# This class is just used for displaying route information when someone
# executes `rake routes`. People should not use this class.
class RouteInspector # :nodoc:
- def format all_routes, filter = nil
+ def initialize
+ @engines = Hash.new
+ end
+
+ def format(all_routes, filter = nil)
if filter
all_routes = all_routes.select{ |route| route.defaults[:controller] == filter }
end
- routes = all_routes.collect do |route|
+ routes = collect_routes(all_routes)
- reqs = route.requirements.dup
- rack_app = route.app unless route.app.class.name.to_s =~ /^ActionDispatch::Routing/
+ formatted_routes(routes) +
+ formatted_routes_for_engines
+ end
- endpoint = rack_app ? rack_app.inspect : "#{reqs[:controller]}##{reqs[:action]}"
- constraints = reqs.except(:controller, :action)
+ def collect_routes(routes)
+ routes = routes.collect do |route|
+ RouteWrapper.new(route)
+ end.reject do |route|
+ route.internal?
+ end.collect do |route|
+ collect_engine_routes(route)
- reqs = endpoint == '#' ? '' : endpoint
+ {:name => route.name, :verb => route.verb, :path => route.path, :reqs => route.reqs }
+ end
+ end
- unless constraints.empty?
- reqs = reqs.empty? ? constraints.inspect : "#{reqs} #{constraints.inspect}"
- end
+ def collect_engine_routes(route)
+ name = route.endpoint
+ return unless route.engine?
+ return if @engines[name]
- verb = route.verb.source.gsub(/[$^]/, '')
- {:name => route.name.to_s, :verb => verb, :path => route.path.spec.to_s, :reqs => reqs}
+ routes = route.rack_app.routes
+ if routes.is_a?(ActionDispatch::Routing::RouteSet)
+ @engines[name] = collect_routes(routes.routes)
end
+ end
- # Skip the route if it's internal info route
- routes.reject! { |r| r[:path] =~ %r{/rails/info/properties|^/assets} }
+ def formatted_routes_for_engines
+ @engines.map do |name, routes|
+ ["\nRoutes for #{name}:"] + formatted_routes(routes)
+ end.flatten
+ end
+ def formatted_routes(routes)
name_width = routes.map{ |r| r[:name].length }.max
verb_width = routes.map{ |r| r[:verb].length }.max
path_width = routes.map{ |r| r[:path].length }.max
diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb
index 1d1f5e1b06..19f616ec50 100644
--- a/railties/lib/rails/application/routes_reloader.rb
+++ b/railties/lib/rails/application/routes_reloader.rb
@@ -1,11 +1,15 @@
+require "active_support/core_ext/module/delegation"
+
module Rails
class Application
- class RoutesReloader < ::ActiveSupport::FileUpdateChecker
- attr_reader :route_sets
+ class RoutesReloader
+ attr_reader :route_sets, :paths, :external_routes
+ delegate :execute_if_updated, :execute, :updated?, :to => :updater
def initialize
- super([]) { reload! }
- @route_sets = []
+ @paths = []
+ @route_sets = []
+ @external_routes = []
end
def reload!
@@ -16,7 +20,19 @@ module Rails
revert
end
- protected
+ private
+
+ def updater
+ @updater ||= begin
+ dirs = @external_routes.each_with_object({}) do |dir, hash|
+ hash[dir.to_s] = %w(rb)
+ end
+
+ updater = ActiveSupport::FileUpdateChecker.new(paths, dirs) { reload! }
+ updater.execute
+ updater
+ end
+ end
def clear!
route_sets.each do |routes|
@@ -31,7 +47,7 @@ module Rails
def finalize!
route_sets.each do |routes|
- ActiveSupport.on_load(:action_controller) { routes.finalize! }
+ routes.finalize!
end
end
diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb
index 36fd9aea19..8cc8eb1103 100644
--- a/railties/lib/rails/backtrace_cleaner.rb
+++ b/railties/lib/rails/backtrace_cleaner.rb
@@ -17,26 +17,11 @@ module Rails
private
def add_gem_filters
- return unless defined?(Gem)
-
- gems_paths = (Gem.path + [Gem.default_dir]).uniq.map!{ |p| Regexp.escape(p) }
+ gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
return if gems_paths.empty?
gems_regexp = %r{(#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)}
add_filter { |line| line.sub(gems_regexp, '\2 (\3) \4') }
end
end
-
- # For installing the BacktraceCleaner in the test/unit
- module BacktraceFilterForTestUnit #:nodoc:
- def self.included(klass)
- klass.send :alias_method_chain, :filter_backtrace, :cleaning
- end
-
- def filter_backtrace_with_cleaning(backtrace, prefix=nil)
- backtrace = filter_backtrace_without_cleaning(backtrace, prefix)
- backtrace = backtrace.first.split("\n") if backtrace.size == 1
- Rails.backtrace_cleaner.clean(backtrace)
- end
- end
end
diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index e6822b75b7..bcac0751d6 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -37,13 +37,26 @@ class CodeStatistics #:nodoc:
next unless file_name =~ pattern
- f = File.open(directory + "/" + file_name)
-
- while line = f.gets
- stats["lines"] += 1
- stats["classes"] += 1 if line =~ /class [A-Z]/
- stats["methods"] += 1 if line =~ /def [a-z]/
- stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/
+ comment_started = false
+
+ File.open(directory + "/" + file_name) do |f|
+ while line = f.gets
+ stats["lines"] += 1
+ if(comment_started)
+ if line =~ /^=end/
+ comment_started = false
+ end
+ next
+ else
+ if line =~ /^=begin/
+ comment_started = true
+ next
+ end
+ end
+ stats["classes"] += 1 if line =~ /^\s*class\s+[_A-Z]/
+ stats["methods"] += 1 if line =~ /^\s*def\s+[_a-z]/
+ stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ /^\s*#/
+ end
end
end
@@ -82,13 +95,7 @@ class CodeStatistics #:nodoc:
m_over_c = (statistics["methods"] / statistics["classes"]) rescue m_over_c = 0
loc_over_m = (statistics["codelines"] / statistics["methods"]) - 2 rescue loc_over_m = 0
- start = if TEST_TYPES.include? name
- "| #{name.ljust(20)} "
- else
- "| #{name.ljust(20)} "
- end
-
- puts start +
+ puts "| #{name.ljust(20)} " +
"| #{statistics["lines"].to_s.rjust(5)} " +
"| #{statistics["codelines"].to_s.rjust(5)} " +
"| #{statistics["classes"].to_s.rjust(7)} " +
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index ada150ceec..7f473c237c 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -57,16 +57,19 @@ when 'server'
when 'dbconsole'
require 'rails/commands/dbconsole'
- require APP_PATH
- Rails::DBConsole.start(Rails.application)
+ Rails::DBConsole.start
when 'application', 'runner'
require "rails/commands/#{command}"
when 'new'
- puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n"
- puts "Type 'rails' for help."
- exit(1)
+ if ARGV.first.in?(['-h', '--help'])
+ require 'rails/commands/application'
+ else
+ puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n"
+ puts "Type 'rails' for help."
+ exit(1)
+ end
when '--version', '-v'
ARGV.unshift '--version'
@@ -91,7 +94,7 @@ In addition to those, there are:
destroy Undo code generated with "generate" (short-cut alias: "d")
benchmarker See how fast a piece of code runs
profiler Get profile information from a piece of code
- plugin Install a plugin
+ plugin new Generates skeleton for developing a Rails plugin
runner Run a piece of code in the application environment (short-cut alias: "r")
All commands can be run with -h (or --help) for more information.
diff --git a/railties/lib/rails/commands/application.rb b/railties/lib/rails/commands/application.rb
index 60d1aed73a..2cb6d5ca2e 100644
--- a/railties/lib/rails/commands/application.rb
+++ b/railties/lib/rails/commands/application.rb
@@ -19,7 +19,6 @@ else
end
end
-require 'rubygems' if ARGV.include?("--dev")
require 'rails/generators'
require 'rails/generators/rails/app/app_generator'
diff --git a/railties/lib/rails/commands/benchmarker.rb b/railties/lib/rails/commands/benchmarker.rb
index 6c52d0f70f..b745b45e17 100644
--- a/railties/lib/rails/commands/benchmarker.rb
+++ b/railties/lib/rails/commands/benchmarker.rb
@@ -21,7 +21,7 @@ def options
options
end
-class BenchmarkerTest < ActionDispatch::PerformanceTest
+class BenchmarkerTest < ActionDispatch::PerformanceTest #:nodoc:
self.profile_options = options
ARGV.each do |expression|
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 32e361d421..cd6a03fe51 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -4,45 +4,81 @@ require 'irb/completion'
module Rails
class Console
- def self.start(app)
- new(app).start
- end
+ attr_reader :options, :app, :console, :arguments
- def initialize(app)
- @app = app
+ def self.start(*args)
+ new(*args).start
end
- def start
- options = {}
-
- OptionParser.new do |opt|
- opt.banner = "Usage: console [environment] [options]"
- opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v }
- opt.on("--debugger", 'Enable ruby-debugging for the console.') { |v| options[:debugger] = v }
- opt.on('--irb', "DEPRECATED: Invoke `/your/choice/of/ruby script/rails console` instead") { |v| abort '--irb option is no longer supported. Invoke `/your/choice/of/ruby script/rails console` instead' }
- opt.parse!(ARGV)
- end
+ def initialize(app, arguments = ARGV)
+ @app = app
+ @arguments = arguments
+ app.load_console
+ @console = app.config.console || IRB
+ end
- @app.sandbox = options[:sandbox]
- @app.load_console
+ def options
+ @options ||= begin
+ options = {}
- if options[:debugger]
- begin
- require 'ruby-debug'
- puts "=> Debugger enabled"
- rescue Exception
- puts "You need to install ruby-debug to run the console in debugging mode. With gems, use 'gem install ruby-debug'"
- exit
+ OptionParser.new do |opt|
+ opt.banner = "Usage: console [environment] [options]"
+ opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |v| options[:sandbox] = v }
+ opt.on("-e", "--environment=name", String,
+ "Specifies the environment to run this console under (test/development/production).",
+ "Default: development") { |v| options[:environment] = v.strip }
+ opt.on("--debugger", 'Enable the debugger.') { |v| options[:debugger] = v }
+ opt.parse!(arguments)
end
+
+ options
end
+ end
+
+ def sandbox?
+ options[:sandbox]
+ end
+
+ def environment?
+ options[:environment]
+ end
+
+ def set_environment!
+ Rails.env = options[:environment]
+ end
- if options[:sandbox]
+ def debugger?
+ options[:debugger]
+ end
+
+ def start
+ app.sandbox = sandbox?
+
+ require_debugger if debugger?
+
+ set_environment! if environment?
+
+ if sandbox?
puts "Loading #{Rails.env} environment in sandbox (Rails #{Rails.version})"
puts "Any modifications you make will be rolled back on exit"
else
puts "Loading #{Rails.env} environment (Rails #{Rails.version})"
end
- IRB.start
+
+ if defined?(console::ExtendCommandBundle)
+ console::ExtendCommandBundle.send :include, Rails::ConsoleMethods
+ end
+ console.start
+ end
+
+ def require_debugger
+ begin
+ require 'debugger'
+ puts "=> Debugger enabled"
+ rescue Exception
+ puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again."
+ exit
+ end
end
end
end
diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb
index b0ba76217a..aaba47117f 100644
--- a/railties/lib/rails/commands/dbconsole.rb
+++ b/railties/lib/rails/commands/dbconsole.rb
@@ -1,22 +1,41 @@
require 'erb'
-
-begin
- require 'psych'
-rescue LoadError
-end
-
require 'yaml'
require 'optparse'
require 'rbconfig'
module Rails
class DBConsole
- def self.start(app)
- new(app).start
+ attr_reader :arguments, :config
+
+ def self.start
+ new(config).start
end
- def initialize(app)
- @app = app
+ def self.config
+ config = begin
+ YAML.load(ERB.new(IO.read("config/database.yml")).result)
+ rescue SyntaxError, StandardError
+ require APP_PATH
+ Rails.application.config.database_configuration
+ end
+
+ unless config[env]
+ abort "No database is configured for the environment '#{env}'"
+ end
+
+ config[env]
+ end
+
+ def self.env
+ if Rails.respond_to?(:env)
+ Rails.env
+ else
+ ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
+ end
+ end
+
+ def initialize(config, arguments = ARGV)
+ @config, @arguments = config, arguments
end
def start
@@ -33,33 +52,15 @@ module Rails
options['mode'] = mode
end
- opt.on("-h", "--header") do |h|
+ opt.on("--header") do |h|
options['header'] = h
end
- opt.parse!(ARGV)
- abort opt.to_s unless (0..1).include?(ARGV.size)
- end
-
- unless config = YAML::load(ERB.new(IO.read("#{@app.root}/config/database.yml")).result)[Rails.env]
- abort "No database is configured for the environment '#{Rails.env}'"
+ opt.parse!(arguments)
+ abort opt.to_s unless (0..1).include?(arguments.size)
end
- def find_cmd(*commands)
- dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
- commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
-
- full_path_command = nil
- found = commands.detect do |cmd|
- dir = dirs_on_path.detect do |path|
- full_path_command = File.join(path, cmd)
- File.executable? full_path_command
- end
- end
- found ? full_path_command : abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
- end
-
case config["adapter"]
when /^mysql/
args = {
@@ -78,17 +79,17 @@ module Rails
args << config['database']
- exec(find_cmd('mysql', 'mysql5'), *args)
+ find_cmd_and_exec(['mysql', 'mysql5'], *args)
when "postgresql", "postgres"
ENV['PGUSER'] = config["username"] if config["username"]
ENV['PGHOST'] = config["host"] if config["host"]
ENV['PGPORT'] = config["port"].to_s if config["port"]
ENV['PGPASSWORD'] = config["password"].to_s if config["password"] && include_password
- exec(find_cmd('psql'), config["database"])
+ find_cmd_and_exec('psql', config["database"])
when "sqlite"
- exec(find_cmd('sqlite'), config["database"])
+ find_cmd_and_exec('sqlite', config["database"])
when "sqlite3"
args = []
@@ -97,7 +98,7 @@ module Rails
args << "-header" if options['header']
args << config['database']
- exec(find_cmd('sqlite3'), *args)
+ find_cmd_and_exec('sqlite3', *args)
when "oracle", "oracle_enhanced"
logon = ""
@@ -108,12 +109,35 @@ module Rails
logon << "@#{config['database']}" if config['database']
end
- exec(find_cmd('sqlplus'), logon)
+ find_cmd_and_exec('sqlplus', logon)
else
abort "Unknown command-line client for #{config['database']}. Submit a Rails patch to add support!"
end
end
+
+ protected
+
+ def find_cmd_and_exec(commands, *args)
+ commands = Array(commands)
+
+ dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR)
+ commands += commands.map{|cmd| "#{cmd}.exe"} if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
+
+ full_path_command = nil
+ found = commands.detect do |cmd|
+ dir = dirs_on_path.detect do |path|
+ full_path_command = File.join(path, cmd)
+ File.executable? full_path_command
+ end
+ end
+
+ if found
+ exec full_path_command, *args
+ else
+ abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
+ end
+ end
end
end
diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb
deleted file mode 100644
index 4df849447d..0000000000
--- a/railties/lib/rails/commands/plugin.rb
+++ /dev/null
@@ -1,542 +0,0 @@
-# Rails Plugin Manager.
-#
-# Installing plugins:
-#
-# $ rails plugin install continuous_builder asset_timestamping
-#
-# Specifying revisions:
-#
-# * Subversion revision is a single integer.
-#
-# * Git revision format:
-# - full - 'refs/tags/1.8.0' or 'refs/heads/experimental'
-# - short: 'experimental' (equivalent to 'refs/heads/experimental')
-# 'tag 1.8.0' (equivalent to 'refs/tags/1.8.0')
-#
-#
-# This is Free Software, copyright 2005 by Ryan Tomayko (rtomayko@gmail.com)
-# and is licensed MIT: (http://www.opensource.org/licenses/mit-license.php)
-
-$verbose = false
-
-require 'open-uri'
-require 'fileutils'
-require 'tempfile'
-
-include FileUtils
-
-class RailsEnvironment
- attr_reader :root
-
- def initialize(dir)
- @root = dir
- end
-
- def self.find(dir=nil)
- dir ||= pwd
- while dir.length > 1
- return new(dir) if File.exist?(File.join(dir, 'config', 'environment.rb'))
- dir = File.dirname(dir)
- end
- end
-
- def self.default
- @default ||= find
- end
-
- def self.default=(rails_env)
- @default = rails_env
- end
-
- def install(name_uri_or_plugin)
- if name_uri_or_plugin.is_a? String
- if name_uri_or_plugin =~ /:\/\//
- plugin = Plugin.new(name_uri_or_plugin)
- else
- plugin = Plugins[name_uri_or_plugin]
- end
- else
- plugin = name_uri_or_plugin
- end
- if plugin
- plugin.install
- else
- puts "Plugin not found: #{name_uri_or_plugin}"
- end
- end
-
- def use_svn?
- require 'active_support/core_ext/kernel'
- silence_stderr {`svn --version` rescue nil}
- !$?.nil? && $?.success?
- end
-
- def use_externals?
- use_svn? && File.directory?("#{root}/vendor/plugins/.svn")
- end
-
- def use_checkout?
- # this is a bit of a guess. we assume that if the rails environment
- # is under subversion then they probably want the plugin checked out
- # instead of exported. This can be overridden on the command line
- File.directory?("#{root}/.svn")
- end
-
- def best_install_method
- return :http unless use_svn?
- case
- when use_externals? then :externals
- when use_checkout? then :checkout
- else :export
- end
- end
-
- def externals
- return [] unless use_externals?
- ext = `svn propget svn:externals "#{root}/vendor/plugins"`
- lines = ext.respond_to?(:lines) ? ext.lines : ext
- lines.reject{ |line| line.strip == '' }.map do |line|
- line.strip.split(/\s+/, 2)
- end
- end
-
- def externals=(items)
- unless items.is_a? String
- items = items.map{|name,uri| "#{name.ljust(29)} #{uri.chomp('/')}"}.join("\n")
- end
- Tempfile.open("svn-set-prop") do |file|
- file.write(items)
- file.flush
- system("svn propset -q svn:externals -F \"#{file.path}\" \"#{root}/vendor/plugins\"")
- end
- end
-end
-
-class Plugin
- attr_reader :name, :uri
-
- def initialize(uri, name = nil)
- @uri = uri
- guess_name(uri)
- end
-
- def self.find(name)
- new(name)
- end
-
- def to_s
- "#{@name.ljust(30)}#{@uri}"
- end
-
- def svn_url?
- @uri =~ /svn(?:\+ssh)?:\/\/*/
- end
-
- def git_url?
- @uri =~ /^git:\/\// || @uri =~ /\.git$/
- end
-
- def installed?
- File.directory?("#{rails_env.root}/vendor/plugins/#{name}") \
- or rails_env.externals.detect{ |name, repo| self.uri == repo }
- end
-
- def install(method=nil, options = {})
- method ||= rails_env.best_install_method?
- if :http == method
- method = :export if svn_url?
- method = :git if git_url?
- end
-
- uninstall if installed? and options[:force]
-
- unless installed?
- send("install_using_#{method}", options)
- run_install_hook
- else
- puts "already installed: #{name} (#{uri}). pass --force to reinstall"
- end
- end
-
- def uninstall
- path = "#{rails_env.root}/vendor/plugins/#{name}"
- if File.directory?(path)
- puts "Removing 'vendor/plugins/#{name}'" if $verbose
- run_uninstall_hook
- rm_r path
- else
- puts "Plugin doesn't exist: #{path}"
- end
-
- if rails_env.use_externals?
- # clean up svn:externals
- externals = rails_env.externals
- externals.reject!{|n, u| name == n or name == u}
- rails_env.externals = externals
- end
- end
-
- def info
- tmp = "#{rails_env.root}/_tmp_about.yml"
- if svn_url?
- cmd = "svn export #{@uri} \"#{rails_env.root}/#{tmp}\""
- puts cmd if $verbose
- system(cmd)
- end
- open(svn_url? ? tmp : File.join(@uri, 'about.yml')) do |stream|
- stream.read
- end rescue "No about.yml found in #{uri}"
- ensure
- FileUtils.rm_rf tmp if svn_url?
- end
-
- private
-
- def run_install_hook
- install_hook_file = "#{rails_env.root}/vendor/plugins/#{name}/install.rb"
- load install_hook_file if File.exist? install_hook_file
- end
-
- def run_uninstall_hook
- uninstall_hook_file = "#{rails_env.root}/vendor/plugins/#{name}/uninstall.rb"
- load uninstall_hook_file if File.exist? uninstall_hook_file
- end
-
- def install_using_export(options = {})
- svn_command :export, options
- end
-
- def install_using_checkout(options = {})
- svn_command :checkout, options
- end
-
- def install_using_externals(options = {})
- externals = rails_env.externals
- externals.push([@name, uri])
- rails_env.externals = externals
- install_using_checkout(options)
- end
-
- def install_using_http(options = {})
- root = rails_env.root
- mkdir_p "#{root}/vendor/plugins/#{@name}"
- Dir.chdir "#{root}/vendor/plugins/#{@name}" do
- puts "fetching from '#{uri}'" if $verbose
- fetcher = RecursiveHTTPFetcher.new(uri, -1)
- fetcher.quiet = true if options[:quiet]
- fetcher.fetch
- end
- end
-
- def install_using_git(options = {})
- root = rails_env.root
- mkdir_p(install_path = "#{root}/vendor/plugins/#{name}")
- Dir.chdir install_path do
- init_cmd = "git init"
- init_cmd += " -q" if options[:quiet] and not $verbose
- puts init_cmd if $verbose
- system(init_cmd)
- base_cmd = "git pull --depth 1 #{uri}"
- base_cmd += " -q" if options[:quiet] and not $verbose
- base_cmd += " #{options[:revision]}" if options[:revision]
- puts base_cmd if $verbose
- if system(base_cmd)
- puts "removing: .git .gitignore" if $verbose
- rm_rf %w(.git .gitignore)
- else
- rm_rf install_path
- end
- end
- end
-
- def svn_command(cmd, options = {})
- root = rails_env.root
- mkdir_p "#{root}/vendor/plugins"
- base_cmd = "svn #{cmd} #{uri} \"#{root}/vendor/plugins/#{name}\""
- base_cmd += ' -q' if options[:quiet] and not $verbose
- base_cmd += " -r #{options[:revision]}" if options[:revision]
- puts base_cmd if $verbose
- system(base_cmd)
- end
-
- def guess_name(url)
- @name = File.basename(url)
- if @name == 'trunk' || @name.empty?
- @name = File.basename(File.dirname(url))
- end
- @name.gsub!(/\.git$/, '') if @name =~ /\.git$/
- end
-
- def rails_env
- @rails_env || RailsEnvironment.default
- end
-end
-
-# load default environment and parse arguments
-require 'optparse'
-module Commands
- class Plugin
- attr_reader :environment, :script_name
- def initialize
- @environment = RailsEnvironment.default
- @rails_root = RailsEnvironment.default.root
- @script_name = File.basename($0)
- end
-
- def environment=(value)
- @environment = value
- RailsEnvironment.default = value
- end
-
- def options
- OptionParser.new do |o|
- o.set_summary_indent(' ')
- o.banner = "Usage: plugin [OPTIONS] command"
- o.define_head "Rails plugin manager."
-
- o.separator ""
- o.separator "GENERAL OPTIONS"
-
- o.on("-r", "--root=DIR", String,
- "Set an explicit rails app directory.",
- "Default: #{@rails_root}") { |rails_root| @rails_root = rails_root; self.environment = RailsEnvironment.new(@rails_root) }
-
- o.on("-v", "--verbose", "Turn on verbose output.") { |verbose| $verbose = verbose }
- o.on("-h", "--help", "Show this help message.") { puts o; exit }
-
- o.separator ""
- o.separator "COMMANDS"
-
- o.separator " install Install plugin(s) from known repositories or URLs."
- o.separator " remove Uninstall plugins."
-
- o.separator ""
- o.separator "EXAMPLES"
- o.separator " Install a plugin from a subversion URL:"
- o.separator " #{@script_name} plugin install http://example.com/my_svn_plugin\n"
- o.separator " Install a plugin from a git URL:"
- o.separator " #{@script_name} plugin install git://github.com/SomeGuy/my_awesome_plugin.git\n"
- o.separator " Install a plugin and add a svn:externals entry to vendor/plugins"
- o.separator " #{@script_name} plugin install -x my_svn_plugin\n"
- end
- end
-
- def parse!(args=ARGV)
- general, sub = split_args(args)
- options.parse!(general)
-
- command = general.shift
- if command =~ /^(install|remove)$/
- command = Commands.const_get(command.capitalize).new(self)
- command.parse!(sub)
- else
- puts "Unknown command: #{command}" unless command.blank?
- puts options
- exit 1
- end
- end
-
- def split_args(args)
- left = []
- left << args.shift while args[0] and args[0] =~ /^-/
- left << args.shift if args[0]
- [left, args]
- end
-
- def self.parse!(args=ARGV)
- Plugin.new.parse!(args)
- end
- end
-
- class Install
- def initialize(base_command)
- @base_command = base_command
- @method = :http
- @options = { :quiet => false, :revision => nil, :force => false }
- end
-
- def options
- OptionParser.new do |o|
- o.set_summary_indent(' ')
- o.banner = "Usage: #{@base_command.script_name} install PLUGIN [PLUGIN [PLUGIN] ...]"
- o.define_head "Install one or more plugins."
- o.separator ""
- o.separator "Options:"
- o.on( "-x", "--externals",
- "Use svn:externals to grab the plugin.",
- "Enables plugin updates and plugin versioning.") { |v| @method = :externals }
- o.on( "-o", "--checkout",
- "Use svn checkout to grab the plugin.",
- "Enables updating but does not add a svn:externals entry.") { |v| @method = :checkout }
- o.on( "-e", "--export",
- "Use svn export to grab the plugin.",
- "Exports the plugin, allowing you to check it into your local repository. Does not enable updates or add an svn:externals entry.") { |v| @method = :export }
- o.on( "-q", "--quiet",
- "Suppresses the output from installation.",
- "Ignored if -v is passed (rails plugin -v install ...)") { |v| @options[:quiet] = true }
- o.on( "-r REVISION", "--revision REVISION",
- "Checks out the given revision from subversion or git.",
- "Ignored if subversion/git is not used.") { |v| @options[:revision] = v }
- o.on( "-f", "--force",
- "Reinstalls a plugin if it's already installed.") { |v| @options[:force] = true }
- o.separator ""
- o.separator "You can specify plugin names as given in 'plugin list' output or absolute URLs to "
- o.separator "a plugin repository."
- end
- end
-
- def determine_install_method
- best = @base_command.environment.best_install_method
- @method = :http if best == :http and @method == :export
- case
- when (best == :http and @method != :http)
- msg = "Cannot install using subversion because `svn' cannot be found in your PATH"
- when (best == :export and (@method != :export and @method != :http))
- msg = "Cannot install using #{@method} because this project is not under subversion."
- when (best != :externals and @method == :externals)
- msg = "Cannot install using externals because vendor/plugins is not under subversion."
- end
- if msg
- puts msg
- exit 1
- end
- @method
- end
-
- def parse!(args)
- options.parse!(args)
- if args.blank?
- puts options
- exit 1
- end
- environment = @base_command.environment
- install_method = determine_install_method
- puts "Plugins will be installed using #{install_method}" if $verbose
- args.each do |name|
- ::Plugin.find(name).install(install_method, @options)
- end
- rescue StandardError => e
- puts "Plugin not found: #{args.inspect}"
- puts e.inspect if $verbose
- exit 1
- end
- end
-
- class Remove
- def initialize(base_command)
- @base_command = base_command
- end
-
- def options
- OptionParser.new do |o|
- o.set_summary_indent(' ')
- o.banner = "Usage: #{@base_command.script_name} remove name [name]..."
- o.define_head "Remove plugins."
- end
- end
-
- def parse!(args)
- options.parse!(args)
- if args.blank?
- puts options
- exit 1
- end
- root = @base_command.environment.root
- args.each do |name|
- ::Plugin.new(name).uninstall
- end
- end
- end
-
- class Info
- def initialize(base_command)
- @base_command = base_command
- end
-
- def options
- OptionParser.new do |o|
- o.set_summary_indent(' ')
- o.banner = "Usage: #{@base_command.script_name} info name [name]..."
- o.define_head "Shows plugin info at {url}/about.yml."
- end
- end
-
- def parse!(args)
- options.parse!(args)
- args.each do |name|
- puts ::Plugin.find(name).info
- puts
- end
- end
- end
-end
-
-class RecursiveHTTPFetcher
- attr_accessor :quiet
- def initialize(urls_to_fetch, level = 1, cwd = ".")
- @level = level
- @cwd = cwd
- @urls_to_fetch = RUBY_VERSION >= '1.9' ? urls_to_fetch.lines : urls_to_fetch.to_a
- @quiet = false
- end
-
- def ls
- @urls_to_fetch.collect do |url|
- if url =~ /^svn(\+ssh)?:\/\/.*/
- `svn ls #{url}`.split("\n").map {|entry| "/#{entry}"} rescue nil
- else
- open(url) do |stream|
- links("", stream.read)
- end rescue nil
- end
- end.flatten
- end
-
- def push_d(dir)
- @cwd = File.join(@cwd, dir)
- FileUtils.mkdir_p(@cwd)
- end
-
- def pop_d
- @cwd = File.dirname(@cwd)
- end
-
- def links(base_url, contents)
- links = []
- contents.scan(/href\s*=\s*\"*[^\">]*/i) do |link|
- link = link.sub(/href="/i, "")
- next if link =~ /svnindex.xsl$/
- next if link =~ /^(\w*:|)\/\// || link =~ /^\./
- links << File.join(base_url, link)
- end
- links
- end
-
- def download(link)
- puts "+ #{File.join(@cwd, File.basename(link))}" unless @quiet
- open(link) do |stream|
- File.open(File.join(@cwd, File.basename(link)), "wb") do |file|
- file.write(stream.read)
- end
- end
- end
-
- def fetch(links = @urls_to_fetch)
- links.each do |l|
- (l =~ /\/$/ || links == @urls_to_fetch) ? fetch_dir(l) : download(l)
- end
- end
-
- def fetch_dir(url)
- @level += 1
- push_d(File.basename(url)) if @level > 0
- open(url) do |stream|
- contents = stream.read
- fetch(links(url, contents))
- end
- pop_d if @level > 0
- @level -= 1
- end
-end
-
-Commands::Plugin.parse!
diff --git a/railties/lib/rails/commands/plugin_new.rb b/railties/lib/rails/commands/plugin_new.rb
index 0287ba0638..4d7bf3c9f3 100644
--- a/railties/lib/rails/commands/plugin_new.rb
+++ b/railties/lib/rails/commands/plugin_new.rb
@@ -1,5 +1,3 @@
-require 'rubygems' if ARGV.include?("--dev")
-
if ARGV.first != "new"
ARGV[0] = "--help"
else
diff --git a/railties/lib/rails/commands/profiler.rb b/railties/lib/rails/commands/profiler.rb
index ea6347c918..3f6966b4f0 100644
--- a/railties/lib/rails/commands/profiler.rb
+++ b/railties/lib/rails/commands/profiler.rb
@@ -19,7 +19,7 @@ def options
options
end
-class ProfilerTest < ActionDispatch::PerformanceTest
+class ProfilerTest < ActionDispatch::PerformanceTest #:nodoc:
self.profile_options = options
ARGV.each do |expression|
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index e8cc5d9e3b..2802981e7a 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -9,7 +9,6 @@ if ARGV.first.nil?
end
ARGV.clone.options do |opts|
- script_name = File.basename($0)
opts.banner = "Usage: runner [options] ('Some.ruby(code)' or a filename)"
opts.separator ""
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 23392276d5..4c4caad69f 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -17,7 +17,7 @@ module Rails
opts.on("-c", "--config=file", String,
"Use custom rackup configuration file") { |v| options[:config] = v }
opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true }
- opts.on("-u", "--debugger", "Enable ruby-debugging for the server.") { options[:debugger] = true }
+ opts.on("-u", "--debugger", "Enable the debugger") { options[:debugger] = true }
opts.on("-e", "--environment=name", String,
"Specifies the environment to run this server under (test/development/production).",
"Default: development") { |v| options[:environment] = v }
@@ -32,6 +32,11 @@ module Rails
opt_parser.parse! args
+ # Handle's environment like RAILS_ENV=production passed in directly
+ if index = args.index {|arg| arg.include?("RAILS_ENV")}
+ options[:environment] ||= args.delete_at(index).split('=').last
+ end
+
options[:server] = args.shift
options
end
@@ -67,6 +72,15 @@ module Rails
FileUtils.mkdir_p(Rails.root.join('tmp', dir_to_make))
end
+ unless options[:daemonize]
+ wrapped_app # touch the app so the logger is set up
+
+ console = ActiveSupport::Logger.new($stdout)
+ console.formatter = Rails.logger.formatter
+
+ Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
+ end
+
super
ensure
# The '-h' option calls exit before @options is set.
@@ -76,9 +90,8 @@ module Rails
def middleware
middlewares = []
- middlewares << [Rails::Rack::LogTailer, log_path] unless options[:daemonize]
middlewares << [Rails::Rack::Debugger] if options[:debugger]
- middlewares << [Rails::Rack::ContentLength]
+ middlewares << [::Rack::ContentLength]
Hash.new(middlewares)
end
@@ -89,6 +102,7 @@ module Rails
def default_options
super.merge({
:Port => 3000,
+ :DoNotReverseLookup => true,
:environment => (ENV['RAILS_ENV'] || "development").dup,
:daemonize => false,
:debugger => false,
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index f8ad17773a..3d66019e5e 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -1,12 +1,39 @@
require 'active_support/deprecation'
require 'active_support/ordered_options'
-require 'active_support/core_ext/hash/deep_dup'
+require 'active_support/core_ext/object'
require 'rails/paths'
require 'rails/rack'
module Rails
module Configuration
- class MiddlewareStackProxy #:nodoc:
+ # MiddlewareStackProxy is a proxy for the Rails middleware stack that allows
+ # you to configure middlewares in your application. It works basically as a
+ # command recorder, saving each command to be applied after initialization
+ # over the default middleware stack, so you can add, swap, or remove any
+ # middleware in Rails.
+ #
+ # You can add your own middlewares by using the +config.middleware.use+ method:
+ #
+ # config.middleware.use Magical::Unicorns
+ #
+ # This will put the <tt>Magical::Unicorns</tt> middleware on the end of the stack.
+ # You can use +insert_before+ if you wish to add a middleware before another:
+ #
+ # config.middleware.insert_before ActionDispatch::Head, Magical::Unicorns
+ #
+ # There's also +insert_after+ which will insert a middleware after another:
+ #
+ # config.middleware.insert_after ActionDispatch::Head, Magical::Unicorns
+ #
+ # Middlewares can also be completely swapped out and replaced with others:
+ #
+ # config.middleware.swap ActionDispatch::BestStandardsSupport, Magical::Unicorns
+ #
+ # And finally they can also be removed from the stack completely:
+ #
+ # config.middleware.delete ActionDispatch::BestStandardsSupport
+ #
+ class MiddlewareStackProxy
def initialize
@operations = []
end
@@ -33,7 +60,7 @@ module Rails
@operations << [:delete, args, block]
end
- def merge_into(other)
+ def merge_into(other) #:nodoc:
@operations.each do |operation, args, block|
other.send(operation, *args, &block)
end
diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb
index 95c74baae2..ee8bb55f38 100644
--- a/railties/lib/rails/console/app.rb
+++ b/railties/lib/rails/console/app.rb
@@ -3,30 +3,33 @@ require 'active_support/test_case'
require 'action_controller'
# work around the at_exit hook in test/unit, which kills IRB
-Test::Unit.run = true if Test::Unit.respond_to?(:run=)
-# reference the global "app" instance, created on demand. To recreate the
-# instance, pass a non-false value as the parameter.
-def app(create=false)
- @app_integration_instance = nil if create
- @app_integration_instance ||= new_session do |sess|
- sess.host! "www.example.com"
- end
-end
+module Rails
+ module ConsoleMethods
+ # reference the global "app" instance, created on demand. To recreate the
+ # instance, pass a non-false value as the parameter.
+ def app(create=false)
+ @app_integration_instance = nil if create
+ @app_integration_instance ||= new_session do |sess|
+ sess.host! "www.example.com"
+ end
+ end
-# create a new session. If a block is given, the new session will be yielded
-# to the block before being returned.
-def new_session
- app = Rails.application
- session = ActionDispatch::Integration::Session.new(app)
- yield session if block_given?
- session
-end
+ # create a new session. If a block is given, the new session will be yielded
+ # to the block before being returned.
+ def new_session
+ app = Rails.application
+ session = ActionDispatch::Integration::Session.new(app)
+ yield session if block_given?
+ session
+ end
-# reloads the environment
-def reload!(print=true)
- puts "Reloading..." if print
- ActionDispatch::Reloader.cleanup!
- ActionDispatch::Reloader.prepare!
- true
+ # reloads the environment
+ def reload!(print=true)
+ puts "Reloading..." if print
+ ActionDispatch::Reloader.cleanup!
+ ActionDispatch::Reloader.prepare!
+ true
+ end
+ end
end
diff --git a/railties/lib/rails/console/helpers.rb b/railties/lib/rails/console/helpers.rb
index 212fc6125a..230d3d9d04 100644
--- a/railties/lib/rails/console/helpers.rb
+++ b/railties/lib/rails/console/helpers.rb
@@ -1,7 +1,11 @@
-def helper
- @helper ||= ApplicationController.helpers
-end
+module Rails
+ module ConsoleMethods
+ def helper
+ @helper ||= ApplicationController.helpers
+ end
-def controller
- @controller ||= ApplicationController.new
+ def controller
+ @controller ||= ApplicationController.new
+ end
+ end
end
diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb
new file mode 100644
index 0000000000..c5811b2629
--- /dev/null
+++ b/railties/lib/rails/deprecation.rb
@@ -0,0 +1,18 @@
+require 'active_support/deprecation/proxy_wrappers'
+
+module Rails
+ class DeprecatedConstant < ActiveSupport::Deprecation::DeprecatedConstantProxy
+ def self.deprecate(old, current)
+ constant = new(old, current)
+ eval "::#{old} = constant"
+ end
+
+ private
+
+ def target
+ ::Kernel.eval @new_const.to_s
+ end
+ end
+
+ DeprecatedConstant.deprecate('RAILS_CACHE', '::Rails.cache')
+end
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 2d25273050..47856c87c6 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -39,8 +39,6 @@ module Rails
# and <tt>autoload_once_paths</tt>, which, differently from a <tt>Railtie</tt>, are scoped to
# the current engine.
#
- # Example:
- #
# class MyEngine < Rails::Engine
# # Add a load path for this specific Engine
# config.autoload_paths << File.expand_path("../lib/some/path", __FILE__)
@@ -148,7 +146,7 @@ module Rails
#
# # ENGINE/config/routes.rb
# MyEngine::Engine.routes.draw do
- # match "/" => "posts#index"
+ # get "/" => "posts#index"
# end
#
# == Mount priority
@@ -158,7 +156,7 @@ module Rails
#
# MyRailsApp::Application.routes.draw do
# mount MyEngine::Engine => "/blog"
- # match "/blog/omg" => "main#omg"
+ # get "/blog/omg" => "main#omg"
# end
#
# +MyEngine+ is mounted at <tt>/blog</tt>, and <tt>/blog/omg</tt> points to application's
@@ -167,7 +165,7 @@ module Rails
# It's much better to swap that:
#
# MyRailsApp::Application.routes.draw do
- # match "/blog/omg" => "main#omg"
+ # get "/blog/omg" => "main#omg"
# mount MyEngine::Engine => "/blog"
# end
#
@@ -228,7 +226,7 @@ module Rails
# resources :articles
# end
#
- # The routes above will automatically point to <tt>MyEngine::ApplicationContoller</tt>. Furthermore, you don't
+ # The routes above will automatically point to <tt>MyEngine::ArticlesController</tt>. Furthermore, you don't
# need to use longer url helpers like <tt>my_engine_articles_path</tt>. Instead, you should simply use
# <tt>articles_path</tt> as you would do with your application.
#
@@ -245,7 +243,7 @@ module Rails
#
# Additionally, an isolated engine will set its name according to namespace, so
# MyEngine::Engine.engine_name will be "my_engine". It will also set MyEngine.table_name_prefix
- # to "my_engine_", changing the MyEngine::Article model to use the my_engine_article table.
+ # to "my_engine_", changing the MyEngine::Article model to use the my_engine_articles table.
#
# == Using Engine's routes outside Engine
#
@@ -256,7 +254,7 @@ module Rails
# # config/routes.rb
# MyApplication::Application.routes.draw do
# mount MyEngine::Engine => "/my_engine", :as => "my_engine"
- # match "/foo" => "foo#index"
+ # get "/foo" => "foo#index"
# end
#
# Now, you can use the <tt>my_engine</tt> helper inside your application:
@@ -296,16 +294,16 @@ module Rails
# If you want to share just a few specific helpers you can add them to application's
# helpers in ApplicationController:
#
- # class ApplicationController < ActionController::Base
- # helper MyEngine::SharedEngineHelper
- # end
+ # class ApplicationController < ActionController::Base
+ # helper MyEngine::SharedEngineHelper
+ # end
#
- # If you want to include all of the engine's helpers, you can use #helpers method on an engine's
+ # If you want to include all of the engine's helpers, you can use #helper method on an engine's
# instance:
#
- # class ApplicationController < ActionController::Base
- # helper MyEngine::Engine.helpers
- # end
+ # class ApplicationController < ActionController::Base
+ # helper MyEngine::Engine.helpers
+ # end
#
# It will include all of the helpers from engine's directory. Take into account that this does
# not include helpers defined in controllers with helper_method or other similar solutions,
@@ -326,10 +324,18 @@ module Rails
# migration in the application and rerun copying migrations.
#
# If your engine has migrations, you may also want to prepare data for the database in
- # the <tt>seeds.rb</tt> file. You can load that data using the <tt>load_seed</tt> method, e.g.
+ # the <tt>db/seeds.rb</tt> file. You can load that data using the <tt>load_seed</tt> method, e.g.
#
# MyEngine::Engine.load_seed
#
+ # == Loading priority
+ #
+ # In order to change engine's priority you can use +config.railties_order+ in main application.
+ # It will affect the priority of loading views, helpers, assets and all the other files
+ # related to engine or application.
+ #
+ # # load Blog::Engine with highest priority, followed by application and other railties
+ # config.railties_order = [Blog::Engine, :main_app, :all]
class Engine < Railtie
autoload :Configuration, "rails/engine/configuration"
autoload :Railties, "rails/engine/railties"
@@ -371,20 +377,28 @@ module Rails
self.routes.default_scope = { :module => ActiveSupport::Inflector.underscore(mod.name) }
self.isolated = true
- unless mod.respond_to?(:_railtie)
- name = engine_name
- _railtie = self
+ unless mod.respond_to?(:railtie_namespace)
+ name, railtie = engine_name, self
+
mod.singleton_class.instance_eval do
- define_method(:_railtie) do
- _railtie
- end
+ define_method(:railtie_namespace) { railtie }
unless mod.respond_to?(:table_name_prefix)
- define_method(:table_name_prefix) do
- "#{name}_"
- end
+ define_method(:table_name_prefix) { "#{name}_" }
+ end
+
+ unless mod.respond_to?(:use_relative_model_naming?)
+ class_eval "def use_relative_model_naming?; true; end", __FILE__, __LINE__
+ end
+
+ unless mod.respond_to?(:railtie_helpers_paths)
+ define_method(:railtie_helpers_paths) { railtie.helpers_paths }
+ end
+
+ unless mod.respond_to?(:railtie_routes_url_helpers)
+ define_method(:railtie_routes_url_helpers) { railtie.routes_url_helpers }
end
- end
+ end
end
end
@@ -429,13 +443,6 @@ module Rails
def helpers
@helpers ||= begin
helpers = Module.new
-
- helpers_paths = if config.respond_to?(:helpers_paths)
- config.helpers_paths
- else
- paths["app/helpers"].existent
- end
-
all = ActionController::Base.all_helpers_from_path(helpers_paths)
ActionController::Base.modules_for_helpers(all).each do |mod|
helpers.send(:include, mod)
@@ -444,6 +451,14 @@ module Rails
end
end
+ def helpers_paths
+ paths["app/helpers"].existent
+ end
+
+ def routes_url_helpers
+ routes.url_helpers
+ end
+
def app
@app ||= begin
config.middleware = config.middleware.merge_into(default_middleware_stack)
@@ -466,15 +481,27 @@ module Rails
end
def routes
- @routes ||= ActionDispatch::Routing::RouteSet.new
+ @routes ||= ActionDispatch::Routing::RouteSet.new.tap do |routes|
+ routes.draw_paths.concat paths["config/routes"].paths
+ end
+
@routes.append(&Proc.new) if block_given?
@routes
end
+ def ordered_railties
+ railties.all + [self]
+ end
+
def initializers
initializers = []
- railties.all { |r| initializers += r.initializers }
- initializers += super
+ ordered_railties.each do |r|
+ if r == self
+ initializers += super
+ else
+ initializers += r.initializers
+ end
+ end
initializers
end
@@ -487,8 +514,8 @@ module Rails
#
# Blog::Engine.load_seed
def load_seed
- seed_file = paths["db/seeds"].existent.first
- load(seed_file) if File.exist?(seed_file)
+ seed_file = paths["db/seeds.rb"].existent.first
+ load(seed_file) if seed_file
end
# Add configured load paths to ruby load paths and remove duplicates.
@@ -515,11 +542,13 @@ module Rails
end
initializer :add_routing_paths do |app|
- paths = self.paths["config/routes"].existent
+ paths = self.paths["config/routes.rb"].existent
+ external_paths = self.paths["config/routes"].paths
if routes? || paths.any?
app.routes_reloader.paths.unshift(*paths)
app.routes_reloader.route_sets << routes
+ app.routes_reloader.external_routes.unshift(*external_paths)
end
end
@@ -532,14 +561,15 @@ module Rails
initializer :add_view_paths do
views = paths["app/views"].existent
unless views.empty?
- ActiveSupport.on_load(:action_controller){ prepend_view_path(views) }
+ ActiveSupport.on_load(:action_controller){ prepend_view_path(views) if respond_to?(:prepend_view_path) }
ActiveSupport.on_load(:action_mailer){ prepend_view_path(views) }
end
end
initializer :load_environment_config, :before => :load_environment_hook, :group => :all do
- environment = paths["config/environments"].existent.first
- require environment if environment
+ paths["config/environments"].existent.each do |environment|
+ require environment
+ end
end
initializer :append_assets_path, :group => :all do |app|
@@ -574,7 +604,12 @@ module Rails
desc "Copy migrations from #{railtie_name} to application"
task :migrations do
ENV["FROM"] = railtie_name
- Rake::Task["railties:install:migrations"].invoke
+ if Rake::Task.task_defined?("railties:install:migrations")
+ Rake::Task["railties:install:migrations"].invoke
+ else
+ Rake::Task["app:railties:install:migrations"].invoke
+ end
+
end
end
end
@@ -587,7 +622,7 @@ module Rails
end
def routes?
- defined?(@routes)
+ defined?(@routes) && @routes
end
def has_migrations?
diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb
index b71119af77..ffbc0b4bd6 100644
--- a/railties/lib/rails/engine/commands.rb
+++ b/railties/lib/rails/engine/commands.rb
@@ -34,6 +34,10 @@ The common rails commands available for engines are:
destroy Undo code generated with "generate" (short-cut alias: "d")
All commands can be run with -h for more information.
+
+If you want to run any commands that need to be run in context
+of the application, like `rails server` or `rails console`,
+you should do it from application's directory (typically test/dummy).
EOT
exit(1)
end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 99e0201e31..e31df807a6 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -5,7 +5,6 @@ module Rails
class Configuration < ::Rails::Railtie::Configuration
attr_reader :root
attr_writer :middleware, :eager_load_paths, :autoload_once_paths, :autoload_paths
- attr_accessor :plugins
def initialize(root=nil)
super()
@@ -30,7 +29,7 @@ module Rails
#
# config.generators.colorize_logging = false
#
- def generators #:nodoc
+ def generators #:nodoc:
@generators ||= Rails::Configuration::Generators.new
yield(@generators) if block_given?
@generators
@@ -53,13 +52,13 @@ module Rails
paths.add "config/environments", :glob => "#{Rails.env}.rb"
paths.add "config/initializers", :glob => "**/*.rb"
paths.add "config/locales", :glob => "*.{rb,yml}"
- paths.add "config/routes", :with => "config/routes.rb"
+ paths.add "config/routes.rb"
+ paths.add "config/routes", :glob => "**/*.rb"
paths.add "db"
paths.add "db/migrate"
- paths.add "db/seeds", :with => "db/seeds.rb"
+ paths.add "db/seeds.rb"
paths.add "vendor", :load_path => true
paths.add "vendor/assets", :glob => "*"
- paths.add "vendor/plugins"
paths
end
end
diff --git a/railties/lib/rails/engine/railties.rb b/railties/lib/rails/engine/railties.rb
index d5ecd2e48d..033d9c4180 100644
--- a/railties/lib/rails/engine/railties.rb
+++ b/railties/lib/rails/engine/railties.rb
@@ -7,18 +7,11 @@ module Rails
end
def all(&block)
- @all ||= plugins
+ @all ||= []
@all.each(&block) if block
@all
end
- def plugins
- @plugins ||= begin
- plugin_names = (@config.plugins || [:all]).map { |p| p.to_sym }
- Plugin.all(plugin_names, @config.paths["vendor/plugins"].existent)
- end
- end
-
def self.railties
@railties ||= ::Rails::Railtie.subclasses.map(&:instance)
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 27f8d13ce8..4fa990171d 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -38,11 +38,6 @@ module Rails
:test_unit => {
:fixture_replacement => '-r',
- },
-
- :plugin => {
- :generator => '-g',
- :tasks => '-r'
}
}
@@ -57,16 +52,12 @@ module Rails
:orm => false,
:performance_tool => nil,
:resource_controller => :controller,
+ :resource_route => true,
:scaffold_controller => :scaffold_controller,
:stylesheets => true,
:stylesheet_engine => :css,
:test_framework => false,
:template_engine => :erb
- },
-
- :plugin => {
- :generator => false,
- :tasks => false
}
}
@@ -80,7 +71,7 @@ module Rails
hide_namespaces(*config.hidden_namespaces)
end
- def self.templates_path
+ def self.templates_path #:nodoc:
@templates_path ||= []
end
@@ -104,7 +95,6 @@ module Rails
# some of them are not available by adding a fallback:
#
# Rails::Generators.fallbacks[:shoulda] = :test_unit
- #
def self.fallbacks
@fallbacks ||= {}
end
@@ -124,8 +114,6 @@ module Rails
# Generators names must end with "_generator.rb". This is required because Rails
# looks in load paths and loads the generator just before it's going to be used.
#
- # ==== Examples
- #
# find_by_namespace :webrat, :rails, :integration
#
# Will search for the following generators:
@@ -134,7 +122,6 @@ module Rails
#
# Notice that "rails:generators:webrat" could be loaded as well, what
# Rails looks for is the first and last parts of the namespace.
- #
def self.find_by_namespace(name, base=nil, context=nil) #:nodoc:
lookups = []
lookups << "#{base}:#{name}" if base
@@ -182,6 +169,7 @@ module Rails
[
"rails",
+ "resource_route",
"#{orm}:migration",
"#{orm}:model",
"#{orm}:observer",
@@ -195,7 +183,6 @@ module Rails
"#{test}:scaffold",
"#{test}:view",
"#{test}:performance",
- "#{test}:plugin",
"#{template}:controller",
"#{template}:scaffold",
"#{template}:mailer",
@@ -246,7 +233,7 @@ module Rails
rails.delete("plugin_new")
print_list("rails", rails)
- hidden_namespaces.each {|n| groups.delete(n.to_s) }
+ hidden_namespaces.each { |n| groups.delete(n.to_s) }
groups.sort.each { |b, n| print_list(b, n) }
end
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index b26839644e..6cd2ea2bbd 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -5,49 +5,12 @@ module Rails
module Generators
module Actions
- # Install a plugin. You must provide either a Subversion url or Git url.
- #
- # For a Git-hosted plugin, you can specify a branch and
- # whether it should be added as a submodule instead of cloned.
- #
- # For a Subversion-hosted plugin you can specify a revision.
- #
- # ==== Examples
- #
- # plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git'
- # plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git', :branch => 'stable'
- # plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git', :submodule => true
- # plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk'
- # plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk', :revision => 1234
- #
- def plugin(name, options)
- log :plugin, name
-
- if options[:git] && options[:submodule]
- options[:git] = "-b #{options[:branch]} #{options[:git]}" if options[:branch]
- in_root do
- run "git submodule add #{options[:git]} vendor/plugins/#{name}", :verbose => false
- end
- elsif options[:git] || options[:svn]
- options[:git] = "-b #{options[:branch]} #{options[:git]}" if options[:branch]
- options[:svn] = "-r #{options[:revision]} #{options[:svn]}" if options[:revision]
- in_root do
- run_ruby_script "script/rails plugin install #{options[:svn] || options[:git]}", :verbose => false
- end
- else
- log "! no git or svn provided for #{name}. Skipping..."
- end
- end
-
# Adds an entry into Gemfile for the supplied gem. If env
# is specified, add the gem to the given environment.
#
- # ==== Example
- #
# gem "rspec", :group => :test
# gem "technoweenie-restful-authentication", :lib => "restful-authentication", :source => "http://gems.github.com/"
# gem "rails", "3.0", :git => "git://github.com/rails/rails"
- #
def gem(*args)
options = args.extract_options!
name, version = args
@@ -68,39 +31,35 @@ module Rails
end
in_root do
- str = "gem #{parts.join(", ")}\n"
+ str = "gem #{parts.join(", ")}"
str = " " + str if @in_group
+ str = "\n" + str
append_file "Gemfile", str, :verbose => false
end
end
# Wraps gem entries inside a group.
#
- # ==== Example
- #
# gem_group :development, :test do
# gem "rspec-rails"
# end
- #
def gem_group(*names, &block)
name = names.map(&:inspect).join(", ")
log :gemfile, "group #{name}"
in_root do
- append_file "Gemfile", "\ngroup #{name} do\n", :force => true
+ append_file "Gemfile", "\ngroup #{name} do", :force => true
@in_group = true
instance_eval(&block)
@in_group = false
- append_file "Gemfile", "end\n", :force => true
+ append_file "Gemfile", "\nend\n", :force => true
end
end
# Add the given source to Gemfile
#
- # ==== Example
- #
# add_source "http://gems.github.com/"
def add_source(source, options={})
log :source, source
@@ -115,6 +74,13 @@ module Rails
# If options :env is specified, the line is appended to the corresponding
# file in config/environments.
#
+ # environment do
+ # "config.autoload_paths += %W(#{config.root}/extras)"
+ # end
+ #
+ # environment(nil, :env => "development") do
+ # "config.active_record.observers = :cacher"
+ # end
def environment(data=nil, options={}, &block)
sentinel = /class [a-z_:]+ < Rails::Application/i
env_file_sentinel = /::Application\.configure do/
@@ -124,7 +90,7 @@ module Rails
if options[:env].nil?
inject_into_file 'config/application.rb', "\n #{data}", :after => sentinel, :verbose => false
else
- Array.wrap(options[:env]).each do |env|
+ Array(options[:env]).each do |env|
inject_into_file "config/environments/#{env}.rb", "\n #{data}", :after => env_file_sentinel, :verbose => false
end
end
@@ -134,12 +100,9 @@ module Rails
# Run a command in git.
#
- # ==== Examples
- #
# git :init
# git :add => "this.file that.rb"
# git :add => "onefile.rb", :rm => "badfile.cxx"
- #
def git(commands={})
if commands.is_a?(Symbol)
run "git #{commands}"
@@ -153,15 +116,12 @@ module Rails
# Create a new file in the vendor/ directory. Code can be specified
# in a block or a data string can be given.
#
- # ==== Examples
- #
# vendor("sekrit.rb") do
# sekrit_salt = "#{Time.now}--#{3.years.ago}--#{rand}--"
# "salt = '#{sekrit_salt}'"
# end
#
# vendor("foreign.rb", "# Foreign code is fun")
- #
def vendor(filename, data=nil, &block)
log :vendor, filename
create_file("vendor/#{filename}", data, :verbose => false, &block)
@@ -170,14 +130,11 @@ module Rails
# Create a new file in the lib/ directory. Code can be specified
# in a block or a data string can be given.
#
- # ==== Examples
- #
# lib("crypto.rb") do
# "crypted_special_value = '#{rand}--#{Time.now}--#{rand(1337)}--'"
# end
#
# lib("foreign.rb", "# Foreign code is fun")
- #
def lib(filename, data=nil, &block)
log :lib, filename
create_file("lib/#{filename}", data, :verbose => false, &block)
@@ -185,22 +142,19 @@ module Rails
# Create a new Rakefile with the provided code (either in a block or a string).
#
- # ==== Examples
- #
# rakefile("bootstrap.rake") do
# project = ask("What is the UNIX name of your project?")
#
# <<-TASK
# namespace :#{project} do
# task :bootstrap do
- # puts "i like boots!"
+ # puts "I like boots!"
# end
# end
# TASK
# end
#
- # rakefile("seed.rake", "puts 'im plantin ur seedz'")
- #
+ # rakefile('seed.rake', 'puts "Planting seeds"')
def rakefile(filename, data=nil, &block)
log :rakefile, filename
create_file("lib/tasks/#{filename}", data, :verbose => false, &block)
@@ -208,8 +162,6 @@ module Rails
# Create a new initializer with the provided code (either in a block or a string).
#
- # ==== Examples
- #
# initializer("globals.rb") do
# data = ""
#
@@ -221,7 +173,6 @@ module Rails
# end
#
# initializer("api.rb", "API_KEY = '123456'")
- #
def initializer(filename, data=nil, &block)
log :initializer, filename
create_file("config/initializers/#{filename}", data, :verbose => false, &block)
@@ -231,10 +182,7 @@ module Rails
# The second parameter is the argument string that is passed to
# the generator or an Array that is joined.
#
- # ==== Example
- #
# generate(:authenticated, "user session")
- #
def generate(what, *args)
log :generate, what
argument = args.map {|arg| arg.to_s }.flatten.join(" ")
@@ -244,12 +192,9 @@ module Rails
# Runs the supplied rake task
#
- # ==== Example
- #
# rake("db:migrate")
# rake("db:migrate", :env => "production")
# rake("gems:install", :sudo => true)
- #
def rake(command, options={})
log :rake, command
env = options[:env] || ENV["RAILS_ENV"] || 'development'
@@ -259,10 +204,7 @@ module Rails
# Just run the capify command in root
#
- # ==== Example
- #
# capify!
- #
def capify!
log :capify, ""
in_root { run("#{extify(:capify)} .", :verbose => false) }
@@ -270,25 +212,19 @@ module Rails
# Make an entry in Rails routing file config/routes.rb
#
- # === Example
- #
- # route "root :to => 'welcome'"
- #
+ # route "root :to => 'welcome#index'"
def route(routing_code)
log :route, routing_code
- sentinel = /\.routes\.draw do(?:\s*\|map\|)?\s*$/
+ sentinel = /\.routes\.draw do\s*$/
in_root do
- inject_into_file 'config/routes.rb', "\n #{routing_code}\n", { :after => sentinel, :verbose => false }
+ inject_into_file 'config/routes.rb', "\n #{routing_code}", { :after => sentinel, :verbose => false }
end
end
# Reads the given file at the source root and prints it in the console.
#
- # === Example
- #
# readme "README"
- #
def readme(path)
log File.read(find_in_source_paths(path))
end
@@ -298,7 +234,6 @@ module Rails
# Define log for backwards compatibility. If just one argument is sent,
# invoke say, otherwise invoke say_status. Differently from say and
# similarly to say_status, this method respects the quiet? option given.
- #
def log(*args)
if args.size == 1
say args.first.to_s unless options.quiet?
@@ -309,7 +244,6 @@ module Rails
end
# Add an extension to the given name based on the platform.
- #
def extify(name)
if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
"#{name}.bat"
diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb
index 7af893e1fd..0e51b9c568 100644
--- a/railties/lib/rails/generators/active_model.rb
+++ b/railties/lib/rails/generators/active_model.rb
@@ -37,7 +37,7 @@ module Rails
# GET show
# GET edit
- # PUT update
+ # PATCH/PUT update
# DELETE destroy
def self.find(klass, params=nil)
"#{klass}.find(#{params})"
@@ -58,13 +58,13 @@ module Rails
"#{name}.save"
end
- # PUT update
+ # PATCH/PUT update
def update_attributes(params=nil)
"#{name}.update_attributes(#{params})"
end
# POST create
- # PUT update
+ # PATCH/PUT update
def errors
"#{name}.errors"
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 134d86fab0..2c1742c6be 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -49,6 +49,9 @@ module Rails
class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false,
:desc => "Skip JavaScript files"
+ class_option :skip_index_html, :type => :boolean, :aliases => "-I", :default => false,
+ :desc => "Skip public/index.html and app/assets/images/rails.png files"
+
class_option :dev, :type => :boolean, :default => false,
:desc => "Setup the #{name} with Gemfile pointing to your Rails checkout"
@@ -60,9 +63,6 @@ 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)
@@ -123,7 +123,7 @@ module Rails
end
def database_gemfile_entry
- options[:skip_active_record] ? "" : "gem '#{gem_for_database}'\n"
+ options[:skip_active_record] ? "" : "gem '#{gem_for_database}'"
end
def include_all_railties?
@@ -137,20 +137,24 @@ module Rails
def rails_gemfile_entry
if options.dev?
<<-GEMFILE.strip_heredoc
- gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
- gem 'journey', :path => '#{Rails::Generators::JOURNEY_DEV_PATH}'
+ gem 'rails', path: '#{Rails::Generators::RAILS_DEV_PATH}'
+ gem 'journey', github: 'rails/journey'
+ gem 'arel', github: 'rails/arel'
+ gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
GEMFILE
elsif options.edge?
<<-GEMFILE.strip_heredoc
- gem 'rails', :git => 'git://github.com/rails/rails.git'
- gem 'journey', :git => 'git://github.com/rails/journey.git'
+ gem 'rails', github: 'rails/rails'
+ gem 'journey', github: 'rails/journey'
+ gem 'arel', github: 'rails/arel'
+ gem 'active_record_deprecated_finders', github: 'rails/active_record_deprecated_finders'
GEMFILE
else
<<-GEMFILE.strip_heredoc
gem 'rails', '#{Rails::VERSION::STRING}'
# Bundle edge Rails instead:
- # gem 'rails', :git => 'git://github.com/rails/rails.git'
+ # gem 'rails', github: 'rails/rails'
GEMFILE
end
end
@@ -182,41 +186,54 @@ module Rails
end
end
- def ruby_debugger_gemfile_entry
- if RUBY_VERSION < "1.9"
- "gem 'ruby-debug'"
+ def assets_gemfile_entry
+ return if options[:skip_sprockets]
+
+ gemfile = if options.dev? || options.edge?
+ <<-GEMFILE
+ # Gems used only for assets and not required
+ # in production environments by default.
+ group :assets do
+ gem 'sprockets-rails', github: 'rails/sprockets-rails'
+ gem 'sass-rails', github: 'rails/sass-rails'
+ gem 'coffee-rails', github: 'rails/coffee-rails'
+
+ # See https://github.com/sstephenson/execjs#readme for more supported runtimes
+ #{javascript_runtime_gemfile_entry}
+ gem 'uglifier', '>= 1.0.3'
+ end
+ GEMFILE
else
- "gem 'ruby-debug19', :require => 'ruby-debug'"
- end
- end
-
- def turn_gemfile_entry
- unless RUBY_VERSION < "1.9.2" || options[:skip_test_unit]
- <<-GEMFILE.strip_heredoc
- group :test do
- # Pretty printed test output
- gem 'turn', :require => false
+ <<-GEMFILE
+ # Gems used only for assets and not required
+ # in production environments by default.
+ group :assets do
+ gem 'sprockets-rails', github: 'rails/sprockets-rails'
+ gem 'sass-rails', '~> 4.0.0.beta'
+ gem 'coffee-rails', '~> 4.0.0.beta'
+
+ # See https://github.com/sstephenson/execjs#readme for more supported runtimes
+ #{javascript_runtime_gemfile_entry}
+ gem 'uglifier', '>= 1.0.3'
end
GEMFILE
end
- end
- def assets_gemfile_entry
- <<-GEMFILE.strip_heredoc
- # Gems used only for assets and not required
- # in production environments by default.
- group :assets do
- gem 'sass-rails', :git => 'git://github.com/rails/sass-rails.git'
- gem 'coffee-rails', :git => 'git://github.com/rails/coffee-rails.git'
- gem 'uglifier', '>= 1.0.3'
- end
- GEMFILE
+ gemfile.strip_heredoc.gsub(/^[ \t]*$/, '')
end
def javascript_gemfile_entry
"gem '#{options[:javascript]}-rails'" unless options[:skip_javascript]
end
+ def javascript_runtime_gemfile_entry
+ if defined?(JRUBY_VERSION)
+ "gem 'therubyrhino'\n"
+ else
+ "# gem 'therubyracer', platform: :ruby\n"
+ end
+ end
+
def bundle_command(command)
say_status :run, "bundle #{command}"
@@ -230,11 +247,11 @@ module Rails
# end-user gets the bundler commands called anyway, so no big deal.
#
# Thanks to James Tucker for the Gem tricks involved in this call.
- print `"#{Gem.ruby}" -rubygems "#{Gem.bin_path('bundler', 'bundle')}" #{command}`
+ print `"#{Gem.ruby}" "#{Gem.bin_path('bundler', 'bundle')}" #{command}`
end
def run_bundle
- bundle_command('install') unless options[:skip_gemfile] || options[:skip_bundle]
+ bundle_command('install') unless options[:skip_gemfile] || options[:skip_bundle] || options[:pretend]
end
def empty_directory_with_gitkeep(destination, config = {})
@@ -245,16 +262,6 @@ module Rails
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
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 911f80cf3a..28d7680669 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -31,10 +31,9 @@ module Rails
# root otherwise uses a default description.
def self.desc(description=nil)
return super if description
- usage = source_root && File.expand_path("../USAGE", source_root)
- @desc ||= if usage && File.exist?(usage)
- File.read(usage)
+ @desc ||= if usage_path
+ ERB.new(File.read(usage_path)).result(binding)
else
"Description:\n Create #{base_name.humanize.downcase} files for #{generator_name} generator."
end
@@ -48,6 +47,12 @@ module Rails
@namespace ||= super.sub(/_generator$/, '').sub(/:generators:/, ':')
end
+ # Convenience method to hide this generator from the available ones when
+ # running rails generator command.
+ def self.hide!
+ Rails::Generators.hide_namespace self.namespace
+ end
+
# Invoke a generator based on the value supplied by the user to the
# given option named "name". A class option is created when this method
# is invoked and you can set a hash to customize it.
@@ -91,7 +96,7 @@ module Rails
#
# The lookup in this case for test_unit as input is:
#
- # "test_framework:awesome", "test_framework"
+ # "test_unit:awesome", "test_unit"
#
# Which is not the desired the lookup. You can change it by providing the
# :as option:
@@ -102,7 +107,7 @@ module Rails
#
# And now it will lookup at:
#
- # "test_framework:controller", "test_framework"
+ # "test_unit:controller", "test_unit"
#
# Similarly, if you want it to also lookup in the rails namespace, you just
# need to provide the :base value:
@@ -113,7 +118,7 @@ module Rails
#
# And the lookup is exactly the same as previously:
#
- # "rails:test_framework", "test_framework:controller", "test_framework"
+ # "rails:test_unit", "test_unit:controller", "test_unit"
#
# ==== Switches
#
@@ -128,13 +133,13 @@ module Rails
#
# ==== Boolean hooks
#
- # In some cases, you want to provide a boolean hook. For example, webrat
+ # In some cases, you may want to provide a boolean hook. For example, webrat
# developers might want to have webrat available on controller generator.
# This can be achieved as:
#
# Rails::Generators::ControllerGenerator.hook_for :webrat, :type => :boolean
#
- # Then, if you want, webrat to be invoked, just supply:
+ # Then, if you want webrat to be invoked, just supply:
#
# rails generate controller Account --webrat
#
@@ -146,7 +151,7 @@ module Rails
#
# You can also supply a block to hook_for to customize how the hook is
# going to be invoked. The block receives two arguments, an instance
- # of the current class and the klass to be invoked.
+ # of the current class and the class to be invoked.
#
# For example, in the resource generator, the controller should be invoked
# with a pluralized class name. But by default it is invoked with the same
@@ -182,10 +187,7 @@ module Rails
# Remove a previously added hook.
#
- # ==== Examples
- #
# remove_hook_for :orm
- #
def self.remove_hook_for(*names)
remove_invocation(*names)
@@ -207,7 +209,8 @@ module Rails
# root, you should use source_root.
def self.default_source_root
return unless base_name && generator_name
- path = File.expand_path(File.join(base_name, generator_name, 'templates'), base_root)
+ return unless default_generator_root
+ path = File.join(default_generator_root, 'templates')
path if File.exists?(path)
end
@@ -242,7 +245,6 @@ module Rails
# Check whether the given class names are already taken by user
# application or Ruby on Rails.
- #
def class_collisions(*class_names) #:nodoc:
return unless behavior == :invoke
@@ -254,17 +256,13 @@ module Rails
nesting = class_name.split('::')
last_name = nesting.pop
- # Hack to limit const_defined? to non-inherited on 1.9
- extra = []
- extra << false unless Object.method(:const_defined?).arity == 1
-
# Extract the last Module in the nesting
last = nesting.inject(Object) do |last_module, nest|
- break unless last_module.const_defined?(nest, *extra)
+ break unless last_module.const_defined?(nest, false)
last_module.const_get(nest)
end
- if last && last.const_defined?(last_name.camelize, *extra)
+ if last && last.const_defined?(last_name.camelize, false)
raise Error, "The name '#{class_name}' is either already used in your application " <<
"or reserved by Ruby on Rails. Please choose an alternative and run " <<
"this generator again."
@@ -273,13 +271,11 @@ module Rails
end
# Use Rails default banner.
- #
def self.banner
"rails generate #{namespace.sub(/^rails:/,'')} #{self.arguments.map{ |a| a.usage }.join(' ')} [options]".gsub(/\s+/, ' ')
end
# Sets the base_name taking into account the current class namespace.
- #
def self.base_name
@base_name ||= begin
if base = name.to_s.split('::').first
@@ -290,7 +286,6 @@ module Rails
# Removes the namespaces and get the generator name. For example,
# Rails::Generators::ModelGenerator will return "model" as generator name.
- #
def self.generator_name
@generator_name ||= begin
if generator = name.to_s.split('::').last
@@ -302,20 +297,17 @@ module Rails
# Return the default value for the option name given doing a lookup in
# Rails::Generators.options.
- #
def self.default_value_for_option(name, options)
default_for_option(Rails::Generators.options, name, options, options[:default])
end
# Return default aliases for the option name given doing a lookup in
# Rails::Generators.aliases.
- #
def self.default_aliases_for_option(name, options)
default_for_option(Rails::Generators.aliases, name, options, options[:aliases])
end
# Return default for the option name given doing a lookup in config.
- #
def self.default_for_option(config, name, options, default)
if generator_name and c = config[generator_name.to_sym] and c.key?(name)
c[name]
@@ -329,14 +321,12 @@ module Rails
end
# Keep hooks configuration that are used on prepare_for_invocation.
- #
def self.hooks #:nodoc:
@hooks ||= from_superclass(:hooks, {})
end
# Prepare class invocation to search on Rails namespace if a previous
# added hook is being used.
- #
def self.prepare_for_invocation(name, value) #:nodoc:
return super unless value.is_a?(String) || value.is_a?(Symbol)
@@ -352,7 +342,6 @@ module Rails
# Small macro to add ruby as an option to the generator with proper
# default value plus an instance helper method called shebang.
- #
def self.add_shebang_option!
class_option :ruby, :type => :string, :aliases => "-r", :default => Thor::Util.ruby_command,
:desc => "Path to the Ruby binary of your choice", :banner => "PATH"
@@ -371,6 +360,19 @@ module Rails
}
end
+ def self.usage_path
+ paths = [
+ source_root && File.expand_path("../USAGE", source_root),
+ default_generator_root && File.join(default_generator_root, "USAGE")
+ ]
+ paths.compact.detect { |path| File.exists? path }
+ end
+
+ def self.default_generator_root
+ path = File.expand_path(File.join(base_name, generator_name), base_root)
+ path if File.exists?(path)
+ end
+
end
end
end
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 7b1a2a1841..d78d97b2b4 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -1,25 +1,27 @@
<h1>Listing <%= plural_table_name %></h1>
<table>
- <tr>
-<% attributes.each do |attribute| -%>
- <th><%= attribute.human_name %></th>
-<% end -%>
- <th></th>
- <th></th>
- <th></th>
- </tr>
+ <thead>
+ <tr>
+ <% attributes.each do |attribute| -%>
+ <th><%= attribute.human_name %></th>
+ <% end -%>
+ <th></th>
+ <th></th>
+ <th></th>
+ </tr>
+ </thead>
-<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
- <tr>
-<% attributes.each do |attribute| -%>
- <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
-<% 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 %>, <%= key_value :confirm, "'Are you sure?'" %>, <%= key_value :method, ":delete" %> %></td>
- </tr>
-<%% end %>
+ <tbody>
+ <%%= content_tag_for(:tr, @<%= plural_table_name %>) do |<%= singular_table_name %>| %>
+ <% attributes.each do |attribute| -%>
+ <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
+ <% 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>
+ <%% end %>
+ </tbody>
</table>
<br />
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
index 67f263efbb..e15c963971 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
@@ -2,7 +2,7 @@
<% attributes.each do |attribute| -%>
<p>
- <b><%= attribute.human_name %>:</b>
+ <strong><%= attribute.human_name %>:</strong>
<%%= @<%= singular_table_name %>.<%= attribute.name %> %>
</p>
diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb
index 816d82cac3..25d0161e4c 100644
--- a/railties/lib/rails/generators/generated_attribute.rb
+++ b/railties/lib/rails/generators/generated_attribute.rb
@@ -1,14 +1,60 @@
require 'active_support/time'
-require 'active_support/core_ext/object/inclusion'
module Rails
module Generators
class GeneratedAttribute
+ INDEX_OPTIONS = %w(index uniq)
+ UNIQ_INDEX_OPTIONS = %w(uniq)
+
attr_accessor :name, :type
+ attr_reader :attr_options
+
+ class << self
+ def parse(column_definition)
+ name, type, has_index = column_definition.split(':')
+
+ # if user provided "name:index" instead of "name:string:index"
+ # type should be set blank so GeneratedAttribute's constructor
+ # could set it to :string
+ has_index, type = type, nil if INDEX_OPTIONS.include?(type)
+
+ type, attr_options = *parse_type_and_options(type)
+ type = type.to_sym if type
+
+ if type && reference?(type)
+ references_index = UNIQ_INDEX_OPTIONS.include?(has_index) ? { :unique => true } : true
+ attr_options[:index] = references_index
+ end
+
+ new(name, type, has_index, attr_options)
+ end
+
+ def reference?(type)
+ [:references, :belongs_to].include? type
+ end
+
+ private
+
+ # parse possible attribute options like :limit for string/text/binary/integer or :precision/:scale for decimals
+ # when declaring options curly brackets should be used
+ def parse_type_and_options(type)
+ case type
+ when /(string|text|binary|integer)\{(\d+)\}/
+ return $1, :limit => $2.to_i
+ when /decimal\{(\d+)[,.-](\d+)\}/
+ return :decimal, :precision => $1.to_i, :scale => $2.to_i
+ else
+ return type, {}
+ end
+ end
+ end
- def initialize(name, type)
- type = :string if type.blank?
- @name, @type = name, type.to_sym
+ def initialize(name, type=nil, index_type=false, attr_options={})
+ @name = name
+ @type = type || :string
+ @has_index = INDEX_OPTIONS.include?(index_type)
+ @has_uniq_index = UNIQ_INDEX_OPTIONS.include?(index_type)
+ @attr_options = attr_options
end
def field_type
@@ -45,8 +91,28 @@ module Rails
name.to_s.humanize
end
+ def index_name
+ reference? ? "#{name}_id" : name
+ end
+
def reference?
- self.type.in?([:references, :belongs_to])
+ self.class.reference?(type)
+ end
+
+ def has_index?
+ @has_index
+ end
+
+ def has_uniq_index?
+ @has_uniq_index
+ end
+
+ def inject_options
+ "".tap { |s| @attr_options.each { |k,v| s << ", #{k}: #{v.inspect}" } }
+ end
+
+ def inject_index_options
+ has_uniq_index? ? ", unique: true" : ""
end
end
end
diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb
index 0c5c4f6e09..5bf98bb6e0 100644
--- a/railties/lib/rails/generators/migration.rb
+++ b/railties/lib/rails/generators/migration.rb
@@ -3,7 +3,6 @@ module Rails
# Holds common methods for migrations. It assumes that migrations has the
# [0-9]*_name format and can be used by another frameworks (like Sequel)
# just by implementing the next migration version method.
- #
module Migration
attr_reader :migration_number, :migration_file_name, :migration_class_name
@@ -38,10 +37,7 @@ module Rails
# The migration version, migration file name, migration class name are
# available as instance variables in the template to be rendered.
#
- # ==== Examples
- #
# migration_template "migration.rb", "db/migrate/add_foo_to_bar.rb"
- #
def migration_template(source, destination=nil, config={})
destination = File.expand_path(destination || source, self.destination_root)
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index c6c0392f43..e85d1b8fa2 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -9,9 +9,6 @@ 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:
@inside_template = nil
# Unfreeze name in case it's given as a frozen string
@@ -43,7 +40,7 @@ module Rails
def indent(content, multiplier = 2)
spaces = " " * multiplier
- content = content.each_line.map {|line| "#{spaces}#{line}" }.join
+ content = content.each_line.map {|line| line.blank? ? line : "#{spaces}#{line}" }.join
end
def wrap_with_namespace(content)
@@ -102,7 +99,7 @@ module Rails
end
def i18n_scope
- @i18n_scope ||= file_path.gsub('/', '.')
+ @i18n_scope ||= file_path.tr('/', '.')
end
def table_name
@@ -153,9 +150,8 @@ module Rails
# Convert attributes array into GeneratedAttribute objects.
def parse_attributes! #:nodoc:
- self.attributes = (attributes || []).map do |key_value|
- name, type = key_value.split(':')
- Rails::Generators::GeneratedAttribute.new(name, type)
+ self.attributes = (attributes || []).map do |attr|
+ Rails::Generators::GeneratedAttribute.parse(attr)
end
end
@@ -184,16 +180,6 @@ 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 c8648d19f8..c06b0f8994 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -38,7 +38,7 @@ module Rails
end
def readme
- copy_file "README"
+ copy_file "README", "README.rdoc"
end
def gemfile
@@ -97,6 +97,11 @@ module Rails
def public_directory
directory "public", "public", :recursive => false
+ if options[:skip_index_html]
+ remove_file "public/index.html"
+ remove_file 'app/assets/images/rails.png'
+ git_keep 'app/assets/images'
+ end
end
def script
@@ -124,7 +129,6 @@ module Rails
def vendor
vendor_javascripts
vendor_stylesheets
- vendor_plugins
end
def vendor_javascripts
@@ -134,18 +138,12 @@ module Rails
def vendor_stylesheets
empty_directory_with_gitkeep "vendor/assets/stylesheets"
end
-
- def vendor_plugins
- empty_directory_with_gitkeep "vendor/plugins"
- end
end
module Generators
# We need to store the RAILS_DEV_PATH in a constant, otherwise the path
# can change in Ruby 1.8.7 when we FileUtils.cd.
RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__))
- JOURNEY_DEV_PATH = File.expand_path("../../../../../../../journey", File.dirname(__FILE__))
-
RESERVED_NAMES = %w[application destroy benchmarker profiler plugin runner test]
class AppGenerator < AppBase
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 910cd16950..bf47e66cc4 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -1,11 +1,10 @@
-source 'http://rubygems.org'
+source 'https://rubygems.org'
<%= rails_gemfile_entry -%>
<%= database_gemfile_entry -%>
<%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) -%>
-<%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%>
<%= assets_gemfile_entry %>
<%= javascript_gemfile_entry %>
@@ -13,13 +12,14 @@ source 'http://rubygems.org'
# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'
-# Use unicorn as the web server
+# To use Jbuilder templates for JSON
+# gem 'jbuilder'
+
+# Use unicorn as the app server
# gem 'unicorn'
# Deploy with Capistrano
-# gem 'capistrano'
+# gem 'capistrano', :group => :development
# To use debugger
-# <%= ruby_debugger_gemfile_entry %>
-
-<%= turn_gemfile_entry -%>
+# gem 'debugger'
diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README
index 7c36f2356e..b5d7b6436b 100644
--- a/railties/lib/rails/generators/rails/app/templates/README
+++ b/railties/lib/rails/generators/rails/app/templates/README
@@ -86,8 +86,8 @@ programming in general.
Debugger support is available through the debugger command when you start your
Mongrel or WEBrick server with --debugger. This means that you can break out of
execution at any point in the code, investigate and change the model, and then,
-resume execution! You need to install ruby-debug to run the server in debugging
-mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
+resume execution! You need to install the 'debugger' gem to run the server in debugging
+mode. Add gem 'debugger' to your Gemfile and run <tt>bundle</tt> to install it. Example:
class WeblogController < ActionController::Base
def index
@@ -191,7 +191,6 @@ The default directory structure of a generated Ruby on Rails application:
`-- vendor
|-- assets
`-- stylesheets
- `-- plugins
app
Holds all the code that's specific to this particular application.
@@ -256,6 +255,5 @@ test
directory.
vendor
- External libraries that the application depends on. Also includes the plugins
- subdirectory. If the app has frozen rails, those gems also go here, under
- vendor/rails/. This directory is in the load path.
+ External libraries that the application depends on. If the app has frozen rails,
+ those gems also go here, under vendor/rails/. This directory is in the load path.
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index 4dc1023f1f..6eb23f68a3 100755..100644
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
@@ -1,4 +1,3 @@
-#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
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
index 3b5cc6648e..3192ec897b 100644
--- 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
@@ -10,4 +10,4 @@
*
*= require_self
*= require_tree .
-*/
+ */
diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb
deleted file mode 100644
index e8065d9505..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-class ApplicationController < ActionController::Base
- protect_from_forgery
-end
diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt
new file mode 100644
index 0000000000..3ddc86ae0a
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt
@@ -0,0 +1,5 @@
+class ApplicationController < ActionController::Base
+ # Prevent CSRF attacks by raising an exception.
+ # For APIs, you may want to use :reset_session instead.
+ protect_from_forgery :with => :exception
+end \ 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 c63d1b6ac5..bba96a7431 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,7 +2,7 @@
<html>
<head>
<title><%= camelized %></title>
- <%%= stylesheet_link_tag "application" %>
+ <%%= stylesheet_link_tag "application", :media => "all" %>
<%%= javascript_include_tag "application" %>
<%%= csrf_meta_tags %>
</head>
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 13fbe9e526..d816f973e6 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -7,15 +7,14 @@ require 'rails/all'
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
-require "active_resource/railtie"
-<%= comment_if :skip_sprockets %>require "sprockets/railtie"
+<%= comment_if :skip_sprockets %>require "sprockets/rails/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%>
if defined?(Bundler)
- # If you precompile assets before deploying to production, use this line
+ # If you precompile assets before deploying to production, use this line.
Bundler.require(*Rails.groups(:assets => %w(development test)))
- # If you want your assets lazily compiled in production, use this line
+ # If you want your assets lazily compiled in production, use this line.
# Bundler.require(:default, :assets, Rails.env)
end
@@ -28,10 +27,6 @@ module <%= app_const_base %>
# Custom directories with classes and modules you want to be autoloadable.
# config.autoload_paths += %W(#{config.root}/extras)
- # Only load the plugins named here, in the order given (default is alphabetical).
- # :all can be used as a placeholder for all plugins not explicitly named.
- # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
-
# Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
@@ -49,11 +44,27 @@ module <%= app_const_base %>
# Configure sensitive parameters which will be filtered from the log file.
config.filter_parameters += [:password]
+ # Use SQL instead of Active Record's schema dumper when creating the database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types.
+ # config.active_record.schema_format = :sql
+
+ # Enforce whitelist mode for mass assignment.
+ # This will create an empty whitelist of attributes available for mass-assignment for all models
+ # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
+ # parameters by using an attr_accessible or attr_protected declaration.
+ <%= comment_if :skip_active_record %>config.active_record.whitelist_attributes = true
+
+ # Specifies whether or not has_many or has_one association option :dependent => :restrict raises
+ # an exception. If set to true, then an ActiveRecord::DeleteRestrictionError exception would be
+ # raised. If set to false, then an error will be added on the model instead.
+ <%= comment_if :skip_active_record %>config.active_record.dependent_restrict_raises = false
<% unless options.skip_sprockets? -%>
- # Enable the asset pipeline
+
+ # Enable the asset pipeline.
config.assets.enabled = true
- # Version of your assets, change this if you want to expire all your assets
+ # Version of your assets, change this if you want to expire all your assets.
config.assets.version = '1.0'
<% end -%>
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
index 4489e58688..3596736667 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb
@@ -1,5 +1,3 @@
-require 'rubygems'
-
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
index cce166c7c3..c3349912aa 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
@@ -1,5 +1,5 @@
# MySQL. Versions 4.1 and 5.0 are recommended.
-#
+#
# Install the MYSQL driver
# gem install mysql2
#
@@ -11,7 +11,6 @@
development:
adapter: mysql2
encoding: utf8
- reconnect: false
database: <%= app_name %>_development
pool: 5
username: root
@@ -28,7 +27,6 @@ development:
test:
adapter: mysql2
encoding: utf8
- reconnect: false
database: <%= app_name %>_test
pool: 5
username: root
@@ -42,7 +40,6 @@ test:
production:
adapter: mysql2
encoding: utf8
- reconnect: false
database: <%= app_name %>_production
pool: 5
username: root
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
index f08f86aac3..467a4e725f 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml
@@ -24,6 +24,9 @@ development:
# domain socket that doesn't need configuration. Windows does not have
# domain sockets, so uncomment these lines.
#host: localhost
+
+ # The TCP port the server listens on. Defaults to 5432.
+ # If your server runs on a different port number, change accordingly.
#port: 5432
# Schema search path. The server defaults to $user,public
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 47078e3af9..24bcec854c 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
@@ -1,35 +1,41 @@
<%= app_const %>.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
- # Log error messages when you accidentally call methods on nil.
- config.whiny_nils = true
-
- # Show full error reports and disable caching
+ # Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
- # Don't care if the mailer can't send
+ # Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
- # Print deprecation notices to the Rails logger
+ # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log
- # Only use best-standards-support built into browsers
+ # Only use best-standards-support built into browsers.
config.action_dispatch.best_standards_support = :builtin
<%- unless options.skip_active_record? -%>
- # Raise exception on mass assignment protection for ActiveRecord models
+ # Raise exception on mass assignment protection for Active Record models.
config.active_record.mass_assignment_sanitizer = :strict
+
+ # Log the query plan for queries taking more than this (works
+ # with SQLite, MySQL, and PostgreSQL).
+ config.active_record.auto_explain_threshold_in_seconds = 0.5
<%- end -%>
- # Do not compress assets
+ <%- unless options.skip_sprockets? -%>
+ # Do not compress assets.
config.assets.compress = false
- # Expands the lines which load the assets
+ # Expands the lines which load the assets.
config.assets.debug = true
+ <%- end -%>
+
+ # In development, use an in-memory queue for queueing
+ config.queue = Rails::Queueing::Queue
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 64e2c09467..072aa8355d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -1,60 +1,83 @@
<%= app_const %>.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
- # Code is not reloaded between requests
+ # Code is not reloaded between requests.
config.cache_classes = true
- # Full error reports are disabled and caching is turned on
+ # Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
- # Disable Rails's static asset server (Apache or nginx will already do this)
+ # Disable Rails's static asset server (Apache or nginx will already do this).
config.serve_static_assets = false
- # Compress JavaScripts and CSS
+ <%- unless options.skip_sprockets? -%>
+ # Compress JavaScripts and CSS.
config.assets.compress = true
- # Don't fallback to assets pipeline if a precompiled asset is missed
+ # Don't fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
- # Generate digests for assets URLs
+ # Generate digests for assets URLs.
config.assets.digest = true
- # Defaults to Rails.root.join("public/assets")
+ # Defaults to nil and saved in location specified by config.assets.prefix
# config.assets.manifest = YOUR_PATH
+ <%- end -%>
- # Specifies the header that your server uses for sending files
+ # Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
- # See everything in the log (default is :info)
- # config.log_level = :debug
+ # Set to :debug to see everything in the log.
+ config.log_level = :info
- # Use a different logger for distributed setups
- # config.logger = SyslogLogger.new
+ # Prepend all log lines with the following tags.
+ # config.log_tags = [ :subdomain, :uuid ]
- # Use a different cache store in production
+ # Use a different logger for distributed setups.
+ # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
+
+ # Use a different cache store in production.
# config.cache_store = :mem_cache_store
- # Enable serving of images, stylesheets, and JavaScripts from an asset server
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = "http://assets.example.com"
- # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
+ <%- unless options.skip_sprockets? -%>
+ # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added).
# config.assets.precompile += %w( search.js )
+ <%- end -%>
- # Disable delivery errors, bad email addresses will be ignored
+ # Disable delivery errors, bad email addresses will be ignored.
# config.action_mailer.raise_delivery_errors = false
- # Enable threaded mode
+ # Enable threaded mode.
# config.threadsafe!
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
- # the I18n.default_locale when a translation can not be found)
+ # the I18n.default_locale when a translation can not be found).
config.i18n.fallbacks = true
- # Send deprecation notices to registered listeners
+ # Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
+
+ <%- unless options.skip_active_record? -%>
+ # Log the query plan for queries taking more than this (works
+ # with SQLite, MySQL, and PostgreSQL).
+ # config.active_record.auto_explain_threshold_in_seconds = 0.5
+ <%- end -%>
+
+ # Disable automatic flushing of the log to improve performance.
+ # config.autoflush_log = false
+
+ # Use default logging formatter so that PID and timestamp are not suppressed
+ config.log_formatter = ::Logger::Formatter.new
+
+ # Default the production mode queue to an in-memory queue. You will probably
+ # want to replace this with an out-of-process queueing solution
+ config.queue = Rails::Queueing::Queue
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
index 80198cc21e..b27b88a3c6 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
@@ -1,5 +1,5 @@
<%= app_const %>.configure do
- # Settings specified here will take precedence over those in config/application.rb
+ # Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
@@ -7,38 +7,33 @@
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
- # Configure static asset server for tests with Cache-Control for performance
+ # Configure static asset server for tests with Cache-Control for performance.
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
- # Log error messages when you accidentally call methods on nil
- config.whiny_nils = true
-
- # Show full error reports and disable caching
+ # Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
- # Raise exceptions instead of rendering exception templates
+ # Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
- # Disable request forgery protection in test environment
- config.action_controller.allow_forgery_protection = false
+ # Disable request forgery protection in test environment.
+ config.action_controller.allow_forgery_protection = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
- # Use SQL instead of Active Record's schema dumper when creating the test database.
- # This is necessary if your schema can't be completely dumped by the schema dumper,
- # like if you have constraints or database-specific column types
- # config.active_record.schema_format = :sql
-
<%- unless options.skip_active_record? -%>
- # Raise exception on mass assignment protection for ActiveRecord models
+ # Raise exception on mass assignment protection for Active Record models.
config.active_record.mass_assignment_sanitizer = :strict
<%- end -%>
- # Print deprecation notices to the stderr
+ # Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
+
+ # Use the testing queue
+ config.queue = Rails::Queueing::TestQueue
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
index a3143f1346..e02397aaf9 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
@@ -4,4 +4,6 @@
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
+# Make sure your secret_token is kept private
+# if you're sharing your code publicly.
<%= app_const %>.config.secret_token = '<%= app_secret %>'
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 ddfe4ba1e1..ade0c4f78c 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_value :key, "'_#{app_name}_session'" %>
+<%= app_const %>.config.session_store :cookie_store, 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/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
index d640f578da..19cbf0e4f1 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
@@ -5,7 +5,7 @@
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
- wrap_parameters <%= key_value :format, "[:json]" %>
+ wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
end
<%- unless options.skip_active_record? -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
index ea81748464..286e93c3cf 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -3,11 +3,11 @@
# first created -> highest priority.
# Sample of regular route:
- # match 'products/:id' => 'catalog#view'
+ # get 'products/:id' => 'catalog#view'
# Keep in mind you can assign values other than :controller and :action
# Sample of named route:
- # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
+ # get 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
# This route can be invoked with purchase_url(:id => product.id)
# Sample resource route (maps HTTP verbs to controller actions automatically):
@@ -51,8 +51,4 @@
# root :to => 'welcome#index'
# See how all your routes lay out with "rake routes"
-
- # This is a legacy wild controller route that's not recommended for RESTful applications.
- # Note: This route will make all actions in every controller accessible via GET requests.
- # match ':controller(/:action(/:id))(.:format)'
-end
+end \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt
index f75c5dd941..4edb1e857e 100644
--- a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt
@@ -3,5 +3,5 @@
#
# Examples:
#
-# cities = City.create([{ <%= key_value :name, "'Chicago'" %> }, { <%= key_value :name, "'Copenhagen'" %> }])
-# Mayor.create(<%= key_value :name, "'Emanuel'" %>, <%= key_value :city, "cities.first" %>)
+# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
+# Mayor.create(name: 'Emanuel', city: cities.first)
diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore
index eb3489a986..8910bf5a06 100644
--- a/railties/lib/rails/generators/rails/app/templates/gitignore
+++ b/railties/lib/rails/generators/rails/app/templates/gitignore
@@ -9,6 +9,7 @@
# Ignore the default SQLite database.
/db/*.sqlite3
+/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
/log/*.log
diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html
index 9a48320a5f..276c8c1c6a 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/404.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/404.html
@@ -2,7 +2,7 @@
<html>
<head>
<title>The page you were looking for doesn't exist (404)</title>
- <style type="text/css">
+ <style>
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html
index 83660ab187..3f1bfb3417 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/422.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/422.html
@@ -2,7 +2,7 @@
<html>
<head>
<title>The change you wanted was rejected (422)</title>
- <style type="text/css">
+ <style>
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html
index f3648a0dbc..dfdb7d0b05 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/500.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/500.html
@@ -2,7 +2,7 @@
<html>
<head>
<title>We're sorry, but something went wrong (500)</title>
- <style type="text/css">
+ <style>
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
diff --git a/railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt b/railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt
new file mode 100644
index 0000000000..f081e08b6c
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/public/humans.txt.tt
@@ -0,0 +1,7 @@
+# See more about this file at: http://humanstxt.org/
+# For format suggestions, see: http://humanstxt.org/Standard.html
+/* TEAM */
+
+/* APP */
+ Name: <%= app_const_base %>
+ Software: Ruby on Rails
diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/rails/generators/rails/app/templates/public/index.html
index 9d9811a5bf..dd09a96de9 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/index.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/index.html
@@ -2,7 +2,7 @@
<html>
<head>
<title>Ruby on Rails: Welcome aboard</title>
- <style type="text/css" media="screen">
+ <style media="screen">
body {
margin: 0;
margin-bottom: 25px;
@@ -59,7 +59,7 @@
#header {
- background-image: url("/assets/rails.png");
+ background-image: url("assets/rails.png");
background-repeat: no-repeat;
background-position: top left;
height: 64px;
@@ -171,7 +171,7 @@
font-style: italic;
}
</style>
- <script type="text/javascript">
+ <script>
function about() {
info = document.getElementById('about-content');
if (window.XMLHttpRequest)
diff --git a/railties/lib/rails/generators/rails/app/templates/public/robots.txt b/railties/lib/rails/generators/rails/app/templates/public/robots.txt
index 085187fa58..1a3a5e4dd2 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/robots.txt
+++ b/railties/lib/rails/generators/rails/app/templates/public/robots.txt
@@ -1,5 +1,5 @@
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
-# User-Agent: *
+# User-agent: *
# Disallow: /
diff --git a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb
index 3fea27b916..2a849b7f2b 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb
+++ b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb
@@ -3,7 +3,7 @@ require 'rails/performance_test_help'
class BrowsingTest < ActionDispatch::PerformanceTest
# Refer to the documentation for all available options
- # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory]
+ # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory],
# :output => 'tmp/performance', :formats => [:flat] }
def test_homepage
diff --git a/railties/lib/rails/generators/rails/controller/templates/controller.rb b/railties/lib/rails/generators/rails/controller/templates/controller.rb
index 8f5f78556f..52243f4a2f 100644
--- a/railties/lib/rails/generators/rails/controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/controller/templates/controller.rb
@@ -3,7 +3,7 @@ class <%= class_name %>Controller < ApplicationController
<% actions.each do |action| -%>
def <%= action %>
end
-
+<%= "\n" unless action == actions.last -%>
<% end -%>
end
<% end -%>
diff --git a/railties/lib/rails/generators/rails/migration/migration_generator.rb b/railties/lib/rails/generators/rails/migration/migration_generator.rb
index 39fa5b63b1..f87dce1502 100644
--- a/railties/lib/rails/generators/rails/migration/migration_generator.rb
+++ b/railties/lib/rails/generators/rails/migration/migration_generator.rb
@@ -1,7 +1,7 @@
module Rails
module Generators
class MigrationGenerator < NamedBase #metagenerator
- argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
+ argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]"
hook_for :orm, :required => true
end
end
diff --git a/railties/lib/rails/generators/rails/model/model_generator.rb b/railties/lib/rails/generators/rails/model/model_generator.rb
index 629d5eed3f..9bb29b784e 100644
--- a/railties/lib/rails/generators/rails/model/model_generator.rb
+++ b/railties/lib/rails/generators/rails/model/model_generator.rb
@@ -1,7 +1,7 @@
module Rails
module Generators
class ModelGenerator < NamedBase #metagenerator
- argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
+ argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]"
hook_for :orm, :required => true
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 4baa2110e7..722e37e20b 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
@@ -39,7 +39,7 @@ module Rails
end
def gitignore
- copy_file "gitignore", ".gitignore"
+ template "gitignore", ".gitignore"
end
def lib
@@ -133,6 +133,16 @@ task :default => :test
end
chmod "script", 0755, :verbose => false
end
+
+ def gemfile_entry
+ return unless inside_application?
+
+ gemfile_in_app_path = File.join(rails_app_path, "Gemfile")
+ if File.exist? gemfile_in_app_path
+ entry = "gem '#{name}', :path => '#{relative_path}'"
+ append_file gemfile_in_app_path, entry
+ end
+ end
end
module Generators
@@ -153,6 +163,10 @@ task :default => :test
class_option :skip_gemspec, :type => :boolean, :default => false,
:desc => "Skip gemspec file"
+ class_option :skip_gemfile_entry, :type => :boolean, :default => false,
+ :desc => "If creating plugin in application's directory " +
+ "skip adding entry to Gemfile"
+
def initialize(*args)
raise Error, "Options should be given after the plugin name. For details run: rails plugin --help" if args[0].blank?
@@ -208,6 +222,10 @@ task :default => :test
create_dummy_app
end
+ def update_gemfile
+ build(:gemfile_entry) unless options[:skip_gemfile_entry]
+ end
+
def finish_template
build(:leftovers)
end
@@ -246,8 +264,20 @@ task :default => :test
"rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]"
end
+ def original_name
+ @original_name ||= File.basename(destination_root)
+ end
+
def name
- @name ||= File.basename(destination_root)
+ @name ||= begin
+ # same as ActiveSupport::Inflector#underscore except not replacing '-'
+ underscored = original_name.dup
+ underscored.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
+ underscored.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
+ underscored.downcase!
+
+ underscored
+ end
end
def camelized
@@ -255,12 +285,14 @@ task :default => :test
end
def valid_const?
- if camelized =~ /^\d/
- raise Error, "Invalid plugin name #{name}. Please give a name which does not start with numbers."
+ if original_name =~ /[^0-9a-zA-Z_]+/
+ raise Error, "Invalid plugin name #{original_name}. Please give a name which use only alphabetic or numeric or \"_\" characters."
+ elsif camelized =~ /^\d/
+ raise Error, "Invalid plugin name #{original_name}. Please give a name which does not start with numbers."
elsif RESERVED_NAMES.include?(name)
- raise Error, "Invalid plugin name #{name}. Please give a name which does not match one of the reserved rails words."
+ raise Error, "Invalid plugin name #{original_name}. Please give a name which does not match one of the reserved rails words."
elsif Object.const_defined?(camelized)
- raise Error, "Invalid plugin name #{name}, constant #{camelized} is already in use. Please choose another plugin name."
+ raise Error, "Invalid plugin name #{original_name}, constant #{camelized} is already in use. Please choose another plugin name."
end
end
@@ -270,7 +302,7 @@ task :default => :test
dummy_application_path = File.expand_path("#{dummy_path}/config/application.rb", destination_root)
unless options[:pretend] || !File.exists?(dummy_application_path)
contents = File.read(dummy_application_path)
- contents[(contents.index("module Dummy"))..-1]
+ contents[(contents.index(/module ([\w]+)\n(.*)class Application/m))..-1]
end
end
end
@@ -301,6 +333,19 @@ end
def mute(&block)
shell.mute(&block)
end
+
+ def rails_app_path
+ APP_PATH.sub("/config/application", "") if defined?(APP_PATH)
+ end
+
+ def inside_application?
+ rails_app_path && app_path =~ /^#{rails_app_path}/
+ end
+
+ def relative_path
+ return unless inside_application?
+ app_path.sub(/^#{rails_app_path}\//, '')
+ end
end
end
end
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
index 8588e88077..82ffeebb86 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.summary = "TODO: Summary of <%= camelized %>."
s.description = "TODO: Description of <%= camelized %>."
- s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"]
+ s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
<% unless options.skip_test_unit? -%>
s.test_files = Dir["test/**/*"]
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
index f4efd3af74..9399c9cb77 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile
@@ -20,4 +20,4 @@ gem "jquery-rails"
<% end -%>
# To use debugger
-# <%= ruby_debugger_gemfile_entry %>
+# gem 'debugger'
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
index 6ed6adcf1b..743036362e 100755..100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -1,16 +1,10 @@
-#!/usr/bin/env rake
begin
require 'bundler/setup'
rescue LoadError
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
end
-begin
- require 'rdoc/task'
-rescue LoadError
- require 'rdoc/rdoc'
- require 'rake/rdoctask'
- RDoc::Task = Rake::RDocTask
-end
+
+require 'rdoc/task'
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt
index 01550dec2f..bd983fb90f 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/views/layouts/%name%/application.html.erb.tt
@@ -2,7 +2,7 @@
<html>
<head>
<title><%= camelized %></title>
- <%%= stylesheet_link_tag "<%= name %>/application" %>
+ <%%= stylesheet_link_tag "<%= name %>/application", :media => "all" %>
<%%= javascript_include_tag "<%= name %>/application" %>
<%%= csrf_meta_tags %>
</head>
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore
index 1463de6dfb..458b2c662e 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore
@@ -1,6 +1,8 @@
.bundle/
log/*.log
pkg/
-test/dummy/db/*.sqlite3
-test/dummy/log/*.log
-test/dummy/tmp/ \ No newline at end of file
+<%= dummy_path %>/db/*.sqlite3
+<%= dummy_path %>/db/*.sqlite3-journal
+<%= dummy_path %>/log/*.log
+<%= dummy_path %>/tmp/
+<%= dummy_path %>/.sass-cache
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
index 996ea79e67..2f9b7fc962 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb
@@ -7,8 +7,7 @@ require 'rails/all'
<%= comment_if :skip_active_record %>require "active_record/railtie"
require "action_controller/railtie"
require "action_mailer/railtie"
-require "active_resource/railtie"
-<%= comment_if :skip_sprockets %>require "sprockets/railtie"
+<%= comment_if :skip_sprockets %>require "sprockets/rails/railtie"
<%= comment_if :skip_test_unit %>require "rails/test_unit/railtie"
<% end -%>
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb
index eba0681370..c78bfb7f63 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb
@@ -1,4 +1,3 @@
-require 'rubygems'
gemfile = File.expand_path('../../../../Gemfile', __FILE__)
if File.exist?(gemfile)
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb
index dcd3b276e3..1e26a313cd 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb
@@ -8,3 +8,8 @@ Rails.backtrace_cleaner.remove_silencers!
# Load support files
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
+
+# Load fixtures from the engine
+if ActiveSupport::TestCase.method_defined?(:fixture_path=)
+ ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
+end
diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb
index c7345f3cfb..3a0586ee43 100644
--- a/railties/lib/rails/generators/rails/resource/resource_generator.rb
+++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb
@@ -14,13 +14,7 @@ module Rails
class_option :actions, :type => :array, :banner => "ACTION ACTION", :default => [],
:desc => "Actions for the resource controller"
- def add_resource_route
- return if options[:actions].present?
- route_config = regular_class_path.collect{|namespace| "namespace :#{namespace} do " }.join(" ")
- route_config << "resources :#{file_name.pluralize}"
- route_config << " end" * regular_class_path.size
- route route_config
- end
+ hook_for :resource_route, :required => true
end
end
end
diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
new file mode 100644
index 0000000000..6a5d62803c
--- /dev/null
+++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb
@@ -0,0 +1,13 @@
+module Rails
+ module Generators
+ class ResourceRouteGenerator < NamedBase
+ def add_resource_route
+ return if options[:actions].present?
+ route_config = regular_class_path.collect{ |namespace| "namespace :#{namespace} do " }.join(" ")
+ route_config << "resources :#{file_name.pluralize}"
+ route_config << " end" * regular_class_path.size
+ route route_config
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/scaffold/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE
index be1d113ed8..4a3eb2c7c7 100644
--- a/railties/lib/rails/generators/rails/scaffold/USAGE
+++ b/railties/lib/rails/generators/rails/scaffold/USAGE
@@ -7,23 +7,29 @@ Description:
under_scored, as the first argument, and an optional list of attribute
pairs.
- Attribute pairs are field:type arguments specifying the
- model's attributes. Timestamps are added by default, so you don't have to
- specify them by hand as 'created_at:datetime updated_at:datetime'.
+ Attributes are field arguments specifying the model's attributes. You can
+ optionally pass the type and an index to each field. For instance:
+ "title body:text tracking_id:integer:uniq" will generate a title field of
+ string type, a body with text type and a tracking_id as an integer with an
+ unique index. "index" could also be given instead of "uniq" if one desires
+ a non unique index.
+
+ Timestamps are added by default, so you don't have to specify them by hand
+ as 'created_at:datetime updated_at:datetime'.
You don't have to think up every attribute up front, but it helps to
sketch out a few so you can start working with the resource immediately.
- For example, 'scaffold post title:string body:text published:boolean'
- gives you a model with those three attributes, a controller that handles
+ For example, 'scaffold post title body:text published:boolean' gives
+ you a model with those three attributes, a controller that handles
the create/show/update/destroy, forms to create and edit your posts, and
- an index that lists them all, as well as a resources :posts
- declaration in config/routes.rb.
+ an index that lists them all, as well as a resources :posts declaration
+ in config/routes.rb.
If you want to remove all the generated files, run
'rails destroy scaffold ModelName'.
Examples:
`rails generate scaffold post`
- `rails generate scaffold post title:string body:text published:boolean`
- `rails generate scaffold purchase order_id:integer amount:decimal`
+ `rails generate scaffold post title body:text published:boolean`
+ `rails generate scaffold purchase amount:decimal tracking_id:integer:uniq`
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index 2271c6f9c1..0618b16984 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -11,7 +11,7 @@ module Rails
:desc => "ORM to generate the controller for"
def create_controller_files
- template 'controller.rb', File.join('app/controllers', class_path, "#{controller_file_name}_controller.rb")
+ template "controller.rb", File.join('app/controllers', class_path, "#{controller_file_name}_controller.rb")
end
hook_for :template_engine, :test_framework, :as => :scaffold
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 32b961d9fc..b95aea5f19 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -7,7 +7,7 @@ class <%= controller_class_name %>Controller < ApplicationController
respond_to do |format|
format.html # index.html.erb
- format.json { render <%= key_value :json, "@#{plural_table_name}" %> }
+ format.json { render json: <%= "@#{plural_table_name}" %> }
end
end
@@ -18,7 +18,7 @@ class <%= controller_class_name %>Controller < ApplicationController
respond_to do |format|
format.html # show.html.erb
- format.json { render <%= key_value :json, "@#{singular_table_name}" %> }
+ format.json { render json: <%= "@#{singular_table_name}" %> }
end
end
@@ -29,7 +29,7 @@ class <%= controller_class_name %>Controller < ApplicationController
respond_to do |format|
format.html # new.html.erb
- format.json { render <%= key_value :json, "@#{singular_table_name}" %> }
+ format.json { render json: <%= "@#{singular_table_name}" %> }
end
end
@@ -45,27 +45,27 @@ class <%= controller_class_name %>Controller < ApplicationController
respond_to do |format|
if @<%= orm_instance.save %>
- 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}" %> }
+ format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully created.'" %> }
+ format.json { render json: <%= "@#{singular_table_name}" %>, status: :created, location: <%= "@#{singular_table_name}" %> }
else
- format.html { render <%= key_value :action, '"new"' %> }
- format.json { render <%= key_value :json, "@#{orm_instance.errors}" %>, <%= key_value :status, ':unprocessable_entity' %> }
+ format.html { render action: "new" }
+ format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
end
end
end
- # PUT <%= route_url %>/1
- # PUT <%= route_url %>/1.json
+ # PATCH/PUT <%= route_url %>/1
+ # PATCH/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 %>, <%= key_value :notice, "'#{human_name} was successfully updated.'" %> }
- format.json { head :ok }
+ format.html { redirect_to @<%= singular_table_name %>, notice: <%= "'#{human_name} was successfully updated.'" %> }
+ format.json { head :no_content }
else
- format.html { render <%= key_value :action, '"edit"' %> }
- format.json { render <%= key_value :json, "@#{orm_instance.errors}" %>, <%= key_value :status, ':unprocessable_entity' %> }
+ format.html { render action: "edit" }
+ format.json { render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity }
end
end
end
@@ -78,7 +78,7 @@ class <%= controller_class_name %>Controller < ApplicationController
respond_to do |format|
format.html { redirect_to <%= index_helper %>_url }
- format.json { head :ok }
+ format.json { head :no_content }
end
end
end
diff --git a/railties/lib/rails/generators/rails/task/USAGE b/railties/lib/rails/generators/rails/task/USAGE
new file mode 100644
index 0000000000..dbe9bbaf08
--- /dev/null
+++ b/railties/lib/rails/generators/rails/task/USAGE
@@ -0,0 +1,9 @@
+Description:
+ Stubs out a new Rake task. Pass the namespace name, and a list of tasks as arguments.
+
+ This generates a task file in lib/tasks.
+
+Example:
+ `rails generate task feeds fetch erase add`
+
+ Task: lib/tasks/feeds.rake \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/task/task_generator.rb b/railties/lib/rails/generators/rails/task/task_generator.rb
new file mode 100644
index 0000000000..8a62d9e8eb
--- /dev/null
+++ b/railties/lib/rails/generators/rails/task/task_generator.rb
@@ -0,0 +1,12 @@
+module Rails
+ module Generators
+ class TaskGenerator < NamedBase
+ argument :actions, :type => :array, :default => [], :banner => "action action"
+
+ def create_task_files
+ template 'task.rb', File.join('lib/tasks', "#{file_name}.rake")
+ end
+
+ end
+ end
+end
diff --git a/railties/lib/rails/generators/rails/task/templates/task.rb b/railties/lib/rails/generators/rails/task/templates/task.rb
new file mode 100644
index 0000000000..b7407bd6dc
--- /dev/null
+++ b/railties/lib/rails/generators/rails/task/templates/task.rb
@@ -0,0 +1,8 @@
+namespace :<%= file_name %> do
+<% actions.each do |action| -%>
+ desc "TODO"
+ task :<%= action %> => :environment do
+ end
+
+<% end -%>
+end
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index b34bc4a524..48833869e5 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -50,7 +50,7 @@ module Rails
end
def controller_i18n_scope
- @controller_i18n_scope ||= controller_file_path.gsub('/', '.')
+ @controller_i18n_scope ||= controller_file_path.tr('/', '.')
end
# Loads the ORM::Generators::ActiveModel class. This class is responsible
@@ -64,7 +64,7 @@ module Rails
end
begin
- "#{options[:orm].to_s.classify}::Generators::ActiveModel".constantize
+ "#{options[:orm].to_s.camelize}::Generators::ActiveModel".constantize
rescue NameError
Rails::Generators::ActiveModel
end
@@ -73,7 +73,7 @@ module Rails
# Initialize ORM::Generators::ActiveModel to access instance methods.
def orm_instance(name=singular_table_name)
- @orm_instance ||= @orm_class.new(name)
+ @orm_instance ||= orm_class.new(name)
end
end
end
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 7319fb79f6..ff9cf0087e 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -31,7 +31,6 @@ module Rails
include FileUtils
class_attribute :destination_root, :current_path, :generator_class, :default_arguments
- delegate :destination_root, :current_path, :generator_class, :default_arguments, :to => :'self.class'
# Generators frequently change the current path using +FileUtils.cd+.
# So we need to store the path at file load and revert back to it after each test.
@@ -79,8 +78,8 @@ module Rails
#
# Finally, when a block is given, it yields the file content:
#
- # assert_file "app/controller/products_controller.rb" do |controller|
- # assert_instance_method :index, content do |index|
+ # assert_file "app/controllers/products_controller.rb" do |controller|
+ # assert_instance_method :index, controller do |index|
# assert_match(/Product\.all/, index)
# end
# end
@@ -135,7 +134,7 @@ module Rails
# Asserts a given migration does not exist. You need to supply an absolute path or a
# path relative to the configured destination:
#
- # assert_no_file "config/random.rb"
+ # assert_no_migration "db/migrate/create_products.rb"
#
def assert_no_migration(relative)
file_name = migration_file_name(relative)
@@ -159,8 +158,8 @@ module Rails
# Asserts the given method exists in the given content. When a block is given,
# it yields the content of the method.
#
- # assert_file "app/controller/products_controller.rb" do |controller|
- # assert_instance_method :index, content do |index|
+ # assert_file "app/controllers/products_controller.rb" do |controller|
+ # assert_instance_method :index, controller do |index|
# assert_match(/Product\.all/, index)
# end
# end
@@ -182,7 +181,7 @@ module Rails
# Asserts the given attribute type gets a proper default value:
#
- # assert_field_type :string, "MyString"
+ # assert_field_default_value :string, "MyString"
#
def assert_field_default_value(attribute_type, value)
assert_equal(value, create_generated_attribute(attribute_type).default)
@@ -218,8 +217,8 @@ module Rails
#
# create_generated_attribute(:string, 'name')
#
- def create_generated_attribute(attribute_type, name = 'test')
- Rails::Generators::GeneratedAttribute.new(name, attribute_type.to_s)
+ def create_generated_attribute(attribute_type, name = 'test', index = nil)
+ Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(':'))
end
protected
diff --git a/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb b/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb
index d296b26b16..370750a175 100644
--- a/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb
+++ b/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb
@@ -3,7 +3,7 @@ require 'rails/performance_test_help'
class <%= class_name %>Test < ActionDispatch::PerformanceTest
# Refer to the documentation for all available options
- # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory]
+ # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory],
# :output => 'tmp/performance', :formats => [:flat] }
def test_homepage
diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
index 2ca36a1e44..c9af2ca832 100644
--- a/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
+++ b/railties/lib/rails/generators/test_unit/plugin/templates/test_helper.rb
@@ -1,3 +1,2 @@
-require 'rubygems'
-require 'test/unit'
+require 'minitest/autorun'
require 'active_support'
diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
index f7e907a017..ca7fee3b6e 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -8,10 +8,27 @@ module TestUnit
check_class_collision :suffix => "ControllerTest"
+ argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
+
def create_test_files
- template 'functional_test.rb',
- File.join('test/functional', controller_class_path, "#{controller_file_name}_controller_test.rb")
+ template "functional_test.rb",
+ File.join("test/functional", controller_class_path, "#{controller_file_name}_controller_test.rb")
end
+
+ private
+
+ def attributes_hash
+ return if accessible_attributes.empty?
+
+ accessible_attributes.map do |a|
+ name = a.name
+ "#{name}: @#{singular_table_name}.#{name}"
+ end.sort.join(', ')
+ end
+
+ def accessible_attributes
+ attributes.reject(&:reference?)
+ 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 01fe6dda7a..30e1650555 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, <%= key_value singular_table_name, "@#{singular_table_name}.attributes" %>
+ post :create, <%= "#{singular_table_name}: { #{attributes_hash} }" %>
end
assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
test "should show <%= singular_table_name %>" do
- get :show, <%= key_value :id, "@#{singular_table_name}.to_param" %>
+ get :show, id: <%= "@#{singular_table_name}" %>
assert_response :success
end
test "should get edit" do
- get :edit, <%= key_value :id, "@#{singular_table_name}.to_param" %>
+ get :edit, id: <%= "@#{singular_table_name}" %>
assert_response :success
end
test "should update <%= singular_table_name %>" do
- put :update, <%= key_value :id, "@#{singular_table_name}.to_param" %>, <%= key_value singular_table_name, "@#{singular_table_name}.attributes" %>
+ put :update, id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %>
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, <%= key_value :id, "@#{singular_table_name}.to_param" %>
+ delete :destroy, id: <%= "@#{singular_table_name}" %>
end
assert_redirected_to <%= index_helper %>_path
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index a1e15092b2..aacc1be2fc 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -23,7 +23,7 @@ module Rails
end
def frameworks
- %w( active_record action_pack active_resource action_mailer active_support )
+ %w( active_record action_pack action_mailer active_support )
end
def framework_version(framework)
@@ -83,7 +83,7 @@ module Rails
end
# Versions of each Rails framework (Active Record, Action Pack,
- # Active Resource, Action Mailer, and Active Support).
+ # Action Mailer, and Active Support).
frameworks.each do |framework|
property "#{framework.titlecase} version" do
framework_version(framework)
diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb
index 04d5b55c69..a61673fab8 100644
--- a/railties/lib/rails/initializable.rb
+++ b/railties/lib/rails/initializable.rb
@@ -51,7 +51,7 @@ module Rails
def run_initializers(group=:default, *args)
return if instance_variable_defined?(:@ran)
- initializers.tsort.each do |initializer|
+ initializers.tsort_each do |initializer|
initializer.run(*args) if initializer.belongs_to?(group)
end
@ran = true
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index b37421c09c..b787d91821 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -1,4 +1,4 @@
-require 'set'
+require "pathname"
module Rails
module Paths
@@ -10,19 +10,19 @@ module Rails
# root.add "app/controllers", :eager_load => true
#
# The command above creates a new root object and add "app/controllers" as a path.
- # This means we can get a +Rails::Paths::Path+ object back like below:
+ # This means we can get a <tt>Rails::Paths::Path</tt> object back like below:
#
# path = root["app/controllers"]
# path.eager_load? # => true
# path.is_a?(Rails::Paths::Path) # => true
#
- # The +Path+ object is simply an array and allows you to easily add extra paths:
+ # The +Path+ object is simply an enumerable and allows you to easily add extra paths:
#
- # path.is_a?(Array) # => true
- # path.inspect # => ["app/controllers"]
+ # path.is_a?(Enumerable) # => true
+ # path.to_ary.inspect # => ["app/controllers"]
#
# path << "lib/controllers"
- # path.inspect # => ["app/controllers", "lib/controllers"]
+ # path.to_ary.inspect # => ["app/controllers", "lib/controllers"]
#
# Notice that when you add a path using +add+, the path object created already
# contains the path with the same path value given to +add+. In some situations,
@@ -43,25 +43,38 @@ module Rails
# root["app/controllers"].existent # => ["/rails/app/controllers"]
#
# Check the <tt>Rails::Paths::Path</tt> documentation for more information.
- class Root < ::Hash
+ class Root
attr_accessor :path
def initialize(path)
- raise "Argument should be a String of the physical root path" if path.is_a?(Array)
@current = nil
@path = path
- @root = self
- super()
+ @root = {}
end
def []=(path, value)
- value = Path.new(self, path, value) unless value.is_a?(Path)
- super(path, value)
+ add(path, :with => value)
end
def add(path, options={})
with = options[:with] || path
- self[path] = Path.new(self, path, with, options)
+ @root[path] = Path.new(self, path, [with].flatten, options)
+ end
+
+ def [](path)
+ @root[path]
+ end
+
+ def values
+ @root.values
+ end
+
+ def keys
+ @root.keys
+ end
+
+ def values_at(*list)
+ @root.values_at(*list)
end
def all_paths
@@ -100,14 +113,14 @@ module Rails
end
end
- class Path < Array
- attr_reader :path
- attr_accessor :glob
+ class Path
+ include Enumerable
- def initialize(root, current, *paths)
- options = paths.last.is_a?(::Hash) ? paths.pop : {}
- super(paths.flatten)
+ attr_reader :path, :root
+ attr_accessor :glob
+ def initialize(root, current, paths, options = {})
+ @paths = paths
@current = current
@root = root
@glob = options[:glob]
@@ -148,6 +161,35 @@ module Rails
RUBY
end
+ def each(&block)
+ @paths.each(&block)
+ end
+
+ def <<(path)
+ @paths << path
+ end
+ alias :push :<<
+
+ def concat(paths)
+ @paths.concat paths
+ end
+
+ def unshift(path)
+ @paths.unshift path
+ end
+
+ def to_ary
+ @paths
+ end
+
+ def paths
+ raise "You need to set a path root" unless @root.path
+
+ map do |p|
+ Pathname.new(@root.path).join(p)
+ end
+ end
+
# Expands all paths against the root and return all unique values.
def expanded
raise "You need to set a path root" unless @root.path
@@ -156,8 +198,10 @@ module Rails
each do |p|
path = File.expand_path(p, @root.path)
- if @glob
- result.concat Dir[File.join(path, @glob)].sort
+ if @glob && File.directory?(path)
+ result.concat Dir.chdir(path) {
+ Dir.glob(@glob).map { |file| File.join path, file }.sort
+ }
else
result << path
end
diff --git a/railties/lib/rails/performance_test_help.rb b/railties/lib/rails/performance_test_help.rb
index 4ac38981d0..b1285efde2 100644
--- a/railties/lib/rails/performance_test_help.rb
+++ b/railties/lib/rails/performance_test_help.rb
@@ -1,3 +1,3 @@
ActionController::Base.perform_caching = true
ActiveSupport::Dependencies.mechanism = :require
-Rails.logger.level = ActiveSupport::BufferedLogger::INFO
+Rails.logger.level = ActiveSupport::Logger::INFO
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
deleted file mode 100644
index 3e27688bb9..0000000000
--- a/railties/lib/rails/plugin.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-require 'rails/engine'
-require 'active_support/core_ext/array/conversions'
-
-module Rails
- # Rails::Plugin is nothing more than a Rails::Engine, but since it's loaded too late
- # in the boot process, it does not have the same configuration powers as a bare
- # Rails::Engine.
- #
- # Opposite to Rails::Railtie and Rails::Engine, you are not supposed to inherit from
- # Rails::Plugin. Rails::Plugin is automatically configured to be an engine by simply
- # placing inside vendor/plugins. Since this is done automatically, you actually cannot
- # declare a Rails::Engine inside your Plugin, otherwise it would cause the same files
- # to be loaded twice. This means that if you want to ship an Engine as gem it cannot
- # be used as plugin and vice-versa.
- #
- # Besides this conceptual difference, the only difference between Rails::Engine and
- # Rails::Plugin is that plugins automatically load the file "init.rb" at the plugin
- # root during the boot process.
- #
- class Plugin < Engine
- def self.global_plugins
- @global_plugins ||= []
- end
-
- def self.inherited(base)
- raise "You cannot inherit from Rails::Plugin"
- end
-
- def self.all(list, paths)
- plugins = []
- paths.each do |path|
- Dir["#{path}/*"].each do |plugin_path|
- plugin = new(plugin_path)
- next unless list.include?(plugin.name) || list.include?(:all)
- if global_plugins.include?(plugin.name)
- warn "WARNING: plugin #{plugin.name} from #{path} was not loaded. Plugin with the same name has been already loaded."
- next
- end
- global_plugins << plugin.name
- plugins << plugin
- end
- end
-
- plugins.sort_by do |p|
- [list.index(p.name) || list.index(:all), p.name.to_s]
- end
- end
-
- attr_reader :name, :path
-
- def railtie_name
- name.to_s
- end
-
- def initialize(root)
- @name = File.basename(root).to_sym
- config.root = root
- end
-
- def config
- @config ||= Engine::Configuration.new
- end
-
- initializer :handle_lib_autoload, :before => :set_load_path do |app|
- autoload = if app.config.reload_plugins
- config.autoload_paths
- else
- config.autoload_once_paths
- end
-
- autoload.concat paths["lib"].existent
- end
-
- initializer :load_init_rb, :before => :load_config_initializers do |app|
- init_rb = File.expand_path("init.rb", root)
- if File.file?(init_rb)
- # This double assignment is to prevent an "unused variable" warning on Ruby 1.9.3.
- config = config = app.config
- # TODO: think about evaling initrb in context of Engine (currently it's
- # always evaled in context of Rails::Application)
- eval(File.read(init_rb), binding, init_rb)
- end
- end
-
- initializer :sanity_check_railties_collision do
- if Engine.subclasses.map { |k| k.root.to_s }.include?(root.to_s)
- raise "\"#{name}\" is a Railtie/Engine and cannot be installed as a plugin"
- end
- end
- end
-end
diff --git a/railties/lib/rails/queueing.rb b/railties/lib/rails/queueing.rb
new file mode 100644
index 0000000000..b4bc7fcd18
--- /dev/null
+++ b/railties/lib/rails/queueing.rb
@@ -0,0 +1,73 @@
+require "thread"
+
+module Rails
+ module Queueing
+ # A Queue that simply inherits from STDLIB's Queue. Everytime this
+ # queue is used, Rails automatically sets up a ThreadedConsumer
+ # to consume it.
+ class Queue < ::Queue
+ end
+
+ # In test mode, the Rails queue is backed by an Array so that assertions
+ # can be made about its contents. The test queue provides a +jobs+
+ # method to make assertions about the queue's contents and a +drain+
+ # method to drain the queue and run the jobs.
+ #
+ # Jobs are run in a separate thread to catch mistakes where code
+ # assumes that the job is run in the same thread.
+ class TestQueue < ::Queue
+ # Get a list of the jobs off this queue. This method may not be
+ # available on production queues.
+ def jobs
+ @que.dup
+ end
+
+ # Drain the queue, running all jobs in a different thread. This method
+ # may not be available on production queues.
+ def drain
+ # run the jobs in a separate thread so assumptions of synchronous
+ # jobs are caught in test mode.
+ Thread.new { pop.run until empty? }.join
+ end
+ end
+
+ # The threaded consumer will run jobs in a background thread in
+ # development mode or in a VM where running jobs on a thread in
+ # production mode makes sense.
+ #
+ # When the process exits, the consumer pushes a nil onto the
+ # queue and joins the thread, which will ensure that all jobs
+ # are executed before the process finally dies.
+ class ThreadedConsumer
+ def self.start(queue)
+ new(queue).start
+ end
+
+ def initialize(queue)
+ @queue = queue
+ end
+
+ def start
+ @thread = Thread.new do
+ while job = @queue.pop
+ begin
+ job.run
+ rescue Exception => e
+ handle_exception e
+ end
+ end
+ end
+ self
+ end
+
+ def shutdown
+ @queue.push nil
+ @thread.join
+ end
+
+ def handle_exception(e)
+ Rails.logger.error "Job Error: #{e.message}\n#{e.backtrace.join("\n")}"
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails/rack.rb b/railties/lib/rails/rack.rb
index d4a41b217e..d1ee96f7fd 100644
--- a/railties/lib/rails/rack.rb
+++ b/railties/lib/rails/rack.rb
@@ -1,6 +1,5 @@
module Rails
module Rack
- autoload :ContentLength, "rails/rack/content_length"
autoload :Debugger, "rails/rack/debugger"
autoload :Logger, "rails/rack/logger"
autoload :LogTailer, "rails/rack/log_tailer"
diff --git a/railties/lib/rails/rack/content_length.rb b/railties/lib/rails/rack/content_length.rb
deleted file mode 100644
index 6839af4152..0000000000
--- a/railties/lib/rails/rack/content_length.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-require 'action_dispatch'
-require 'rack/utils'
-
-module Rails
- module Rack
- # Sets the Content-Length header on responses with fixed-length bodies.
- class ContentLength
- include ::Rack::Utils
-
- def initialize(app, sendfile=nil)
- @app = app
- @sendfile = sendfile
- end
-
- def call(env)
- status, headers, body = @app.call(env)
- headers = HeaderHash.new(headers)
-
- if !STATUS_WITH_NO_ENTITY_BODY.include?(status.to_i) &&
- !headers['Content-Length'] &&
- !headers['Transfer-Encoding'] &&
- !(@sendfile && headers[@sendfile])
-
- old_body = body
- body, length = [], 0
- old_body.each do |part|
- body << part
- length += bytesize(part)
- end
- old_body.close if old_body.respond_to?(:close)
- headers['Content-Length'] = length.to_s
- end
-
- [status, headers, body]
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb
index 831188eeee..902361ce77 100644
--- a/railties/lib/rails/rack/debugger.rb
+++ b/railties/lib/rails/rack/debugger.rb
@@ -6,13 +6,13 @@ module Rails
ARGV.clear # clear ARGV so that rails server options aren't passed to IRB
- require 'ruby-debug'
+ require 'debugger'
::Debugger.start
::Debugger.settings[:autoeval] = true if ::Debugger.respond_to?(:settings)
puts "=> Debugger enabled"
rescue LoadError
- puts "You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'"
+ puts "You're missing the 'debugger' gem. Add it to your Gemfile, bundle, and try again."
exit
end
diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb
index 830d840894..18f22e8089 100644
--- a/railties/lib/rails/rack/log_tailer.rb
+++ b/railties/lib/rails/rack/log_tailer.rb
@@ -4,10 +4,13 @@ module Rails
def initialize(app, log = nil)
@app = app
- path = Pathname.new(log || "#{File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath
- @cursor = ::File.size(path)
+ path = Pathname.new(log || "#{::File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath
- @file = ::File.open(path, 'r')
+ @cursor = @file = nil
+ if ::File.exists?(path)
+ @cursor = ::File.size(path)
+ @file = ::File.open(path, 'r')
+ end
end
def call(env)
@@ -17,6 +20,7 @@ module Rails
end
def tail!
+ return unless @cursor
@file.seek @cursor
unless @file.eof?
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index 3be262de08..89de10c83d 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -1,32 +1,46 @@
require 'active_support/core_ext/time/conversions'
+require 'active_support/core_ext/object/blank'
module Rails
module Rack
# Log the request started and flush all loggers after it.
class Logger < ActiveSupport::LogSubscriber
- def initialize(app)
- @app = app
+ def initialize(app, tags=nil)
+ @app, @tags = app, tags.presence
end
def call(env)
- before_dispatch(env)
- @app.call(env)
- ensure
- after_dispatch(env)
+ if @tags
+ Rails.logger.tagged(compute_tags(env)) { call_app(env) }
+ else
+ call_app(env)
+ end
end
protected
- def before_dispatch(env)
+ def call_app(env)
request = ActionDispatch::Request.new(env)
path = request.filtered_path
-
- info "\n\nStarted #{request.request_method} \"#{path}\" " \
- "for #{request.ip} at #{Time.now.to_default_s}"
+ Rails.logger.info "\n\nStarted #{request.request_method} \"#{path}\" for #{request.ip} at #{Time.now.to_default_s}"
+ @app.call(env)
+ ensure
+ ActiveSupport::LogSubscriber.flush_all!
end
- def after_dispatch(env)
- ActiveSupport::LogSubscriber.flush_all!
+ def compute_tags(env)
+ request = ActionDispatch::Request.new(env)
+
+ @tags.collect do |tag|
+ case tag
+ when Proc
+ tag.call(request)
+ when Symbol
+ request.send(tag)
+ else
+ tag
+ end
+ end
end
end
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index e8fb1f3d98..2102f8a03c 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -9,7 +9,7 @@ module Rails
# Rails and/or modify the initialization process.
#
# Every major component of Rails (Action Mailer, Action Controller,
- # Action View, Active Record and Active Resource) is a Railtie. Each of
+ # Action View and Active Record) is a Railtie. Each of
# them is responsible for their own initialization. This makes Rails itself
# absent of any component hooks, allowing other components to be used in
# place of any of the Rails defaults.
@@ -22,7 +22,7 @@ module Rails
#
# * creating initializers
# * configuring a Rails framework for the application, like setting a generator
- # * adding config.* keys to the environment
+ # * +adding config.*+ keys to the environment
# * setting up a subscriber with ActiveSupport::Notifications
# * adding rake tasks
#
@@ -103,11 +103,11 @@ module Rails
# end
# end
#
- # == Application, Plugin and Engine
+ # == Application and Engine
#
# A Rails::Engine is nothing more than a Railtie with some initializers already set.
- # And since Rails::Application and Rails::Plugin are engines, the same configuration
- # described here can be used in all three.
+ # And since Rails::Application is an engine, the same configuration described here
+ # can be used in both.
#
# Be sure to look at the documentation of those specific classes for more information.
#
@@ -117,7 +117,7 @@ module Rails
include Initializable
- ABSTRACT_RAILTIES = %w(Rails::Railtie Rails::Plugin Rails::Engine Rails::Application)
+ ABSTRACT_RAILTIES = %w(Rails::Railtie Rails::Engine Rails::Application)
class << self
private :new
@@ -162,7 +162,7 @@ module Rails
protected
def generate_railtie_name(class_or_module)
- ActiveSupport::Inflector.underscore(class_or_module).gsub("/", "_")
+ ActiveSupport::Inflector.underscore(class_or_module).tr("/", "_")
end
end
@@ -181,7 +181,7 @@ module Rails
def load_tasks(app=self)
extend Rake::DSL if defined? Rake::DSL
- self.class.rake_tasks.each { |block| block.call(app) }
+ self.class.rake_tasks.each { |block| self.instance_exec(app, &block) }
# load also tasks from all superclasses
klass = self.class.superclass
@@ -196,7 +196,7 @@ module Rails
end
def railtie_namespace
- @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:_railtie) }
+ @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) }
end
end
end
diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb
index f888684117..1c6b3769a5 100644
--- a/railties/lib/rails/railtie/configuration.rb
+++ b/railties/lib/rails/railtie/configuration.rb
@@ -7,6 +7,18 @@ module Rails
@@options ||= {}
end
+ # Add files that should be watched for change.
+ def watchable_files
+ @@watchable_files ||= []
+ end
+
+ # Add directories that should be watched for change.
+ # The key of the hashes should be directories and the values should
+ # be an array of extensions to match in each directory.
+ def watchable_dirs
+ @@watchable_dirs ||= {}
+ end
+
# This allows you to modify the application's middlewares from Engines.
#
# All operations you run on the app_middleware will be replayed on the
@@ -31,7 +43,7 @@ module Rails
ActiveSupport.on_load(:before_configuration, :yield => true, &block)
end
- # Third configurable block to run. Does not run if config.cache_classes
+ # Third configurable block to run. Does not run if +config.cache_classes+
# set to false.
def before_eager_load(&block)
ActiveSupport.on_load(:before_eager_load, :yield => true, &block)
diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb
index 4d57c5973c..4536fedaa3 100644
--- a/railties/lib/rails/ruby_version_check.rb
+++ b/railties/lib/rails/ruby_version_check.rb
@@ -1,8 +1,8 @@
-if RUBY_VERSION < '1.8.7'
+if RUBY_VERSION < '1.9.3'
desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
abort <<-end_message
- Rails 3 requires Ruby 1.8.7 or 1.9.2.
+ Rails 4 requires Ruby 1.9.3+.
You're running
#{desc}
@@ -10,14 +10,4 @@ if RUBY_VERSION < '1.8.7'
Please upgrade to continue.
end_message
-elsif RUBY_VERSION > '1.9' and RUBY_VERSION < '1.9.2'
- $stderr.puts <<-end_message
-
- Rails 3 doesn't officially support Ruby 1.9.1 since recent stable
- releases have segfaulted the test suite. Please upgrade to Ruby 1.9.2.
-
- You're running
- #{RUBY_DESCRIPTION}
-
- end_message
end
diff --git a/railties/lib/rails/rubyprof_ext.rb b/railties/lib/rails/rubyprof_ext.rb
index f6e90357ce..017eba3a76 100644
--- a/railties/lib/rails/rubyprof_ext.rb
+++ b/railties/lib/rails/rubyprof_ext.rb
@@ -12,7 +12,7 @@ module Prof #:nodoc:
io.puts " time seconds seconds calls ms/call ms/call name"
sum = 0.0
- for r in results
+ results.each do |r|
sum += r.self_time
name = if r.method_class.nil?
diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb
index 1eed763aa3..31e34023c0 100644
--- a/railties/lib/rails/source_annotation_extractor.rb
+++ b/railties/lib/rails/source_annotation_extractor.rb
@@ -14,6 +14,9 @@
# of the line (or closing ERB comment tag) is considered to be their text.
class SourceAnnotationExtractor
class Annotation < Struct.new(:line, :tag, :text)
+ def self.directories
+ @@directories ||= %w(app config lib script test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',')
+ end
# Returns a representation of the annotation that looks like this:
#
@@ -22,7 +25,7 @@ class SourceAnnotationExtractor
# If +options+ has a flag <tt>:tag</tt> the tag is shown as in the example above.
# Otherwise the string contains just line and text.
def to_s(options={})
- s = "[%3d] " % line
+ s = "[#{line.to_s.rjust(options[:indent])}] "
s << "[#{tag}] " if options[:tag]
s << text
end
@@ -30,8 +33,9 @@ class SourceAnnotationExtractor
# Prints all annotations with tag +tag+ under the root directories +app+, +config+, +lib+,
# +script+, and +test+ (recursively). Only filenames with extension
- # +.builder+, +.rb+, +.rxml+, +.rhtml+, or +.erb+ are taken into account. The +options+
- # hash is passed to each annotation's +to_s+.
+ # +.builder+, +.rb+, +.erb+, +.haml+, +.slim+, +.css+, +.scss+, +.js+, and
+ # +.coffee+ 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.
def self.enumerate(tag, options={})
@@ -46,16 +50,15 @@ class SourceAnnotationExtractor
end
# 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+, +.rhtml+, and +.erb+
- # are taken into account.
- def find(dirs=%w(app config lib script test))
+ # with their annotations.
+ def find(dirs = Annotation.directories)
dirs.inject({}) { |h, dir| h.update(find_in(dir)) }
end
# 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+, +.rhtml+, and +.erb+
+ # those with extension +.builder+, +.rb+, +.erb+, +.haml+, +.slim+, +.css+,
+ # +.scss+, +.js+, and +.coffee+
# are taken into account.
def find_in(dir)
results = {}
@@ -65,10 +68,16 @@ class SourceAnnotationExtractor
if File.directory?(item)
results.update(find_in(item))
- elsif item =~ /\.(builder|(r(?:b|xml|js)))$/
+ elsif item =~ /\.(builder|rb|coffee)$/
results.update(extract_annotations_from(item, /#\s*(#{tag}):?\s*(.*)$/))
- elsif item =~ /\.(rhtml|erb)$/
+ elsif item =~ /\.(css|scss|js)$/
+ results.update(extract_annotations_from(item, /\/\/\s*(#{tag}):?\s*(.*)$/))
+ elsif item =~ /\.erb$/
results.update(extract_annotations_from(item, /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/))
+ elsif item =~ /\.haml$/
+ results.update(extract_annotations_from(item, /-\s*#\s*(#{tag}):?\s*(.*)$/))
+ elsif item =~ /\.slim$/
+ results.update(extract_annotations_from(item, /\/\s*\s*(#{tag}):?\s*(.*)$/))
end
end
@@ -91,6 +100,7 @@ class SourceAnnotationExtractor
# Prints the mapping from filenames to annotations in +results+ ordered by filename.
# The +options+ hash is passed to each annotation's +to_s+.
def display(results, options={})
+ options[:indent] = results.map { |f, a| a.map(&:line) }.flatten.max.to_s.size
results.keys.sort.each do |file|
puts "#{file}:"
results[file].each do |note|
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index ca8875ad9b..4ec49eee76 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -1,14 +1,8 @@
-begin
- require 'rdoc/task'
-rescue LoadError
- require 'rdoc/rdoc'
- require 'rake/rdoctask'
- RDoc::Task = Rake::RDocTask
-end
+require 'rdoc/task'
# Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise
class RDocTaskWithoutDescriptions < RDoc::Task
- include ::Rake::DSL
+ include ::Rake::DSL if defined?(::Rake::DSL)
def define
task rdoc_task_name
@@ -55,7 +49,7 @@ namespace :doc do
rdoc.rdoc_files.include('app/**/*.rb')
rdoc.rdoc_files.include('lib/**/*.rb')
}
- Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides, doc:plugins (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")"
+ Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")"
# desc 'Generate documentation for the Rails framework.'
RDocTaskWithoutDescriptions.new("rails") { |rdoc|
@@ -63,93 +57,49 @@ namespace :doc do
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.title = "Rails Framework Documentation"
rdoc.options << '--line-numbers'
- rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('README.rdoc')
gem_path('actionmailer') do |actionmailer|
- %w(README.rdoc CHANGELOG MIT-LICENSE lib/action_mailer/base.rb).each do |file|
+ %w(README.rdoc CHANGELOG.md MIT-LICENSE lib/action_mailer/base.rb).each do |file|
rdoc.rdoc_files.include("#{actionmailer}/#{file}")
end
end
gem_path('actionpack') do |actionpack|
- %w(README.rdoc CHANGELOG MIT-LICENSE lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG.md MIT-LICENSE lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{actionpack}/#{file}")
end
end
gem_path('activemodel') do |activemodel|
- %w(README.rdoc CHANGELOG MIT-LICENSE lib/active_model/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG.md MIT-LICENSE lib/active_model/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{activemodel}/#{file}")
end
end
gem_path('activerecord') do |activerecord|
- %w(README.rdoc CHANGELOG lib/active_record/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG.md lib/active_record/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{activerecord}/#{file}")
end
end
- gem_path('activeresource') do |activeresource|
- %w(README.rdoc CHANGELOG lib/active_resource.rb lib/active_resource/*).each do |file|
- rdoc.rdoc_files.include("#{activeresource}/#{file}")
- end
- end
-
gem_path('activesupport') do |activesupport|
- %w(README.rdoc CHANGELOG lib/active_support/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG.md lib/active_support/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{activesupport}/#{file}")
end
end
gem_path('railties') do |railties|
- %w(README.rdoc CHANGELOG lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file|
+ %w(README.rdoc CHANGELOG.md lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file|
rdoc.rdoc_files.include("#{railties}/#{file}")
end
end
}
- plugins = FileList['vendor/plugins/**'].collect { |plugin| File.basename(plugin) }
-
- # desc "Generate documentation for all installed plugins"
- task :plugins => plugins.collect { |plugin| "doc:plugins:#{plugin}" }
-
- # desc "Remove plugin documentation"
- task :clobber_plugins do
- rm_rf 'doc/plugins' rescue nil
- end
-
# desc "Generate Rails Guides"
task :guides do
# FIXME: Reaching outside lib directory is a bad idea
require File.expand_path('../../../../guides/rails_guides', __FILE__)
RailsGuides::Generator.new(Rails.root.join("doc/guides")).generate
end
-
- namespace :plugins do
- # Define doc tasks for each plugin
- plugins.each do |plugin|
- # desc "Generate documentation for the #{plugin} plugin"
- task(plugin => :environment) do
- plugin_base = "vendor/plugins/#{plugin}"
- options = []
- files = Rake::FileList.new
- options << "-o doc/plugins/#{plugin}"
- options << "--title '#{plugin.titlecase} Plugin Documentation'"
- options << '--line-numbers'
- options << '--charset' << 'utf-8'
- options << '-T html'
-
- files.include("#{plugin_base}/lib/**/*.rb")
- if File.exist?("#{plugin_base}/README")
- files.include("#{plugin_base}/README")
- options << "--main '#{plugin_base}/README'"
- end
- files.include("#{plugin_base}/CHANGELOG") if File.exist?("#{plugin_base}/CHANGELOG")
-
- options << files.to_s
-
- sh %(rdoc #{options * ' '})
- end
- end
- end
end
diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake
index 2152e811f5..eea8abe7d2 100644
--- a/railties/lib/rails/tasks/engine.rake
+++ b/railties/lib/rails/tasks/engine.rake
@@ -2,6 +2,7 @@ task "load_app" do
namespace :app do
load APP_RAKEFILE
end
+ task :environment => "app:environment"
if !defined?(ENGINE_PATH) || !ENGINE_PATH
ENGINE_PATH = find_engine_path(APP_RAKEFILE)
diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake
index 40f8c1034a..f684e71267 100644
--- a/railties/lib/rails/tasks/statistics.rake
+++ b/railties/lib/rails/tasks/statistics.rake
@@ -2,6 +2,7 @@ STATS_DIRECTORIES = [
%w(Controllers app/controllers),
%w(Helpers app/helpers),
%w(Models app/models),
+ %w(Mailers app/mailers),
%w(Libraries lib/),
%w(APIs app/apis),
%w(Integration\ tests test/integration),
diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb
index 8d0d8cacac..46bf3bbe48 100644
--- a/railties/lib/rails/test_help.rb
+++ b/railties/lib/rails/test_help.rb
@@ -2,38 +2,22 @@
# so fixtures aren't loaded into that environment
abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production?
-require 'test/unit'
+require 'minitest/autorun'
require 'active_support/test_case'
require 'action_controller/test_case'
require 'action_dispatch/testing/integration'
-if defined?(Test::Unit::Util::BacktraceFilter) && ENV['BACKTRACE'].nil?
- require 'rails/backtrace_cleaner'
- Test::Unit::Util::BacktraceFilter.module_eval { include Rails::BacktraceFilterForTestUnit }
-end
-
-if defined?(MiniTest)
- # 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
- end
- rescue LoadError
- end
+# Enable turn if it is available
+begin
+ require 'turn'
+ MiniTest::Unit.use_natural_language_case_names = true
+rescue LoadError
end
if defined?(ActiveRecord::Base)
- require 'active_record/test_case'
-
class ActiveSupport::TestCase
include ActiveRecord::TestFixtures
self.fixture_path = "#{Rails.root}/test/fixtures/"
-
- setup do
- ActiveRecord::IdentityMap.clear
- end
end
ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path
diff --git a/railties/lib/rails/test_unit/sub_test_task.rb b/railties/lib/rails/test_unit/sub_test_task.rb
new file mode 100644
index 0000000000..87b6f9b5a4
--- /dev/null
+++ b/railties/lib/rails/test_unit/sub_test_task.rb
@@ -0,0 +1,8 @@
+module Rails
+ # Silence the default description to cut down on `rake -T` noise.
+ class SubTestTask < Rake::TestTask
+ def desc(string)
+ # Ignore the description.
+ end
+ end
+end
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index fa01f42c5b..55bebb549b 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -1,35 +1,6 @@
require 'rbconfig'
require 'rake/testtask'
-
-# Monkey-patch to silence the description from Rake::TestTask to cut down on rake -T noise
-class TestTaskWithoutDescription < Rake::TestTask
- # Create the tasks defined by this task lib.
- def define
- lib_path = @libs.join(File::PATH_SEPARATOR)
- task @name do
- run_code = ''
- RakeFileUtils.verbose(@verbose) do
- run_code =
- case @loader
- when :direct
- "-e 'ARGV.each{|f| load f}'"
- when :testrb
- "-S testrb #{fix}"
- when :rake
- rake_loader
- end
- @ruby_opts.unshift( "-I\"#{lib_path}\"" )
- @ruby_opts.unshift( "-w" ) if @warning
- ruby @ruby_opts.join(" ") +
- " \"#{run_code}\" " +
- file_list.collect { |fn| "\"#{fn}\"" }.join(' ') +
- " #{option_list}"
- end
- end
- self
- end
-end
-
+require 'rails/test_unit/sub_test_task'
TEST_CHANGES_SINCE = Time.now - 600
@@ -61,6 +32,7 @@ end
# Recreated here from Active Support because :uncommitted needs it before Rails is available
module Kernel
+ remove_method :silence_stderr # Removing old method to prevent method redefined warning
def silence_stderr
old_stderr = STDERR.dup
STDERR.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
@@ -73,22 +45,9 @@ end
task :default => :test
-desc 'Runs test:units, test:functionals, test:integration together (also available: test:benchmark, test:profile, test:plugins)'
+desc 'Runs test:units, test:functionals, test:integration together (also available: test:benchmark, test:profile)'
task :test do
- tests_to_run = ENV['TEST'] ? ["test:single"] : %w(test:units test:functionals test:integration)
- errors = tests_to_run.collect do |task|
- begin
- Rake::Task[task].invoke
- nil
- rescue => e
- { :task => task, :exception => e }
- end
- end.compact
-
- if errors.any?
- puts errors.map { |e| "Errors running #{e[:task]}! #{e[:exception].inspect}" }.join("\n")
- abort
- end
+ Rake::Task[ENV['TEST'] ? 'test:single' : 'test:run'].invoke
end
namespace :test do
@@ -96,6 +55,22 @@ namespace :test do
# Placeholder task for other Railtie and plugins to enhance. See Active Record for an example.
end
+ task :run do
+ errors = %w(test:units test:functionals test:integration).collect do |task|
+ begin
+ Rake::Task[task].invoke
+ nil
+ rescue => e
+ { :task => task, :exception => e }
+ end
+ end.compact
+
+ if errors.any?
+ puts errors.map { |e| "Errors running #{e[:task]}! #{e[:exception].inspect}" }.join("\n")
+ abort
+ end
+ end
+
Rake::TestTask.new(:recent => "test:prepare") do |t|
since = TEST_CHANGES_SINCE
touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } +
@@ -122,8 +97,7 @@ namespace :test do
unit_tests = models.map { |model| "test/unit/#{File.basename(model, '.rb')}_test.rb" }
functional_tests = controllers.map { |controller| "test/functional/#{File.basename(controller, '.rb')}_test.rb" }
-
- unit_tests.uniq + functional_tests.uniq
+ (unit_tests + functional_tests).uniq.select { |file| File.exist?(file) }
end
t.libs << 'test'
@@ -134,39 +108,29 @@ namespace :test do
t.libs << "test"
end
- TestTaskWithoutDescription.new(:units => "test:prepare") do |t|
+ Rails::SubTestTask.new(:units => "test:prepare") do |t|
t.libs << "test"
t.pattern = 'test/unit/**/*_test.rb'
end
- TestTaskWithoutDescription.new(:functionals => "test:prepare") do |t|
+ Rails::SubTestTask.new(:functionals => "test:prepare") do |t|
t.libs << "test"
t.pattern = 'test/functional/**/*_test.rb'
end
- TestTaskWithoutDescription.new(:integration => "test:prepare") do |t|
+ Rails::SubTestTask.new(:integration => "test:prepare") do |t|
t.libs << "test"
t.pattern = 'test/integration/**/*_test.rb'
end
- TestTaskWithoutDescription.new(:benchmark => 'test:prepare') do |t|
+ Rails::SubTestTask.new(:benchmark => 'test:prepare') do |t|
t.libs << 'test'
t.pattern = 'test/performance/**/*_test.rb'
t.options = '-- --benchmark'
end
- TestTaskWithoutDescription.new(:profile => 'test:prepare') do |t|
+ Rails::SubTestTask.new(:profile => 'test:prepare') do |t|
t.libs << 'test'
t.pattern = 'test/performance/**/*_test.rb'
end
-
- TestTaskWithoutDescription.new(:plugins => :environment) do |t|
- t.libs << "test"
-
- if ENV['PLUGIN']
- t.pattern = "vendor/plugins/#{ENV['PLUGIN']}/test/**/*_test.rb"
- else
- t.pattern = 'vendor/plugins/*/**/test/**/*_test.rb'
- end
- end
end
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index 254227ecf7..ec878f9dcf 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -1,7 +1,7 @@
module Rails
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index b917128885..7067253279 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -6,13 +6,13 @@ Gem::Specification.new do |s|
s.version = version
s.summary = 'Tools for creating, working with, and running Rails applications.'
s.description = 'Rails internals: application bootup, plugins, generators, and rake tasks.'
- s.required_ruby_version = '>= 1.8.7'
+ s.required_ruby_version = '>= 1.9.3'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG', 'README.rdoc', 'bin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}']
+ s.files = Dir['CHANGELOG.md', 'README.rdoc', 'bin/**/*', 'lib/**/{*,.[a-z]*}']
s.require_path = 'lib'
s.bindir = 'bin'
@@ -21,8 +21,11 @@ Gem::Specification.new do |s|
s.rdoc_options << '--exclude' << '.'
s.add_dependency('rake', '>= 0.8.7')
- s.add_dependency('thor', '~> 0.14.6')
- s.add_dependency('rack-ssl', '~> 1.3.2')
+
+ # The current API of the Thor gem (0.14) will remain stable at least until Thor 2.0. Because
+ # Thor is so heavily used by other gems, we will accept Thor's semver guarantee to reduce
+ # the possibility of conflicts.
+ s.add_dependency('thor', '>= 0.14.6', '< 2.0')
s.add_dependency('rdoc', '~> 3.4')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index 1c3f8a701a..dfcf5aa27d 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -1,11 +1,12 @@
+ENV["RAILS_ENV"] ||= "test"
+
require File.expand_path("../../../load_paths", __FILE__)
require 'stringio'
-require 'test/unit'
+require 'minitest/autorun'
require 'fileutils'
require 'active_support'
-require 'active_support/core_ext/logger'
require 'action_controller'
require 'rails/all'
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
index 1b99af22a4..ecacb34cb2 100644
--- a/railties/test/application/asset_debugging_test.rb
+++ b/railties/test/application/asset_debugging_test.rb
@@ -2,7 +2,7 @@ require 'isolation/abstract_unit'
require 'rack/test'
module ApplicationTests
- class AssetDebuggingTest < Test::Unit::TestCase
+ class AssetDebuggingTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
include Rack::Test::Methods
@@ -15,7 +15,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match '/posts', :to => "posts#index"
+ get '/posts', :to => "posts#index"
end
RUBY
@@ -45,8 +45,8 @@ module ApplicationTests
# the debug_assets params isn't used if compile is off
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application-([0-z]+)\.js" type="text\/javascript"><\/script>/, last_response.body)
- assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js" type="text\/javascript"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application-([0-z]+)\.js"><\/script>/, last_response.body)
+ assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body)
end
test "assets aren't concatened when compile is true is on and debug_assets params is true" do
@@ -58,8 +58,8 @@ module ApplicationTests
class ::PostsController < ActionController::Base ; end
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application-([0-z]+)\.js\?body=1" type="text\/javascript"><\/script>/, last_response.body)
- assert_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1" type="text\/javascript"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
end
end
end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index d4ffbe3d66..e23a19d69c 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/kernel/reporting'
require 'rack/test'
module ApplicationTests
- class AssetsTest < Test::Unit::TestCase
+ class AssetsTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
include Rack::Test::Methods
@@ -17,10 +17,6 @@ module ApplicationTests
teardown_app
end
- def app
- @app ||= Rails.application
- end
-
def precompile!
quietly do
Dir.chdir(app_path){ `bundle exec rake assets:precompile` }
@@ -32,7 +28,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '*path', :to => lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] }
+ get '*path', :to => lambda { |env| [200, { "Content-Type" => "text/html" }, "Not an asset"] }
end
RUBY
@@ -68,11 +64,11 @@ module ApplicationTests
files << Dir["#{app_path}/public/assets/foo/application.js"].first
files.each do |file|
assert_not_nil file, "Expected application.js asset to be generated, but none found"
- assert_equal "alert()", File.read(file)
+ assert_equal "alert();", File.read(file)
end
end
- test "precompile application.js and application.css and all other files not ending with .js or .css by default" do
+ test "precompile application.js and application.css and all other non JS/CSS files" do
app_file "app/assets/javascripts/application.js", "alert();"
app_file "app/assets/stylesheets/application.css", "body{}"
@@ -82,8 +78,11 @@ module ApplicationTests
app_file "app/assets/javascripts/something.min.js", "alert();"
app_file "app/assets/stylesheets/something.min.css", "body{}"
+ app_file "app/assets/javascripts/something.else.js.erb", "alert();"
+ app_file "app/assets/stylesheets/something.else.css.erb", "body{}"
+
images_should_compile = ["a.png", "happyface.png", "happy_face.png", "happy.face.png",
- "happy-face.png", "happy.happy_face.png", "happy_happy.face.png",
+ "happy-face.png", "happy.happy_face.png", "happy_happy.face.png",
"happy.happy.face.png", "happy", "happy.face", "-happyface",
"-happy.png", "-happy.face.png", "_happyface", "_happy.face.png",
"_happy.png"]
@@ -106,6 +105,9 @@ module ApplicationTests
assert !File.exists?("#{app_path}/public/assets/something.min.js")
assert !File.exists?("#{app_path}/public/assets/something.min.css")
+
+ assert !File.exists?("#{app_path}/public/assets/something.else.js")
+ assert !File.exists?("#{app_path}/public/assets/something.else.css")
end
test "asset pipeline should use a Sprockets::Index when config.assets.digest is true" do
@@ -202,7 +204,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match '/posts', :to => "posts#index"
+ get '/posts', :to => "posts#index"
end
RUBY
@@ -213,7 +215,9 @@ module ApplicationTests
app_file "app/assets/javascripts/app.js", "alert();"
require "#{app_path}/config/environment"
- class ::PostsController < ActionController::Base ; end
+ class ::PostsController < ActionController::Base
+ def show_detailed_exceptions?() true end
+ end
get '/posts'
assert_match(/AssetNotPrecompiledError/, last_response.body)
@@ -226,7 +230,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match '/posts', :to => "posts#index"
+ get '/posts', :to => "posts#index"
end
RUBY
@@ -310,7 +314,7 @@ module ApplicationTests
Dir.chdir(app_path){ `bundle exec rake assets:clean` }
end
- files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/*"]
+ files = Dir["#{app_path}/public/assets/**/*", "#{app_path}/tmp/cache/assets/*"]
assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}"
end
@@ -330,7 +334,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match '/omg', :to => "omg#index"
+ get '/omg', :to => "omg#index"
end
RUBY
@@ -378,8 +382,8 @@ module ApplicationTests
# the debug_assets params isn't used if compile is off
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application-([0-z]+)\.js" type="text\/javascript"><\/script>/, last_response.body)
- assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js" type="text\/javascript"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application-([0-z]+)\.js"><\/script>/, last_response.body)
+ assert_no_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js"><\/script>/, last_response.body)
end
test "assets aren't concatened when compile is true is on and debug_assets params is true" do
@@ -393,8 +397,8 @@ module ApplicationTests
class ::PostsController < ActionController::Base ; end
get '/posts?debug_assets=true'
- assert_match(/<script src="\/assets\/application-([0-z]+)\.js\?body=1" type="text\/javascript"><\/script>/, last_response.body)
- assert_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1" type="text\/javascript"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/application-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
+ assert_match(/<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1"><\/script>/, last_response.body)
end
test "assets can access model information when precompiling" do
@@ -419,6 +423,12 @@ module ApplicationTests
assert_equal "NoPost;\n", File.read("#{app_path}/public/assets/application.js")
end
+ test "initialization on the assets group should set assets_dir" do
+ require "#{app_path}/config/application"
+ Rails.application.initialize!(:assets)
+ assert_not_nil Rails.application.config.action_controller.assets_dir
+ end
+
test "enhancements to assets:precompile should only run once" do
app_file "lib/tasks/enhance.rake", "Rake::Task['assets:precompile'].enhance { puts 'enhancement' }"
output = precompile!
@@ -451,6 +461,49 @@ module ApplicationTests
assert_equal 0, files.length, "Expected application.js asset to be removed, but still exists"
end
+ test "asset urls should use the request's protocol by default" do
+ app_with_assets_in_view
+ add_to_config "config.asset_host = 'example.com'"
+ require "#{app_path}/config/environment"
+ class ::PostsController < ActionController::Base; end
+
+ get '/posts', {}, {'HTTPS'=>'off'}
+ assert_match('src="http://example.com/assets/application.js', last_response.body)
+ get '/posts', {}, {'HTTPS'=>'on'}
+ assert_match('src="https://example.com/assets/application.js', last_response.body)
+ end
+
+ test "asset urls should be protocol-relative if no request is in scope" do
+ app_file "app/assets/javascripts/image_loader.js.erb", 'var src="<%= image_path("rails.png") %>";'
+ add_to_config "config.assets.precompile = %w{image_loader.js}"
+ add_to_config "config.asset_host = 'example.com'"
+ precompile!
+
+ assert_match 'src="//example.com/assets/rails.png"', File.read("#{app_path}/public/assets/image_loader.js")
+ end
+
+ test "asset paths should use RAILS_RELATIVE_URL_ROOT by default" do
+ ENV["RAILS_RELATIVE_URL_ROOT"] = "/sub/uri"
+
+ app_file "app/assets/javascripts/app.js.erb", 'var src="<%= image_path("rails.png") %>";'
+ add_to_config "config.assets.precompile = %w{app.js}"
+ precompile!
+
+ assert_match 'src="/sub/uri/assets/rails.png"', File.read("#{app_path}/public/assets/app.js")
+ end
+
+ test "assets:cache:clean should clean cache" do
+ ENV["RAILS_ENV"] = "production"
+ precompile!
+
+ quietly do
+ Dir.chdir(app_path){ `bundle exec rake assets:cache:clean` }
+ end
+
+ require "#{app_path}/config/environment"
+ assert_equal 0, Dir.entries(Rails.application.assets.cache.cache_path).size - 2 # reject [".", ".."]
+ end
+
private
def app_with_assets_in_view
@@ -460,7 +513,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match '/posts', :to => "posts#index"
+ get '/posts', :to => "posts#index"
end
RUBY
end
diff --git a/railties/test/application/build_original_fullpath_test.rb b/railties/test/application/build_original_fullpath_test.rb
new file mode 100644
index 0000000000..647ffb097a
--- /dev/null
+++ b/railties/test/application/build_original_fullpath_test.rb
@@ -0,0 +1,27 @@
+require "abstract_unit"
+
+module ApplicationTests
+ class BuildOriginalPathTest < ActiveSupport::TestCase
+ def test_include_original_PATH_info_in_ORIGINAL_FULLPATH
+ env = { 'PATH_INFO' => '/foo/' }
+ assert_equal "/foo/", Rails.application.send(:build_original_fullpath, env)
+ end
+
+ def test_include_SCRIPT_NAME
+ env = {
+ 'SCRIPT_NAME' => '/foo',
+ 'PATH_INFO' => '/bar'
+ }
+
+ assert_equal "/foo/bar", Rails.application.send(:build_original_fullpath, env)
+ end
+
+ def test_include_QUERY_STRING
+ env = {
+ 'PATH_INFO' => '/foo',
+ 'QUERY_STRING' => 'bar',
+ }
+ assert_equal "/foo?bar", Rails.application.send(:build_original_fullpath, env)
+ end
+ end
+end
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 97ad47ac14..252dd0e31a 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -1,4 +1,5 @@
require "isolation/abstract_unit"
+require 'rack/test'
class ::MyMailInterceptor
def self.delivering_email(email); email; end
@@ -13,8 +14,9 @@ end
class ::MyOtherMailObserver < ::MyMailObserver; end
module ApplicationTests
- class ConfigurationTest < Test::Unit::TestCase
+ class ConfigurationTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
def new_app
File.expand_path("#{app_path}/../new_app")
@@ -39,6 +41,14 @@ module ApplicationTests
FileUtils.rm_rf(new_app) if File.directory?(new_app)
end
+ test "multiple queue construction is possible" do
+ require 'rails'
+ require "#{app_path}/config/environment"
+ mail_queue = Rails.application.build_queue
+ image_processing_queue = Rails.application.build_queue
+ assert_not_equal mail_queue, image_processing_queue
+ end
+
test "Rails.groups returns available groups" do
require "rails"
@@ -144,7 +154,7 @@ module ApplicationTests
test "frameworks are not preloaded by default" do
require "#{app_path}/config/environment"
- assert ActionController.autoload?(:RecordIdentifier)
+ assert ActionController.autoload?(:Caching)
end
test "frameworks are preloaded with config.preload_frameworks is set" do
@@ -154,7 +164,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
- assert !ActionController.autoload?(:RecordIdentifier)
+ assert !ActionController.autoload?(:Caching)
end
test "filter_parameters should be able to set via config.filter_parameters" do
@@ -181,20 +191,14 @@ module ApplicationTests
assert !$prepared
require "#{app_path}/config/environment"
- require 'rack/test'
- extend Rack::Test::Methods
get "/"
assert $prepared
end
def assert_utf8
- if RUBY_VERSION < '1.9'
- assert_equal "UTF8", $KCODE
- else
- assert_equal Encoding::UTF_8, Encoding.default_external
- assert_equal Encoding::UTF_8, Encoding.default_internal
- end
+ assert_equal Encoding::UTF_8, Encoding.default_external
+ assert_equal Encoding::UTF_8, Encoding.default_internal
end
test "skipping config.encoding still results in 'utf-8' as the default" do
@@ -250,6 +254,55 @@ module ApplicationTests
assert last_response.body =~ /csrf\-param/
end
+ test "default method for update can be changed" do
+ app_file 'app/models/post.rb', <<-RUBY
+ class Post
+ extend ActiveModel::Naming
+ def to_key; [1]; end
+ def persisted?; true; end
+ end
+ RUBY
+
+ app_file 'app/controllers/posts_controller.rb', <<-RUBY
+ class PostsController < ApplicationController
+ def show
+ render :inline => "<%= begin; form_for(Post.new) {}; rescue => e; e.to_s; end %>"
+ end
+
+ def update
+ render :text => "update"
+ end
+ end
+ RUBY
+
+ add_to_config <<-RUBY
+ routes.prepend do
+ resources :posts
+ end
+ RUBY
+
+ require "#{app_path}/config/environment"
+
+ token = "cf50faa3fe97702ca1ae"
+ PostsController.any_instance.stubs(:form_authenticity_token).returns(token)
+ params = {:authenticity_token => token}
+
+ get "/posts/1"
+ assert_match /patch/, last_response.body
+
+ patch "/posts/1", params
+ assert_match /update/, last_response.body
+
+ patch "/posts/1", params
+ assert_equal 200, last_response.status
+
+ put "/posts/1", params
+ assert_match /update/, last_response.body
+
+ put "/posts/1", params
+ assert_equal 200, last_response.status
+ end
+
test "request forgery token param can be changed" do
make_basic_app do
app.config.action_controller.request_forgery_protection_token = '_xsrf_token_here'
@@ -286,6 +339,19 @@ module ApplicationTests
assert_equal res, last_response.body # value should be unchanged
end
+ test "sets ActionDispatch.test_app" do
+ make_basic_app
+ assert_equal Rails.application, ActionDispatch.test_app
+ end
+
+ test "sets ActionDispatch::Response.default_charset" do
+ make_basic_app do |app|
+ app.config.action_dispatch.default_charset = "utf-16"
+ end
+
+ assert_equal "utf-16", ActionDispatch::Response.default_charset
+ end
+
test "sets all Active Record models to whitelist all attributes by default" do
add_to_config <<-RUBY
config.active_record.whitelist_attributes = true
@@ -474,23 +540,27 @@ module ApplicationTests
end
RUBY
+ app_file 'app/controllers/application_controller.rb', <<-RUBY
+ class ApplicationController < ActionController::Base
+ protect_from_forgery :with => :reset_session # as we are testing API here
+ end
+ RUBY
+
app_file 'app/controllers/posts_controller.rb', <<-RUBY
class PostsController < ApplicationController
- def index
+ def create
render :text => params[:post].inspect
end
end
RUBY
add_to_config <<-RUBY
- routes.append do
+ routes.prepend do
resources :posts
end
RUBY
require "#{app_path}/config/environment"
- require "rack/test"
- extend Rack::Test::Methods
post "/posts.json", '{ "title": "foo", "name": "bar" }', "CONTENT_TYPE" => "application/json"
assert_equal '{"title"=>"foo"}', last_response.body
@@ -521,9 +591,16 @@ module ApplicationTests
make_basic_app
assert_respond_to app, :env_config
- assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters
- assert_equal app.env_config['action_dispatch.secret_token'], app.config.secret_token
- assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions
+ assert_equal app.env_config['action_dispatch.parameter_filter'], app.config.filter_parameters
+ assert_equal app.env_config['action_dispatch.secret_token'], app.config.secret_token
+ assert_equal app.env_config['action_dispatch.show_exceptions'], app.config.action_dispatch.show_exceptions
+ assert_equal app.env_config['action_dispatch.logger'], Rails.logger
+ assert_equal app.env_config['action_dispatch.backtrace_cleaner'], Rails.backtrace_cleaner
+ end
+
+ test "config.colorize_logging default is true" do
+ make_basic_app
+ assert app.config.colorize_logging
end
end
end
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 1528d5dd87..f372afa51c 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -1,6 +1,6 @@
require 'isolation/abstract_unit'
-class ConsoleTest < Test::Unit::TestCase
+class ConsoleTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
@@ -18,16 +18,20 @@ class ConsoleTest < Test::Unit::TestCase
Rails.application.load_console
end
+ def irb_context
+ Object.new.extend(Rails::ConsoleMethods)
+ end
+
def test_app_method_should_return_integration_session
TestHelpers::Rack.send :remove_method, :app
load_environment
- console_session = app
+ console_session = irb_context.app
assert_instance_of ActionDispatch::Integration::Session, console_session
end
def test_new_session_should_return_integration_session
load_environment
- session = new_session
+ session = irb_context.new_session
assert_instance_of ActionDispatch::Integration::Session, session
end
@@ -41,7 +45,7 @@ class ConsoleTest < Test::Unit::TestCase
ActionDispatch::Reloader.to_prepare { c = 3 }
# Hide Reloading... output
- silence_stream(STDOUT) { reload! }
+ silence_stream(STDOUT) { irb_context.reload! }
assert_equal 1, a
assert_equal 2, b
@@ -57,7 +61,6 @@ class ConsoleTest < Test::Unit::TestCase
load_environment
assert User.new.respond_to?(:name)
- assert !User.new.respond_to?(:age)
app_file "app/models/user.rb", <<-MODEL
class User
@@ -66,12 +69,13 @@ class ConsoleTest < Test::Unit::TestCase
MODEL
assert !User.new.respond_to?(:age)
- silence_stream(STDOUT) { reload! }
+ silence_stream(STDOUT) { irb_context.reload! }
assert User.new.respond_to?(:age)
end
def test_access_to_helpers
load_environment
+ helper = irb_context.helper
assert_not_nil helper
assert_instance_of ActionView::Base, helper
assert_equal 'Once upon a time in a world...',
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index 8718f27de1..b80244f1d2 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -1,7 +1,7 @@
require "isolation/abstract_unit"
module ApplicationTests
- class GeneratorsTest < Test::Unit::TestCase
+ class GeneratorsTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
diff --git a/railties/test/application/initializers/boot_test.rb b/railties/test/application/initializers/boot_test.rb
index b1e01dc13f..04c46058cb 100644
--- a/railties/test/application/initializers/boot_test.rb
+++ b/railties/test/application/initializers/boot_test.rb
@@ -1,7 +1,7 @@
require "isolation/abstract_unit"
module ApplicationTests
- class GemBooting < Test::Unit::TestCase
+ class GemBooting < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
diff --git a/railties/test/application/initializers/check_ruby_version_test.rb b/railties/test/application/initializers/check_ruby_version_test.rb
deleted file mode 100644
index df7e9696a9..0000000000
--- a/railties/test/application/initializers/check_ruby_version_test.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require "isolation/abstract_unit"
-
-module ApplicationTests
- class CheckRubyVersionTest < Test::Unit::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- boot_rails
- end
-
- def teardown
- teardown_app
- end
-
- test "rails initializes with ruby 1.8.7 or later, except for 1.9.1" do
- if RUBY_VERSION < '1.8.7'
- assert_rails_does_not_boot
- elsif RUBY_VERSION == '1.9.1'
- assert_rails_does_not_boot
- else
- assert_rails_boots
- end
- end
-
- def assert_rails_boots
- assert_nothing_raised "It appears that rails does not boot" do
- require "rails/all"
- end
- end
-
- def assert_rails_does_not_boot
- $stderr = File.open("/dev/null", "w")
- assert_raises(SystemExit) do
- require "rails/all"
- end
- end
- end
-end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 446c85d65a..c2d4a0f2c8 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -1,7 +1,8 @@
require "isolation/abstract_unit"
+require 'set'
module ApplicationTests
- class FrameworksTest < Test::Unit::TestCase
+ class FrameworksTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
@@ -66,7 +67,7 @@ module ApplicationTests
require "#{app_path}/config/environment"
assert Foo.method_defined?(:foo_path)
assert Foo.method_defined?(:main_app)
- assert_equal ["notify"], Foo.action_methods
+ assert_equal Set.new(["notify"]), Foo.action_methods
end
test "allows to not load all helpers for controllers" do
@@ -115,7 +116,7 @@ module ApplicationTests
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match "/:controller(/:action)"
+ get "/:controller(/:action)"
end
RUBY
@@ -136,6 +137,13 @@ module ApplicationTests
assert_equal 2, ActionDispatch::Http::URL.tld_length
end
+ test "assignment config.encoding to default_charset" do
+ charset = 'Shift_JIS'
+ add_to_config "config.encoding = '#{charset}'"
+ require "#{app_path}/config/environment"
+ assert_equal charset, ActionDispatch::Response.default_charset
+ end
+
# AS
test "if there's no config.active_support.bare, all of ActiveSupport is required" do
use_frameworks []
@@ -186,5 +194,26 @@ module ApplicationTests
require "#{app_path}/config/environment"
assert_nil defined?(ActiveRecord::Base)
end
+
+ test "use schema cache dump" do
+ Dir.chdir(app_path) do
+ `rails generate model post title:string;
+ bundle exec rake db:migrate db:schema:cache:dump`
+ end
+ require "#{app_path}/config/environment"
+ ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test.
+ assert ActiveRecord::Base.connection.schema_cache.tables["posts"]
+ end
+
+ test "expire schema cache dump" do
+ Dir.chdir(app_path) do
+ `rails generate model post title:string;
+ bundle exec rake db:migrate db:schema:cache:dump db:rollback`
+ end
+ silence_warnings {
+ require "#{app_path}/config/environment"
+ assert !ActiveRecord::Base.connection.schema_cache.tables["posts"]
+ }
+ end
end
end
diff --git a/railties/test/application/initializers/hooks_test.rb b/railties/test/application/initializers/hooks_test.rb
index 8c7726339c..7b3e6844fd 100644
--- a/railties/test/application/initializers/hooks_test.rb
+++ b/railties/test/application/initializers/hooks_test.rb
@@ -1,7 +1,7 @@
require "isolation/abstract_unit"
module ApplicationTests
- class InitializersTest < Test::Unit::TestCase
+ class InitializersTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 8c2c079fb8..02d20bc150 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -1,7 +1,7 @@
require "isolation/abstract_unit"
module ApplicationTests
- class I18nTest < Test::Unit::TestCase
+ class I18nTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
@@ -85,7 +85,7 @@ en:
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '/i18n', :to => lambda { |env| [200, {}, [Foo.instance_variable_get('@foo')]] }
+ get '/i18n', :to => lambda { |env| [200, {}, [Foo.instance_variable_get('@foo')]] }
end
RUBY
@@ -109,7 +109,7 @@ en:
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] }
+ get '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] }
end
RUBY
@@ -120,6 +120,9 @@ en:
get "/i18n"
assert_equal "1", last_response.body
+ # Wait a full second so we have time for changes to propagate
+ sleep(1)
+
app_file "config/locales/en.yml", <<-YAML
en:
foo: "2"
diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb
index 644b8208a9..31811e7f92 100644
--- a/railties/test/application/initializers/load_path_test.rb
+++ b/railties/test/application/initializers/load_path_test.rb
@@ -1,7 +1,7 @@
require "isolation/abstract_unit"
module ApplicationTests
- class LoadPathTest < Test::Unit::TestCase
+ class LoadPathTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb
index b72c14eaf0..d866a63fe0 100644
--- a/railties/test/application/initializers/notifications_test.rb
+++ b/railties/test/application/initializers/notifications_test.rb
@@ -1,7 +1,7 @@
require "isolation/abstract_unit"
module ApplicationTests
- class NotificationsTest < Test::Unit::TestCase
+ class NotificationsTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 47c6fd5c6e..e0286502f3 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -1,6 +1,6 @@
require 'isolation/abstract_unit'
-class LoadingTest < Test::Unit::TestCase
+class LoadingTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
@@ -16,10 +16,11 @@ class LoadingTest < Test::Unit::TestCase
@app ||= Rails.application
end
- def test_constants_in_app_are_autoloaded
+ test "constants in app are autoloaded" do
app_file "app/models/post.rb", <<-MODEL
class Post < ActiveRecord::Base
validates_acceptance_of :title, :accept => "omg"
+ attr_accessible :title
end
MODEL
@@ -33,7 +34,7 @@ class LoadingTest < Test::Unit::TestCase
assert_equal 'omg', p.title
end
- def test_models_without_table_do_not_panic_on_scope_definitions_when_loaded
+ test "models without table do not panic on scope definitions when loaded" do
app_file "app/models/user.rb", <<-MODEL
class User < ActiveRecord::Base
default_scope where(:published => true)
@@ -63,9 +64,10 @@ class LoadingTest < Test::Unit::TestCase
assert ::AppTemplate::Application.config.loaded
end
- def test_descendants_are_cleaned_on_each_request_without_cache_classes
+ test "descendants loaded after framework initialization are cleaned on each request without cache classes" do
add_to_config <<-RUBY
config.cache_classes = false
+ config.reload_classes_only_on_change = false
RUBY
app_file "app/models/post.rb", <<-MODEL
@@ -75,8 +77,8 @@ class LoadingTest < Test::Unit::TestCase
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match '/load', :to => lambda { |env| [200, {}, Post.all] }
- match '/unload', :to => lambda { |env| [200, {}, []] }
+ get '/load', :to => lambda { |env| [200, {}, Post.all] }
+ get '/unload', :to => lambda { |env| [200, {}, []] }
end
RUBY
@@ -86,18 +88,213 @@ class LoadingTest < Test::Unit::TestCase
require "#{rails_root}/config/environment"
setup_ar!
- assert_equal [], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants
get "/load"
- assert_equal [Post], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration, Post], ActiveRecord::Base.descendants
get "/unload"
- assert_equal [], ActiveRecord::Base.descendants
+ assert_equal [ActiveRecord::SchemaMigration], ActiveRecord::Base.descendants
end
- test "initialize_cant_be_called_twice" do
+ test "initialize cant be called twice" do
require "#{app_path}/config/environment"
assert_raise(RuntimeError) { ::AppTemplate::Application.initialize! }
end
+ test "reload constants on development" do
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get '/c', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] }
+ end
+ RUBY
+
+ app_file "app/models/user.rb", <<-MODEL
+ class User
+ def self.counter; 1; end
+ end
+ MODEL
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ require "#{rails_root}/config/environment"
+
+ get "/c"
+ assert_equal "1", last_response.body
+
+ app_file "app/models/user.rb", <<-MODEL
+ class User
+ def self.counter; 2; end
+ end
+ MODEL
+
+ get "/c"
+ assert_equal "2", last_response.body
+ end
+
+ test "does not reload constants on development if custom file watcher always returns false" do
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ config.file_watcher = Class.new do
+ def initialize(*); end
+ def updated?; false; end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get '/c', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [User.counter.to_s]] }
+ end
+ RUBY
+
+ app_file "app/models/user.rb", <<-MODEL
+ class User
+ def self.counter; 1; end
+ end
+ MODEL
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ require "#{rails_root}/config/environment"
+
+ get "/c"
+ assert_equal "1", last_response.body
+
+ app_file "app/models/user.rb", <<-MODEL
+ class User
+ def self.counter; 2; end
+ end
+ MODEL
+
+ get "/c"
+ assert_equal "1", last_response.body
+ end
+
+ test "added files (like db/schema.rb) also trigger reloading" do
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ $counter = 0
+ AppTemplate::Application.routes.draw do
+ get '/c', :to => lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] }
+ end
+ RUBY
+
+ app_file "app/models/user.rb", <<-MODEL
+ class User
+ $counter += 1
+ end
+ MODEL
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ require "#{rails_root}/config/environment"
+
+ get "/c"
+ assert_equal "1", last_response.body
+
+ app_file "db/schema.rb", ""
+
+ get "/c"
+ assert_equal "2", last_response.body
+ end
+
+ test "columns migrations also trigger reloading" do
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get '/title', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.title]] }
+ get '/body', :to => lambda { |env| [200, {"Content-Type" => "text/plain"}, [Post.new.body]] }
+ end
+ RUBY
+
+ app_file "app/models/post.rb", <<-MODEL
+ class Post < ActiveRecord::Base
+ end
+ MODEL
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ app_file "db/migrate/1_create_posts.rb", <<-MIGRATION
+ class CreatePosts < ActiveRecord::Migration
+ def change
+ create_table :posts do |t|
+ t.string :title, :default => "TITLE"
+ end
+ end
+ end
+ MIGRATION
+
+ Dir.chdir(app_path) { `rake db:migrate`}
+ require "#{rails_root}/config/environment"
+
+ get "/title"
+ assert_equal "TITLE", last_response.body
+
+ app_file "db/migrate/2_add_body_to_posts.rb", <<-MIGRATION
+ class AddBodyToPosts < ActiveRecord::Migration
+ def change
+ add_column :posts, :body, :text, :default => "BODY"
+ end
+ end
+ MIGRATION
+
+ Dir.chdir(app_path) { `rake db:migrate` }
+
+ get "/body"
+ assert_equal "BODY", last_response.body
+ end
+
+ test "AC load hooks can be used with metal" do
+ app_file "app/controllers/omg_controller.rb", <<-RUBY
+ begin
+ class OmgController < ActionController::Metal
+ ActiveSupport.run_load_hooks(:action_controller, self)
+ def show
+ self.response_body = ["OK"]
+ end
+ end
+ rescue => e
+ puts "Error loading metal: \#{e.class} \#{e.message}"
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get "/:controller(/:action)"
+ end
+ RUBY
+
+ require "#{rails_root}/config/environment"
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ get '/omg/show'
+ assert_equal 'OK', last_response.body
+ end
+
+ def test_initialize_can_be_called_at_any_time
+ require "#{app_path}/config/application"
+
+ assert !Rails.initialized?
+ assert !AppTemplate::Application.initialized?
+ Rails.initialize!
+ assert Rails.initialized?
+ assert AppTemplate::Application.initialized?
+ end
+
protected
def setup_ar!
diff --git a/railties/test/application/middleware/best_practices_test.rb b/railties/test/application/middleware/best_practices_test.rb
index 1c88b9bf06..f6783c6ca2 100644
--- a/railties/test/application/middleware/best_practices_test.rb
+++ b/railties/test/application/middleware/best_practices_test.rb
@@ -1,7 +1,7 @@
require 'isolation/abstract_unit'
module ApplicationTests
- class BestPracticesTest < Test::Unit::TestCase
+ class BestPracticesTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
diff --git a/railties/test/application/middleware/cache_test.rb b/railties/test/application/middleware/cache_test.rb
index 050a2161ae..54b18542c2 100644
--- a/railties/test/application/middleware/cache_test.rb
+++ b/railties/test/application/middleware/cache_test.rb
@@ -1,7 +1,7 @@
require 'isolation/abstract_unit'
module ApplicationTests
- class RoutingTest < Test::Unit::TestCase
+ class RoutingTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
@@ -46,7 +46,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
RUBY
end
@@ -54,9 +54,9 @@ module ApplicationTests
def test_cache_keeps_if_modified_since
simple_controller
expected = "Wed, 30 May 1984 19:43:31 GMT"
-
+
get "/expires/keeps_if_modified_since", {}, "HTTP_IF_MODIFIED_SINCE" => expected
-
+
assert_equal 200, last_response.status
assert_equal expected, last_response.body, "cache should have kept If-Modified-Since"
end
diff --git a/railties/test/application/middleware/cookies_test.rb b/railties/test/application/middleware/cookies_test.rb
new file mode 100644
index 0000000000..18af7abafc
--- /dev/null
+++ b/railties/test/application/middleware/cookies_test.rb
@@ -0,0 +1,47 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ class CookiesTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def new_app
+ File.expand_path("#{app_path}/../new_app")
+ end
+
+ def setup
+ build_app
+ boot_rails
+ FileUtils.rm_rf("#{app_path}/config/environments")
+ end
+
+ def teardown
+ teardown_app
+ FileUtils.rm_rf(new_app) if File.directory?(new_app)
+ end
+
+ test 'always_write_cookie is true by default in development' do
+ require 'rails'
+ Rails.env = 'development'
+ require "#{app_path}/config/environment"
+ assert_equal true, ActionDispatch::Cookies::CookieJar.always_write_cookie
+ end
+
+ test 'always_write_cookie is false by default in production' do
+ require 'rails'
+ Rails.env = 'production'
+ require "#{app_path}/config/environment"
+ assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie
+ end
+
+ test 'always_write_cookie can be overrided' do
+ add_to_config <<-RUBY
+ config.action_dispatch.always_write_cookie = false
+ RUBY
+
+ require 'rails'
+ Rails.env = 'development'
+ require "#{app_path}/config/environment"
+ assert_equal false, ActionDispatch::Cookies::CookieJar.always_write_cookie
+ end
+ end
+end
diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb
new file mode 100644
index 0000000000..d1a614e181
--- /dev/null
+++ b/railties/test/application/middleware/exceptions_test.rb
@@ -0,0 +1,117 @@
+# encoding: utf-8
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class MiddlewareExceptionsTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "show exceptions middleware filter backtrace before logging" do
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def index
+ raise 'oops'
+ end
+ end
+ RUBY
+
+ get "/foo"
+ assert_equal 500, last_response.status
+
+ log = File.read(Rails.application.config.paths["log"].first)
+ assert_no_match(/action_dispatch/, log, log)
+ assert_match(/oops/, log, log)
+ end
+
+ test "renders active record exceptions as 404" do
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def index
+ raise ActiveRecord::RecordNotFound
+ end
+ end
+ RUBY
+
+ get "/foo"
+ assert_equal 404, last_response.status
+ end
+
+ test "uses custom exceptions app" do
+ add_to_config <<-RUBY
+ config.exceptions_app = lambda do |env|
+ [404, { "Content-Type" => "text/plain" }, ["YOU FAILED BRO"]]
+ end
+ RUBY
+
+ app.config.action_dispatch.show_exceptions = true
+
+ get "/foo"
+ assert_equal 404, last_response.status
+ assert_equal "YOU FAILED BRO", last_response.body
+ end
+
+ test "unspecified route when action_dispatch.show_exceptions is not set raises an exception" do
+ app.config.action_dispatch.show_exceptions = false
+
+ assert_raise(ActionController::RoutingError) do
+ get '/foo'
+ end
+ end
+
+ test "unspecified route when action_dispatch.show_exceptions is set shows 404" do
+ app.config.action_dispatch.show_exceptions = true
+
+ assert_nothing_raised(ActionController::RoutingError) do
+ get '/foo'
+ assert_match "The page you were looking for doesn't exist.", last_response.body
+ end
+ end
+
+ test "unspecified route when action_dispatch.show_exceptions and consider_all_requests_local are set shows diagnostics" do
+ app.config.action_dispatch.show_exceptions = true
+ app.config.consider_all_requests_local = true
+
+ assert_nothing_raised(ActionController::RoutingError) do
+ get '/foo'
+ assert_match "No route matches", last_response.body
+ end
+ end
+
+ test "displays diagnostics message when exception raised in template that contains UTF-8" do
+ app.config.action_dispatch.show_exceptions = true
+ app.config.consider_all_requests_local = true
+
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def index
+ end
+ end
+ RUBY
+
+ app_file 'app/views/foo/index.html.erb', <<-ERB
+ <% raise 'boooom' %>
+ ✓測試テスト시험
+ ERB
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ post ':controller(/:action)'
+ end
+ RUBY
+
+ post '/foo', :utf8 => '✓'
+ assert_match(/boooom/, last_response.body)
+ assert_match(/測試テスト시험/, last_response.body)
+ end
+ end
+end
diff --git a/railties/test/application/middleware/remote_ip_test.rb b/railties/test/application/middleware/remote_ip_test.rb
index da291f061c..066f0c1c84 100644
--- a/railties/test/application/middleware/remote_ip_test.rb
+++ b/railties/test/application/middleware/remote_ip_test.rb
@@ -1,7 +1,7 @@
require 'isolation/abstract_unit'
module ApplicationTests
- class RemoteIpTest < Test::Unit::TestCase
+ class RemoteIpTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
diff --git a/railties/test/application/middleware/sendfile_test.rb b/railties/test/application/middleware/sendfile_test.rb
index d2ad2668bb..eb791f5687 100644
--- a/railties/test/application/middleware/sendfile_test.rb
+++ b/railties/test/application/middleware/sendfile_test.rb
@@ -1,7 +1,7 @@
require 'isolation/abstract_unit'
module ApplicationTests
- class SendfileTest < Test::Unit::TestCase
+ class SendfileTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
@@ -57,5 +57,18 @@ module ApplicationTests
get "/"
assert_equal File.expand_path(__FILE__), last_response.headers["X-Lighttpd-Send-File"]
end
+
+ test "files handled by ActionDispatch::Static are handled by Rack::Sendfile" do
+ make_basic_app do |app|
+ app.config.action_dispatch.x_sendfile_header = 'X-Sendfile'
+ app.config.serve_static_assets = true
+ app.paths["public"] = File.join(rails_root, "public")
+ end
+
+ app_file "public/foo.txt", "foo"
+
+ get "/foo.txt", "HTTP_X_SENDFILE_TYPE" => "X-Sendfile"
+ assert_equal File.join(rails_root, "public/foo.txt"), last_response.headers["X-Sendfile"]
+ end
end
end
diff --git a/railties/test/application/middleware/session_test.rb b/railties/test/application/middleware/session_test.rb
new file mode 100644
index 0000000000..07134cc935
--- /dev/null
+++ b/railties/test/application/middleware/session_test.rb
@@ -0,0 +1,50 @@
+# encoding: utf-8
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class MiddlewareSessionTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app
+ boot_rails
+ FileUtils.rm_rf "#{app_path}/config/environments"
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ def app
+ @app ||= Rails.application
+ end
+
+ test "config.force_ssl sets cookie to secure only" do
+ add_to_config "config.force_ssl = true"
+ require "#{app_path}/config/environment"
+ assert app.config.session_options[:secure], "Expected session to be marked as secure"
+ end
+
+ test "session is not loaded if it's not used" do
+ make_basic_app
+
+ class ::OmgController < ActionController::Base
+ def index
+ if params[:flash]
+ flash[:notice] = "notice"
+ end
+
+ render :nothing => true
+ end
+ end
+
+ get "/?flash=true"
+ get "/"
+
+ assert last_request.env["HTTP_COOKIE"]
+ assert !last_response.headers["Set-Cookie"]
+ end
+ end
+end
diff --git a/railties/test/application/middleware/show_exceptions_test.rb b/railties/test/application/middleware/show_exceptions_test.rb
deleted file mode 100644
index e3f27f63c3..0000000000
--- a/railties/test/application/middleware/show_exceptions_test.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-require 'isolation/abstract_unit'
-
-module ApplicationTests
- class ShowExceptionsTest < Test::Unit::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- boot_rails
- FileUtils.rm_rf "#{app_path}/config/environments"
- end
-
- def teardown
- teardown_app
- end
-
- def app
- @app ||= Rails.application
- end
-
- test "unspecified route when set action_dispatch.show_exceptions to false" do
- make_basic_app do |app|
- app.config.action_dispatch.show_exceptions = false
- end
-
- assert_raise(ActionController::RoutingError) do
- get '/foo'
- end
- end
-
- test "unspecified route when set action_dispatch.show_exceptions to true" do
- make_basic_app do |app|
- app.config.action_dispatch.show_exceptions = true
- end
-
- assert_nothing_raised(ActionController::RoutingError) do
- get '/foo'
- end
- end
- end
-end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 093cb6ca2a..ba747bc633 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -1,8 +1,9 @@
require 'isolation/abstract_unit'
require 'stringio'
+require 'rack/test'
module ApplicationTests
- class MiddlewareTest < Test::Unit::TestCase
+ class MiddlewareTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
@@ -25,15 +26,17 @@ module ApplicationTests
boot!
assert_equal [
+ "Rack::Sendfile",
"ActionDispatch::Static",
"Rack::Lock",
"ActiveSupport::Cache::Strategy::LocalCache",
"Rack::Runtime",
"Rack::MethodOverride",
+ "ActionDispatch::RequestId",
"Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods
"ActionDispatch::ShowExceptions",
+ "ActionDispatch::DebugExceptions",
"ActionDispatch::RemoteIp",
- "Rack::Sendfile",
"ActionDispatch::Reloader",
"ActionDispatch::Callbacks",
"ActiveRecord::ConnectionAdapters::ConnectionManagement",
@@ -63,17 +66,17 @@ module ApplicationTests
assert_equal "Rack::Cache", middleware.first
end
- test "Rack::SSL is present when force_ssl is set" do
+ test "ActionDispatch::SSL is present when force_ssl is set" do
add_to_config "config.force_ssl = true"
boot!
- assert middleware.include?("Rack::SSL")
+ assert middleware.include?("ActionDispatch::SSL")
end
- test "Rack::SSL is configured with options when given" do
+ test "ActionDispatch::SSL is configured with options when given" do
add_to_config "config.force_ssl = true"
add_to_config "config.ssl_options = { :host => 'example.com' }"
boot!
-
+
assert_equal AppTemplate::Application.middleware.first.args, [{:host => 'example.com'}]
end
@@ -82,7 +85,6 @@ module ApplicationTests
boot!
assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement")
assert !middleware.include?("ActiveRecord::QueryCache")
- assert !middleware.include?("ActiveRecord::IdentityMap::Middleware")
end
test "removes lock if allow concurrency is set" do
@@ -103,10 +105,11 @@ module ApplicationTests
assert !middleware.include?("ActionDispatch::Static")
end
- test "includes show exceptions even action_dispatch.show_exceptions is disabled" do
+ test "includes exceptions middlewares even if action_dispatch.show_exceptions is disabled" do
add_to_config "config.action_dispatch.show_exceptions = false"
boot!
assert middleware.include?("ActionDispatch::ShowExceptions")
+ assert middleware.include?("ActionDispatch::DebugExceptions")
end
test "removes ActionDispatch::Reloader if cache_classes is true" do
@@ -128,29 +131,30 @@ module ApplicationTests
assert_equal "Rack::Config", middleware.second
end
- test "RAILS_CACHE does not respond to middleware" do
+ test "Rails.cache does not respond to middleware" do
add_to_config "config.cache_store = :memory_store"
boot!
assert_equal "Rack::Runtime", middleware.third
end
- test "RAILS_CACHE does respond to middleware" do
+ test "Rails.cache does respond to middleware" do
boot!
assert_equal "Rack::Runtime", middleware.fourth
end
- test "identity map is inserted" do
- add_to_config "config.active_record.identity_map = true"
- boot!
- assert middleware.include?("ActiveRecord::IdentityMap::Middleware")
- end
-
test "insert middleware before" do
add_to_config "config.middleware.insert_before ActionDispatch::Static, Rack::Config"
boot!
assert_equal "Rack::Config", middleware.first
end
+ test "can't change middleware after it's built" do
+ boot!
+ assert_raise RuntimeError do
+ app.config.middleware.use Rack::Config
+ end
+ end
+
# ConditionalGet + Etag
test "conditional get + etag middlewares handle http caching based on body" do
make_basic_app
@@ -182,7 +186,6 @@ module ApplicationTests
assert_equal etag, last_response.headers["Etag"]
get "/?nothing=true"
- puts last_response.body
assert_equal 200, last_response.status
assert_equal "", last_response.body
assert_equal "text/html; charset=utf-8", last_response.headers["Content-Type"]
@@ -190,24 +193,12 @@ module ApplicationTests
assert_equal nil, last_response.headers["Etag"]
end
- # Show exceptions middleware
- test "show exceptions middleware filter backtrace before logging" do
- my_middleware = Struct.new(:app) do
- def call(env)
- raise "Failure"
- end
- end
-
- make_basic_app do |app|
- app.config.middleware.use my_middleware
- end
-
- stringio = StringIO.new
- Rails.logger = Logger.new(stringio)
-
- env = Rack::MockRequest.env_for("/")
+ test "ORIGINAL_FULLPATH is passed to env" do
+ boot!
+ env = ::Rack::MockRequest.env_for("/foo/?something")
Rails.application.call(env)
- assert_no_match(/action_dispatch/, stringio.string)
+
+ assert_equal "/foo/?something", env["ORIGINAL_FULLPATH"]
end
private
diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb
index 964cff48cd..e0893f53be 100644
--- a/railties/test/application/paths_test.rb
+++ b/railties/test/application/paths_test.rb
@@ -1,7 +1,7 @@
require "isolation/abstract_unit"
module ApplicationTests
- class PathsTest < Test::Unit::TestCase
+ class PathsTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
@@ -15,7 +15,6 @@ module ApplicationTests
app.config.session_store nil
end
RUBY
- use_frameworks [:action_controller, :action_view, :action_mailer, :active_record]
require "#{app_path}/config/environment"
@paths = Rails.application.config.paths
end
@@ -46,12 +45,13 @@ module ApplicationTests
assert_path @paths["app/views"], "app/views"
assert_path @paths["lib"], "lib"
assert_path @paths["vendor"], "vendor"
- assert_path @paths["vendor/plugins"], "vendor/plugins"
assert_path @paths["tmp"], "tmp"
assert_path @paths["config"], "config"
assert_path @paths["config/locales"], "config/locales/en.yml"
assert_path @paths["config/environment"], "config/environment.rb"
assert_path @paths["config/environments"], "config/environments/development.rb"
+ assert_path @paths["config/routes.rb"], "config/routes.rb"
+ assert_path @paths["config/routes"], "config/routes"
assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first
end
diff --git a/railties/test/application/queue_test.rb b/railties/test/application/queue_test.rb
new file mode 100644
index 0000000000..da8bdeed52
--- /dev/null
+++ b/railties/test/application/queue_test.rb
@@ -0,0 +1,145 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class GeneratorsTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ def app_const
+ @app_const ||= Class.new(Rails::Application)
+ end
+
+ test "the queue is a TestQueue in test mode" do
+ app("test")
+ assert_kind_of Rails::Queueing::TestQueue, Rails.application.queue
+ assert_kind_of Rails::Queueing::TestQueue, Rails.queue
+ end
+
+ test "the queue is a Queue in development mode" do
+ app("development")
+ assert_kind_of Rails::Queueing::Queue, Rails.application.queue
+ assert_kind_of Rails::Queueing::Queue, Rails.queue
+ end
+
+ test "in development mode, an enqueued job will be processed in a separate thread" do
+ app("development")
+
+ job = Struct.new(:origin, :target).new(Thread.current)
+ def job.run
+ self.target = Thread.current
+ end
+
+ Rails.queue.push job
+ sleep 0.1
+
+ assert job.target, "The job was run"
+ assert_not_equal job.origin, job.target
+ end
+
+ test "in test mode, explicitly draining the queue will process it in a separate thread" do
+ app("test")
+
+ job = Struct.new(:origin, :target).new(Thread.current)
+ def job.run
+ self.target = Thread.current
+ end
+
+ Rails.queue.push job
+ Rails.queue.drain
+
+ assert job.target, "The job was run"
+ assert_not_equal job.origin, job.target
+ end
+
+ test "in test mode, the queue can be observed" do
+ app("test")
+
+ job = Struct.new(:id) do
+ def run
+ end
+ end
+
+ jobs = (1..10).map do |id|
+ job.new(id)
+ end
+
+ jobs.each do |job|
+ Rails.queue.push job
+ end
+
+ assert_equal jobs, Rails.queue.jobs
+ end
+
+ def setup_custom_queue
+ add_to_env_config "production", <<-RUBY
+ require "my_queue"
+ config.queue = MyQueue
+ RUBY
+
+ app_file "lib/my_queue.rb", <<-RUBY
+ class MyQueue
+ def push(job)
+ job.run
+ end
+ end
+ RUBY
+
+ app("production")
+ end
+
+ test "a custom queue implementation can be provided" do
+ setup_custom_queue
+
+ assert_kind_of MyQueue, Rails.queue
+
+ job = Struct.new(:id, :ran) do
+ def run
+ self.ran = true
+ end
+ end
+
+ job1 = job.new(1)
+ Rails.queue.push job1
+
+ assert_equal true, job1.ran
+ end
+
+ test "a custom consumer implementation can be provided" do
+ add_to_env_config "production", <<-RUBY
+ require "my_queue_consumer"
+ config.queue_consumer = MyQueueConsumer
+ RUBY
+
+ app_file "lib/my_queue_consumer.rb", <<-RUBY
+ class MyQueueConsumer < Rails::Queueing::ThreadedConsumer
+ attr_reader :started
+
+ def start
+ @started = true
+ self
+ end
+ end
+ RUBY
+
+ app("production")
+
+ assert_kind_of MyQueueConsumer, Rails.application.queue_consumer
+ assert Rails.application.queue_consumer.started
+ end
+
+ test "default consumer is not used with custom queue implementation" do
+ setup_custom_queue
+
+ assert_nil Rails.application.queue_consumer
+ end
+ end
+end
diff --git a/railties/test/application/rack/logger_test.rb b/railties/test/application/rack/logger_test.rb
index 8b2b2f1802..a77c6f472c 100644
--- a/railties/test/application/rack/logger_test.rb
+++ b/railties/test/application/rack/logger_test.rb
@@ -4,7 +4,7 @@ require "rack/test"
module ApplicationTests
module RackTests
- class LoggerTest < Test::Unit::TestCase
+ class LoggerTest < ActiveSupport::TestCase
include ActiveSupport::LogSubscriber::TestHelper
include Rack::Test::Methods
@@ -12,6 +12,8 @@ module ApplicationTests
build_app
require "#{app_path}/config/environment"
super
+ @logger = MockLogger.new
+ Rails.stubs(:logger).returns(@logger)
end
def teardown
diff --git a/railties/test/application/rackup_test.rb b/railties/test/application/rackup_test.rb
index ff9cdcadc7..49ac9fc66c 100644
--- a/railties/test/application/rackup_test.rb
+++ b/railties/test/application/rackup_test.rb
@@ -1,12 +1,12 @@
require "isolation/abstract_unit"
module ApplicationTests
- class RackupTest < Test::Unit::TestCase
+ class RackupTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def rackup
require "rack"
- app, options = Rack::Builder.parse_file("#{app_path}/config.ru")
+ app, _ = Rack::Builder.parse_file("#{app_path}/config.ru")
app
end
diff --git a/railties/test/application/rake/migrations_test.rb b/railties/test/application/rake/migrations_test.rb
new file mode 100644
index 0000000000..0a47fd014c
--- /dev/null
+++ b/railties/test/application/rake/migrations_test.rb
@@ -0,0 +1,158 @@
+require "isolation/abstract_unit"
+
+module ApplicationTests
+ module RakeTests
+ class RakeMigrationsTest < ActiveSupport::TestCase
+ def setup
+ build_app
+ boot_rails
+ FileUtils.rm_rf("#{app_path}/config/environments")
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test 'running migrations with given scope' do
+ Dir.chdir(app_path) do
+ `rails generate model user username:string password:string`
+
+ app_file "db/migrate/01_a_migration.bukkits.rb", <<-MIGRATION
+ class AMigration < ActiveRecord::Migration
+ end
+ MIGRATION
+
+ output = `rake db:migrate SCOPE=bukkits`
+ assert_no_match(/create_table\(:users\)/, output)
+ assert_no_match(/CreateUsers/, output)
+ assert_no_match(/add_column\(:users, :email, :string\)/, output)
+
+ assert_match(/AMigration: migrated/, output)
+
+ output = `rake db:migrate SCOPE=bukkits VERSION=0`
+ assert_no_match(/drop_table\(:users\)/, output)
+ assert_no_match(/CreateUsers/, output)
+ assert_no_match(/remove_column\(:users, :email\)/, output)
+
+ assert_match(/AMigration: reverted/, output)
+ end
+ end
+
+ test 'model and migration generator with change syntax' do
+ Dir.chdir(app_path) do
+ `rails generate model user username:string password:string;
+ rails generate migration add_email_to_users email:string`
+
+ output = `rake db:migrate`
+ assert_match(/create_table\(:users\)/, output)
+ assert_match(/CreateUsers: migrated/, output)
+ assert_match(/add_column\(:users, :email, :string\)/, output)
+ assert_match(/AddEmailToUsers: migrated/, output)
+
+ output = `rake db:rollback STEP=2`
+ assert_match(/drop_table\("users"\)/, output)
+ assert_match(/CreateUsers: reverted/, output)
+ assert_match(/remove_column\("users", :email\)/, output)
+ assert_match(/AddEmailToUsers: reverted/, output)
+ end
+ end
+
+ test 'migration status when schema migrations table is not present' do
+ output = Dir.chdir(app_path){ `rake db:migrate:status` }
+ assert_equal "Schema migrations table does not exist yet.\n", output
+ end
+
+ test 'test migration status' do
+ Dir.chdir(app_path) do
+ `rails generate model user username:string password:string;
+ rails generate migration add_email_to_users email:string;
+ rake db:migrate`
+
+ output = `rake db:migrate:status`
+
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+Add email to users/, output)
+
+ `rake db:rollback STEP=1`
+ output = `rake db:migrate:status`
+
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/down\s+\d{14}\s+Add email to users/, output)
+ end
+ end
+
+ test 'migration status without timestamps' do
+ add_to_config('config.active_record.timestamped_migrations = false')
+
+ Dir.chdir(app_path) do
+ `rails generate model user username:string password:string;
+ rails generate migration add_email_to_users email:string;
+ rake db:migrate`
+
+ output = `rake db:migrate:status`
+
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/up\s+\d{3,}\s+Add email to users/, output)
+
+ `rake db:rollback STEP=1`
+ output = `rake db:migrate:status`
+
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/down\s+\d{3,}\s+Add email to users/, output)
+ end
+ end
+
+ test 'test migration status after rollback and redo' do
+ Dir.chdir(app_path) do
+ `rails generate model user username:string password:string;
+ rails generate migration add_email_to_users email:string;
+ rake db:migrate`
+
+ output = `rake db:migrate:status`
+
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+Add email to users/, output)
+
+ `rake db:rollback STEP=2`
+ output = `rake db:migrate:status`
+
+ assert_match(/down\s+\d{14}\s+Create users/, output)
+ assert_match(/down\s+\d{14}\s+Add email to users/, output)
+
+ `rake db:migrate:redo`
+ output = `rake db:migrate:status`
+
+ assert_match(/up\s+\d{14}\s+Create users/, output)
+ assert_match(/up\s+\d{14}\s+Add email to users/, output)
+ end
+ end
+
+ test 'migration status after rollback and redo without timestamps' do
+ add_to_config('config.active_record.timestamped_migrations = false')
+
+ Dir.chdir(app_path) do
+ `rails generate model user username:string password:string;
+ rails generate migration add_email_to_users email:string;
+ rake db:migrate`
+
+ output = `rake db:migrate:status`
+
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/up\s+\d{3,}\s+Add email to users/, output)
+
+ `rake db:rollback STEP=2`
+ output = `rake db:migrate:status`
+
+ assert_match(/down\s+\d{3,}\s+Create users/, output)
+ assert_match(/down\s+\d{3,}\s+Add email to users/, output)
+
+ `rake db:migrate:redo`
+ output = `rake db:migrate:status`
+
+ assert_match(/up\s+\d{3,}\s+Create users/, output)
+ assert_match(/up\s+\d{3,}\s+Add email to users/, output)
+ end
+ end
+ end
+ end
+end
diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb
new file mode 100644
index 0000000000..05d73dfc5c
--- /dev/null
+++ b/railties/test/application/rake/notes_test.rb
@@ -0,0 +1,134 @@
+require "isolation/abstract_unit"
+
+module ApplicationTests
+ module RakeTests
+ class RakeNotesTest < ActiveSupport::TestCase
+ def setup
+ build_app
+ require "rails/all"
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test 'notes finds notes for certain file_types' do
+ app_file "app/views/home/index.html.erb", "<% # TODO: note in erb %>"
+ app_file "app/views/home/index.html.haml", "-# TODO: note in haml"
+ app_file "app/views/home/index.html.slim", "/ TODO: note in slim"
+ app_file "app/assets/javascripts/application.js.coffee", "# TODO: note in coffee"
+ app_file "app/assets/javascripts/application.js", "// TODO: note in js"
+ app_file "app/assets/stylesheets/application.css", "// TODO: note in css"
+ app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss"
+ app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby"
+
+ boot_rails
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+
+ Rails.application.load_tasks
+
+ Dir.chdir(app_path) do
+ output = `bundle exec rake notes`
+ lines = output.scan(/\[([0-9\s]+)\](\s)/)
+
+ assert_match /note in erb/, output
+ assert_match /note in haml/, output
+ assert_match /note in slim/, output
+ assert_match /note in ruby/, output
+ assert_match /note in coffee/, output
+ assert_match /note in js/, output
+ assert_match /note in css/, output
+ assert_match /note in scss/, output
+
+ assert_equal 8, lines.size
+
+ lines.each do |line|
+ assert_equal 4, line[0].size
+ assert_equal ' ', line[1]
+ end
+ end
+ end
+
+ test 'notes finds notes in default directories' do
+ app_file "app/controllers/some_controller.rb", "# TODO: note in app directory"
+ app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory"
+ app_file "lib/some_file.rb", "# TODO: note in lib directory"
+ app_file "script/run_something.rb", "# TODO: note in script directory"
+ app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory"
+
+ app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
+
+ boot_rails
+
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+
+ Rails.application.load_tasks
+
+ Dir.chdir(app_path) do
+ output = `bundle exec rake notes`
+ lines = output.scan(/\[([0-9\s]+)\]/).flatten
+
+ assert_match /note in app directory/, output
+ assert_match /note in config directory/, output
+ assert_match /note in lib directory/, output
+ assert_match /note in script directory/, output
+ assert_match /note in test directory/, output
+ assert_no_match /note in some_other directory/, output
+
+ assert_equal 5, lines.size
+
+ lines.each do |line_number|
+ assert_equal 4, line_number.size
+ end
+ end
+ end
+
+ test 'notes finds notes in custom directories' do
+ app_file "app/controllers/some_controller.rb", "# TODO: note in app directory"
+ app_file "config/initializers/some_initializer.rb", "# TODO: note in config directory"
+ app_file "lib/some_file.rb", "# TODO: note in lib directory"
+ app_file "script/run_something.rb", "# TODO: note in script directory"
+ app_file "test/some_test.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in test directory"
+
+ app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory"
+
+ boot_rails
+
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+
+ Rails.application.load_tasks
+
+ Dir.chdir(app_path) do
+ output = `SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bundle exec rake notes`
+ lines = output.scan(/\[([0-9\s]+)\]/).flatten
+
+ assert_match /note in app directory/, output
+ assert_match /note in config directory/, output
+ assert_match /note in lib directory/, output
+ assert_match /note in script directory/, output
+ assert_match /note in test directory/, output
+
+ assert_match /note in some_other directory/, output
+
+ assert_equal 6, lines.size
+
+ lines.each do |line_number|
+ assert_equal 4, line_number.size
+ end
+ end
+ end
+
+ private
+ def boot_rails
+ super
+ require "#{app_path}/config/environment"
+ end
+ end
+ end
+end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index c76bc3d526..27d521485c 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -2,7 +2,7 @@
require "isolation/abstract_unit"
module ApplicationTests
- class RakeTest < Test::Unit::TestCase
+ class RakeTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
@@ -63,26 +63,23 @@ module ApplicationTests
def test_rake_test_error_output
Dir.chdir(app_path){ `rake db:migrate` }
- app_file "config/database.yml", <<-RUBY
- development:
- RUBY
-
app_file "test/unit/one_unit_test.rb", <<-RUBY
+ raise 'unit'
RUBY
app_file "test/functional/one_functional_test.rb", <<-RUBY
- raise RuntimeError
+ raise 'functional'
RUBY
app_file "test/integration/one_integration_test.rb", <<-RUBY
- raise RuntimeError
+ raise 'integration'
RUBY
silence_stderr do
- output = Dir.chdir(app_path){ `rake test` }
- assert_match(/Errors running test:units! #<ActiveRecord::AdapterNotSpecified/, output)
- assert_match(/Errors running test:functionals! #<RuntimeError/, output)
- assert_match(/Errors running test:integration! #<RuntimeError/, output)
+ output = Dir.chdir(app_path) { `rake test 2>&1` }
+ assert_match 'unit', output
+ assert_match 'functional', output
+ assert_match 'integration', output
end
end
@@ -108,98 +105,67 @@ module ApplicationTests
assert_match "Sample log message", output
end
- def test_model_and_migration_generator_with_change_syntax
+ def test_loading_specific_fixtures
Dir.chdir(app_path) do
- `rails generate model user username:string password:string`
- `rails generate migration add_email_to_users email:string`
+ `rails generate model user username:string password:string;
+ rails generate model product name:string;
+ rake db:migrate`
end
- output = Dir.chdir(app_path){ `rake db:migrate` }
- assert_match(/create_table\(:users\)/, output)
- assert_match(/CreateUsers: migrated/, output)
- assert_match(/add_column\(:users, :email, :string\)/, output)
- assert_match(/AddEmailToUsers: migrated/, output)
-
- output = Dir.chdir(app_path){ `rake db:rollback STEP=2` }
- assert_match(/drop_table\("users"\)/, output)
- assert_match(/CreateUsers: reverted/, output)
- assert_match(/remove_column\("users", :email\)/, output)
- assert_match(/AddEmailToUsers: reverted/, output)
- end
+ require "#{rails_root}/config/environment"
+
+ # loading a specific fixture
+ errormsg = Dir.chdir(app_path) { `rake db:fixtures:load FIXTURES=products` }
+ assert $?.success?, errormsg
- def test_migration_status_when_schema_migrations_table_is_not_present
- output = Dir.chdir(app_path){ `rake db:migrate:status` }
- assert_equal "Schema migrations table does not exist yet.\n", output
+ assert_equal 2, ::AppTemplate::Application::Product.count
+ assert_equal 0, ::AppTemplate::Application::User.count
end
- def test_migration_status
- Dir.chdir(app_path) do
- `rails generate model user username:string password:string`
- `rails generate migration add_email_to_users email:string`
+ def test_scaffold_tests_pass_by_default
+ output = Dir.chdir(app_path) do
+ `rails generate scaffold user username:string password:string;
+ bundle exec rake db:migrate db:test:clone test`
end
- Dir.chdir(app_path) { `rake db:migrate`}
- output = Dir.chdir(app_path) { `rake db:migrate:status` }
-
- assert_match(/up\s+\d{14}\s+Create users/, output)
- assert_match(/up\s+\d{14}\s+Add email to users/, output)
-
- Dir.chdir(app_path) { `rake db:rollback STEP=1` }
- output = Dir.chdir(app_path) { `rake db:migrate:status` }
-
- assert_match(/up\s+\d{14}\s+Create users/, output)
- assert_match(/down\s+\d{14}\s+Add email to users/, output)
+ assert_match(/7 tests, 13 assertions, 0 failures, 0 errors/, output)
+ assert_no_match(/Errors running/, output)
end
- def test_migration_status_after_rollback_and_redo
+ def test_rake_dump_structure_should_respect_db_structure_env_variable
Dir.chdir(app_path) do
- `rails generate model user username:string password:string`
- `rails generate migration add_email_to_users email:string`
+ # ensure we have a schema_migrations table to dump
+ `bundle exec rake db:migrate db:structure:dump DB_STRUCTURE=db/my_structure.sql`
end
+ assert File.exists?(File.join(app_path, 'db', 'my_structure.sql'))
+ end
- Dir.chdir(app_path) { `rake db:migrate`}
- output = Dir.chdir(app_path) { `rake db:migrate:status` }
-
- assert_match(/up\s+\d{14}\s+Create users/, output)
- assert_match(/up\s+\d{14}\s+Add email to users/, output)
-
- Dir.chdir(app_path) { `rake db:rollback STEP=2` }
- output = Dir.chdir(app_path) { `rake db:migrate:status` }
-
- assert_match(/down\s+\d{14}\s+Create users/, output)
- assert_match(/down\s+\d{14}\s+Add email to users/, output)
+ def test_rake_dump_structure_should_be_called_twice_when_migrate_redo
+ add_to_config "config.active_record.schema_format = :sql"
- Dir.chdir(app_path) { `rake db:migrate:redo` }
- output = Dir.chdir(app_path) { `rake db:migrate:status` }
+ output = Dir.chdir(app_path) do
+ `rails g model post title:string;
+ bundle exec rake db:migrate:redo 2>&1 --trace;`
+ end
- assert_match(/up\s+\d{14}\s+Create users/, output)
- assert_match(/up\s+\d{14}\s+Add email to users/, output)
+ # expect only Invoke db:structure:dump (first_time)
+ assert_no_match(/^\*\* Invoke db:structure:dump\s+$/, output)
end
- def test_loading_specific_fixtures
+ def test_rake_dump_schema_cache
Dir.chdir(app_path) do
- `rails generate model user username:string password:string`
- `rails generate model product name:string`
- `rake db:migrate`
+ `rails generate model post title:string;
+ rails generate model product name:string;
+ bundle exec rake db:migrate db:schema:cache:dump`
end
-
- require "#{rails_root}/config/environment"
-
- # loading a specific fixture
- errormsg = Dir.chdir(app_path) { `rake db:fixtures:load FIXTURES=products` }
- assert $?.success?, errormsg
-
- assert_equal 2, ::AppTemplate::Application::Product.count
- assert_equal 0, ::AppTemplate::Application::User.count
+ assert File.exists?(File.join(app_path, 'db', 'schema_cache.dump'))
end
- def test_scaffold_tests_pass_by_default
- content = Dir.chdir(app_path) do
- `rails generate scaffold user username:string password:string`
- `bundle exec rake db:migrate db:test:clone test`
+ def test_rake_clear_schema_cache
+ Dir.chdir(app_path) do
+ `bundle exec rake db:schema:cache:dump db:schema:cache:clear`
end
-
- assert_match(/7 tests, 10 assertions, 0 failures, 0 errors/, content)
+ assert !File.exists?(File.join(app_path, 'db', 'schema_cache.dump'))
end
end
end
diff --git a/railties/test/application/route_inspect_test.rb b/railties/test/application/route_inspect_test.rb
index add8256b5d..3b8c874b5b 100644
--- a/railties/test/application/route_inspect_test.rb
+++ b/railties/test/application/route_inspect_test.rb
@@ -1,84 +1,120 @@
-require 'test/unit'
+require 'minitest/autorun'
require 'rails/application/route_inspector'
require 'action_controller'
+require 'rails/engine'
module ApplicationTests
- class RouteInspectTest < Test::Unit::TestCase
+ class RouteInspectTest < ActiveSupport::TestCase
def setup
@set = ActionDispatch::Routing::RouteSet.new
@inspector = Rails::Application::RouteInspector.new
+ app = ActiveSupport::OrderedOptions.new
+ app.config = ActiveSupport::OrderedOptions.new
+ app.config.assets = ActiveSupport::OrderedOptions.new
+ app.config.assets.prefix = '/sprockets'
+ Rails.stubs(:application).returns(app)
+ Rails.stubs(:env).returns("development")
+ end
+
+ def draw(&block)
+ @set.draw(&block)
+ @inspector.format(@set.routes)
+ end
+
+ def test_displaying_routes_for_engines
+ engine = Class.new(Rails::Engine) do
+ def self.to_s
+ "Blog::Engine"
+ end
+ end
+ engine.routes.draw do
+ get '/cart', :to => 'cart#show'
+ end
+
+ output = draw do
+ get '/custom/assets', :to => 'custom_assets#show'
+ mount engine => "/blog", :as => "blog"
+ end
+
+ expected = [
+ "custom_assets GET /custom/assets(.:format) custom_assets#show",
+ " blog /blog Blog::Engine",
+ "\nRoutes for Blog::Engine:",
+ "cart GET /cart(.:format) cart#show"
+ ]
+ assert_equal expected, output
end
def test_cart_inspect
- @set.draw do
+ output = draw do
get '/cart', :to => 'cart#show'
end
- output = @inspector.format @set.routes
assert_equal ["cart GET /cart(.:format) cart#show"], output
end
def test_inspect_shows_custom_assets
- @set.draw do
+ output = draw do
get '/custom/assets', :to => 'custom_assets#show'
end
- output = @inspector.format @set.routes
assert_equal ["custom_assets GET /custom/assets(.:format) custom_assets#show"], output
end
def test_inspect_routes_shows_resources_route
- @set.draw do
+ output = draw do
resources :articles
end
- output = @inspector.format @set.routes
expected = [
" articles GET /articles(.:format) articles#index",
" POST /articles(.:format) articles#create",
" new_article GET /articles/new(.:format) articles#new",
"edit_article GET /articles/:id/edit(.:format) articles#edit",
" article GET /articles/:id(.:format) articles#show",
+ " PATCH /articles/:id(.:format) articles#update",
" PUT /articles/:id(.:format) articles#update",
" DELETE /articles/:id(.:format) articles#destroy" ]
assert_equal expected, output
end
def test_inspect_routes_shows_root_route
- @set.draw do
+ output = draw do
root :to => 'pages#main'
end
- output = @inspector.format @set.routes
- assert_equal ["root / pages#main"], output
+ assert_equal ["root GET / pages#main"], output
+ end
+
+ def test_inspect_routes_shows_dynamic_action_route
+ output = draw do
+ get 'api/:action' => 'api'
+ end
+ assert_equal [" GET /api/:action(.:format) api#:action"], output
end
def test_inspect_routes_shows_controller_and_action_only_route
- @set.draw do
- match ':controller/:action'
+ output = draw do
+ get ':controller/:action'
end
- output = @inspector.format @set.routes
- assert_equal [" /:controller/:action(.:format) "], output
+ assert_equal [" GET /:controller/:action(.:format) :controller#:action"], output
end
def test_inspect_routes_shows_controller_and_action_route_with_constraints
- @set.draw do
- match ':controller(/:action(/:id))', :id => /\d+/
+ output = draw do
+ get ':controller(/:action(/:id))', :id => /\d+/
end
- output = @inspector.format @set.routes
- assert_equal [" /:controller(/:action(/:id))(.:format) {:id=>/\\d+/}"], output
+ assert_equal [" GET /:controller(/:action(/:id))(.:format) :controller#:action {:id=>/\\d+/}"], output
end
def test_rake_routes_shows_route_with_defaults
- @set.draw do
- match 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'}
+ output = draw do
+ get 'photos/:id' => 'photos#show', :defaults => {:format => 'jpg'}
end
- output = @inspector.format @set.routes
- assert_equal [%Q[ /photos/:id(.:format) photos#show {:format=>"jpg"}]], output
+ assert_equal [%Q[ GET /photos/:id(.:format) photos#show {:format=>"jpg"}]], output
end
def test_rake_routes_shows_route_with_constraints
- @set.draw do
- match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+ output = draw do
+ get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
end
- output = @inspector.format @set.routes
- assert_equal [" /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output
+ assert_equal [" GET /photos/:id(.:format) photos#show {:id=>/[A-Z]\\d{5}/}"], output
end
class RackApp
@@ -87,11 +123,46 @@ module ApplicationTests
end
def test_rake_routes_shows_route_with_rack_app
- @set.draw do
- match 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/
+ output = draw do
+ get 'foo/:id' => RackApp, :id => /[A-Z]\d{5}/
+ end
+ assert_equal [" GET /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output
+ end
+
+ def test_rake_routes_shows_route_with_rack_app_nested_with_dynamic_constraints
+ constraint = Class.new do
+ def to_s
+ "( my custom constraint )"
+ end
+ end
+
+ output = draw do
+ scope :constraint => constraint.new do
+ mount RackApp => '/foo'
+ end
+ end
+
+ assert_equal [" /foo #{RackApp.name} {:constraint=>( my custom constraint )}"], output
+ end
+
+ def test_rake_routes_dont_show_app_mounted_in_assets_prefix
+ output = draw do
+ get '/sprockets' => RackApp
end
- output = @inspector.format @set.routes
- assert_equal [" /foo/:id(.:format) #{RackApp.name} {:id=>/[A-Z]\\d{5}/}"], output
+ assert_no_match(/RackApp/, output.first)
+ assert_no_match(/\/sprockets/, output.first)
+ end
+
+ def test_redirect
+ output = draw do
+ get "/foo" => redirect("/foo/bar"), :constraints => { :subdomain => "admin" }
+ get "/bar" => redirect(path: "/foo/bar", status: 307)
+ get "/foobar" => redirect{ "/foo/bar" }
+ end
+
+ assert_equal " foo GET /foo(.:format) redirect(301, /foo/bar) {:subdomain=>\"admin\"}", output[0]
+ assert_equal " bar GET /bar(.:format) redirect(307, path: /foo/bar)", output[1]
+ assert_equal "foobar GET /foobar(.:format) redirect(301)", output[2]
end
end
end
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index a05e39658d..977a5fc7e8 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -2,7 +2,7 @@ require 'isolation/abstract_unit'
require 'rack/test'
module ApplicationTests
- class RoutingTest < Test::Unit::TestCase
+ class RoutingTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
include Rack::Test::Methods
@@ -53,7 +53,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
RUBY
@@ -94,7 +94,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
RUBY
@@ -126,8 +126,8 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match 'admin/foo', :to => 'admin/foo#index'
- match 'foo', :to => 'foo#index'
+ get 'admin/foo', :to => 'admin/foo#index'
+ get 'foo', :to => 'foo#index'
end
RUBY
@@ -141,13 +141,13 @@ module ApplicationTests
test "routes appending blocks" do
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller/:action'
+ get ':controller/:action'
end
RUBY
add_to_config <<-R
routes.append do
- match '/win' => lambda { |e| [200, {'Content-Type'=>'text/plain'}, ['WIN']] }
+ get '/win' => lambda { |e| [200, {'Content-Type'=>'text/plain'}, ['WIN']] }
end
R
@@ -158,7 +158,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-R
AppTemplate::Application.routes.draw do
- match 'lol' => 'hello#index'
+ get 'lol' => 'hello#index'
end
R
@@ -166,7 +166,90 @@ module ApplicationTests
assert_equal 'WIN', last_response.body
end
+ test "routes drawing from config/routes" do
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ draw :external
+ end
+ RUBY
+
+ app_file 'config/routes/external.rb', <<-RUBY
+ get ':controller/:action'
+ RUBY
+
+ controller :success, <<-RUBY
+ class SuccessController < ActionController::Base
+ def index
+ render :text => "success!"
+ end
+ end
+ RUBY
+
+ app 'development'
+ get '/success/index'
+ assert_equal 'success!', last_response.body
+ end
+
{"development" => "baz", "production" => "bar"}.each do |mode, expected|
+ test "reloads routes when external configuration is changed in #{mode}" do
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def bar
+ render :text => "bar"
+ end
+
+ def baz
+ render :text => "baz"
+ end
+ end
+ RUBY
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ draw :external
+ end
+ RUBY
+
+ app_file 'config/routes/external.rb', <<-RUBY
+ get 'foo', :to => 'foo#bar'
+ RUBY
+
+ app(mode)
+
+ get '/foo'
+ assert_equal 'bar', last_response.body
+
+ app_file 'config/routes/external.rb', <<-RUBY
+ get 'foo', :to => 'foo#baz'
+ RUBY
+
+ sleep 0.1
+
+ get '/foo'
+ assert_equal expected, last_response.body
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do
+ draw :external
+ draw :other_external
+ end
+ RUBY
+
+ app_file 'config/routes/other_external.rb', <<-RUBY
+ get 'win', :to => 'foo#baz'
+ RUBY
+
+ sleep 0.1
+
+ get '/win'
+
+ if mode == "development"
+ assert_equal expected, last_response.body
+ else
+ assert_equal 404, last_response.status
+ end
+ end
+
test "reloads routes when configuration is changed in #{mode}" do
controller :foo, <<-RUBY
class FooController < ApplicationController
@@ -182,7 +265,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match 'foo', :to => 'foo#bar'
+ get 'foo', :to => 'foo#bar'
end
RUBY
@@ -193,7 +276,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match 'foo', :to => 'foo#baz'
+ get 'foo', :to => 'foo#baz'
end
RUBY
@@ -214,7 +297,7 @@ module ApplicationTests
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match 'foo', :to => ::InitializeRackApp
+ get 'foo', :to => ::InitializeRackApp
end
RUBY
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
index 4468fa295e..e1d283a7fd 100644
--- a/railties/test/application/runner_test.rb
+++ b/railties/test/application/runner_test.rb
@@ -1,7 +1,7 @@
require 'isolation/abstract_unit'
module ApplicationTests
- class RunnerTest < Test::Unit::TestCase
+ class RunnerTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb
index 27a7959e84..f6bcaa466a 100644
--- a/railties/test/application/test_test.rb
+++ b/railties/test/application/test_test.rb
@@ -1,7 +1,7 @@
require 'isolation/abstract_unit'
module ApplicationTests
- class TestTest < Test::Unit::TestCase
+ class TestTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
@@ -24,24 +24,7 @@ module ApplicationTests
end
RUBY
- run_test 'unit/foo_test.rb'
- end
-
- # Run just in Ruby < 1.9
- if defined?(Test::Unit::Util::BacktraceFilter)
- test "adds backtrace cleaner" do
- app_file 'test/unit/backtrace_test.rb', <<-RUBY
- require 'test_helper'
-
- class FooTest < ActiveSupport::TestCase
- def test_truth
- assert Test::Unit::Util::BacktraceFilter.ancestors.include?(Rails::BacktraceFilterForTestUnit)
- end
- end
- RUBY
-
- run_test 'unit/backtrace_test.rb'
- end
+ run_test_file 'unit/foo_test.rb'
end
test "integration test" do
@@ -66,7 +49,7 @@ module ApplicationTests
end
RUBY
- run_test 'integration/posts_test.rb'
+ run_test_file 'integration/posts_test.rb'
end
test "performance test" do
@@ -91,11 +74,11 @@ module ApplicationTests
end
RUBY
- run_test 'performance/posts_test.rb'
+ run_test_file 'performance/posts_test.rb'
end
private
- def run_test(name)
+ def run_test_file(name)
result = ruby '-Itest', "#{app_path}/test/#{name}"
assert_equal 0, $?.to_i, result
end
diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb
index 2b6ec26cd0..f7e60749a7 100644
--- a/railties/test/application/url_generation_test.rb
+++ b/railties/test/application/url_generation_test.rb
@@ -1,7 +1,7 @@
require 'isolation/abstract_unit'
module ApplicationTests
- class UrlGenerationTest < Test::Unit::TestCase
+ class UrlGenerationTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def app
@@ -31,7 +31,7 @@ module ApplicationTests
end
MyApp.routes.draw do
- match "/" => "omg#index", :as => :omg
+ get "/" => "omg#index", :as => :omg
end
require 'rack/test'
diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb
index 80077378db..2dd74f8fd1 100644
--- a/railties/test/backtrace_cleaner_test.rb
+++ b/railties/test/backtrace_cleaner_test.rb
@@ -1,54 +1,24 @@
require 'abstract_unit'
require 'rails/backtrace_cleaner'
-if defined? Test::Unit::Util::BacktraceFilter
- class TestWithBacktrace
- include Test::Unit::Util::BacktraceFilter
- include Rails::BacktraceFilterForTestUnit
+class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase
+ def setup
+ @cleaner = Rails::BacktraceCleaner.new
end
- class BacktraceCleanerFilterTest < ActiveSupport::TestCase
- def setup
- @test = TestWithBacktrace.new
- @backtrace = [ './test/rails/benchmark_test.rb', './test/rails/dependencies.rb', '/opt/local/lib/ruby/kernel.rb' ]
- end
-
- test "test with backtrace should use the rails backtrace cleaner to clean" do
- Rails.stubs(:backtrace_cleaner).returns(stub(:clean))
- Rails.backtrace_cleaner.expects(:clean).with(@backtrace, nil)
- @test.send(:filter_backtrace, @backtrace)
- end
-
- test "filter backtrace should have the same arity as Test::Unit::Util::BacktraceFilter" do
- assert_nothing_raised do
- @test.send(:filter_backtrace, @backtrace, '/opt/local/lib')
- end
- end
+ test "should format installed gems correctly" do
+ @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
+ @result = @cleaner.clean(@backtrace, :all)
+ assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
end
-else
- $stderr.puts 'No BacktraceFilter for minitest'
-end
-
-if defined? Gem
- class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase
- def setup
- @cleaner = Rails::BacktraceCleaner.new
- end
- test "should format installed gems correctly" do
- @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
+ test "should format installed gems not in Gem.default_dir correctly" do
+ @target_dir = Gem.path.detect { |p| p != Gem.default_dir }
+ # skip this test if default_dir is the only directory on Gem.path
+ if @target_dir
+ @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
@result = @cleaner.clean(@backtrace, :all)
assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
end
-
- test "should format installed gems not in Gem.default_dir correctly" do
- @target_dir = Gem.path.detect { |p| p != Gem.default_dir }
- # skip this test if default_dir is the only directory on Gem.path
- if @target_dir
- @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ]
- @result = @cleaner.clean(@backtrace, :all)
- assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0]
- end
- end
end
end
diff --git a/railties/test/commands/console_test.rb b/railties/test/commands/console_test.rb
new file mode 100644
index 0000000000..9aa1d68675
--- /dev/null
+++ b/railties/test/commands/console_test.rb
@@ -0,0 +1,106 @@
+require 'abstract_unit'
+require 'rails/commands/console'
+
+class Rails::ConsoleTest < ActiveSupport::TestCase
+ class FakeConsole
+ end
+
+ def setup
+ end
+
+ def test_sandbox_option
+ console = Rails::Console.new(app, ["--sandbox"])
+ assert console.sandbox?
+ end
+
+ def test_short_version_of_sandbox_option
+ console = Rails::Console.new(app, ["-s"])
+ assert console.sandbox?
+ end
+
+ def test_debugger_option
+ console = Rails::Console.new(app, ["--debugger"])
+ assert console.debugger?
+ end
+
+ def test_no_options
+ console = Rails::Console.new(app, [])
+ assert !console.debugger?
+ assert !console.sandbox?
+ end
+
+ def test_start
+ app.expects(:sandbox=).with(nil)
+ FakeConsole.expects(:start)
+
+ start
+
+ assert_match /Loading \w+ environment \(Rails/, output
+ end
+
+ def test_start_with_debugger
+ app.expects(:sandbox=).with(nil)
+ rails_console.expects(:require_debugger).returns(nil)
+ FakeConsole.expects(:start)
+
+ start ["--debugger"]
+ end
+
+ def test_start_with_sandbox
+ app.expects(:sandbox=).with(true)
+ FakeConsole.expects(:start)
+
+ start ["--sandbox"]
+
+ assert_match /Loading \w+ environment in sandbox \(Rails/, output
+ end
+
+ def test_console_with_environment
+ app.expects(:sandbox=).with(nil)
+ FakeConsole.expects(:start)
+
+ start ["-e production"]
+
+ assert_match /production/, output
+ end
+
+ def test_console_with_rails_environment
+ app.expects(:sandbox=).with(nil)
+ FakeConsole.expects(:start)
+
+ start ["RAILS_ENV=production"]
+
+ assert_match /production/, output
+ end
+
+
+ def test_console_defaults_to_IRB
+ config = mock("config", :console => nil)
+ app = mock("app", :config => config)
+ app.expects(:load_console).returns(nil)
+
+ assert_equal IRB, Rails::Console.new(app).console
+ end
+
+ private
+
+ attr_reader :output
+
+ def rails_console
+ @rails_console ||= Rails::Console.new(app)
+ end
+
+ def start(argv = [])
+ rails_console.stubs(:arguments => argv)
+ @output = output = capture(:stdout) { rails_console.start }
+ end
+
+ def app
+ @app ||= begin
+ config = mock("config", :console => FakeConsole)
+ app = mock("app", :config => config)
+ app.expects(:load_console)
+ app
+ end
+ end
+end
diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb
new file mode 100644
index 0000000000..85a7edfacd
--- /dev/null
+++ b/railties/test/commands/dbconsole_test.rb
@@ -0,0 +1,160 @@
+require 'abstract_unit'
+require 'rails/commands/dbconsole'
+
+class Rails::DBConsoleTest < ActiveSupport::TestCase
+ def teardown
+ %w[PGUSER PGHOST PGPORT PGPASSWORD].each{|key| ENV.delete(key)}
+ end
+
+ def test_config
+ Rails::DBConsole.const_set(:APP_PATH, "erb")
+
+ app_config({})
+ capture_abort { Rails::DBConsole.config }
+ assert aborted
+ assert_match /No database is configured for the environment '\w+'/, output
+
+ app_config(test: "with_init")
+ assert_equal Rails::DBConsole.config, "with_init"
+
+ app_db_file("test:\n without_init")
+ assert_equal Rails::DBConsole.config, "without_init"
+
+ app_db_file("test:\n <%= Rails.something_app_specific %>")
+ assert_equal Rails::DBConsole.config, "with_init"
+
+ app_db_file("test:\n\ninvalid")
+ assert_equal Rails::DBConsole.config, "with_init"
+ end
+
+ def test_env
+ assert_equal Rails::DBConsole.env, "test"
+
+ Rails.stubs(:respond_to?).with(:env).returns(false)
+ assert_equal Rails::DBConsole.env, "test"
+
+ ENV['RAILS_ENV'] = nil
+ ENV['RACK_ENV'] = "rack_env"
+ assert_equal Rails::DBConsole.env, "rack_env"
+
+ ENV['RAILS_ENV'] = "rails_env"
+ assert_equal Rails::DBConsole.env, "rails_env"
+ ensure
+ ENV['RAILS_ENV'] = "test"
+ end
+
+ def test_mysql
+ dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], 'db')
+ start(adapter: 'mysql', database: 'db')
+ assert !aborted
+ end
+
+ def test_mysql_full
+ dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], '--host=locahost', '--port=1234', '--socket=socket', '--user=user', '--default-character-set=UTF-8', '-p', 'db')
+ start(adapter: 'mysql', database: 'db', host: 'locahost', port: 1234, socket: 'socket', username: 'user', password: 'qwerty', encoding: 'UTF-8')
+ assert !aborted
+ end
+
+ def test_mysql_include_password
+ dbconsole.expects(:find_cmd_and_exec).with(%w[mysql mysql5], '--user=user', '--password=qwerty', 'db')
+ start({adapter: 'mysql', database: 'db', username: 'user', password: 'qwerty'}, ['-p'])
+ assert !aborted
+ end
+
+ def test_postgresql
+ dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
+ start(adapter: 'postgresql', database: 'db')
+ assert !aborted
+ end
+
+ def test_postgresql_full
+ dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
+ start(adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3', host: 'host', port: 5432)
+ assert !aborted
+ assert_equal 'user', ENV['PGUSER']
+ assert_equal 'host', ENV['PGHOST']
+ assert_equal '5432', ENV['PGPORT']
+ assert_not_equal 'q1w2e3', ENV['PGPASSWORD']
+ end
+
+ def test_postgresql_include_password
+ dbconsole.expects(:find_cmd_and_exec).with('psql', 'db')
+ start({adapter: 'postgresql', database: 'db', username: 'user', password: 'q1w2e3'}, ['-p'])
+ assert !aborted
+ assert_equal 'user', ENV['PGUSER']
+ assert_equal 'q1w2e3', ENV['PGPASSWORD']
+ end
+
+ def test_sqlite
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite', 'db')
+ start(adapter: 'sqlite', database: 'db')
+ assert !aborted
+ end
+
+ def test_sqlite3
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', 'db')
+ start(adapter: 'sqlite3', database: 'db')
+ assert !aborted
+ end
+
+ def test_sqlite3_mode
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-html', 'db')
+ start({adapter: 'sqlite3', database: 'db'}, ['--mode', 'html'])
+ assert !aborted
+ end
+
+ def test_sqlite3_header
+ dbconsole.expects(:find_cmd_and_exec).with('sqlite3', '-header', 'db')
+ start({adapter: 'sqlite3', database: 'db'}, ['--header'])
+ assert !aborted
+ end
+
+ def test_oracle
+ dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user@db')
+ start(adapter: 'oracle', database: 'db', username: 'user', password: 'secret')
+ assert !aborted
+ end
+
+ def test_oracle_include_password
+ dbconsole.expects(:find_cmd_and_exec).with('sqlplus', 'user/secret@db')
+ start({adapter: 'oracle', database: 'db', username: 'user', password: 'secret'}, ['-p'])
+ assert !aborted
+ end
+
+ def test_unknown_command_line_client
+ start(adapter: 'unknown', database: 'db')
+ assert aborted
+ assert_match /Unknown command-line client for db/, output
+ end
+
+ private
+ attr_reader :aborted, :output
+
+ def dbconsole
+ @dbconsole ||= Rails::DBConsole.new(nil)
+ end
+
+ def start(config = {}, argv = [])
+ dbconsole.stubs(config: config.stringify_keys, arguments: argv)
+ capture_abort { dbconsole.start }
+ end
+
+ def capture_abort
+ @aborted = false
+ @output = capture(:stderr) do
+ begin
+ yield
+ rescue SystemExit
+ @aborted = true
+ end
+ end
+ end
+
+ def app_db_file(result)
+ IO.stubs(:read).with("config/database.yml").returns(result)
+ end
+
+ def app_config(result)
+ Rails.application.config.stubs(:database_configuration).returns(result.stringify_keys)
+ end
+end
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
new file mode 100644
index 0000000000..8039aec873
--- /dev/null
+++ b/railties/test/commands/server_test.rb
@@ -0,0 +1,26 @@
+require 'abstract_unit'
+require 'rails/commands/server'
+
+class Rails::ServerTest < ActiveSupport::TestCase
+
+ def test_environment_with_server_option
+ args = ["thin", "RAILS_ENV=production"]
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal 'production', options[:environment]
+ assert_equal 'thin', options[:server]
+ end
+
+ def test_environment_without_server_option
+ args = ["RAILS_ENV=production"]
+ options = Rails::Server::Options.new.parse!(args)
+ assert_equal 'production', options[:environment]
+ assert_nil options[:server]
+ end
+
+ def test_server_option_without_environment
+ args = ["thin"]
+ options = Rails::Server::Options.new.parse!(args)
+ assert_nil options[:environment]
+ assert_equal 'thin', options[:server]
+ end
+end
diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb
new file mode 100644
index 0000000000..68406dce4c
--- /dev/null
+++ b/railties/test/engine_test.rb
@@ -0,0 +1,24 @@
+require 'abstract_unit'
+
+class EngineTest < ActiveSupport::TestCase
+ it "reports routes as available only if they're actually present" do
+ engine = Class.new(Rails::Engine) do
+ def initialize(*args)
+ @routes = nil
+ super
+ end
+ end
+
+ assert !engine.routes?
+ end
+
+ it "does not add more paths to routes on each call" do
+ engine = Class.new(Rails::Engine)
+
+ engine.routes
+ length = engine.routes.draw_paths.length
+
+ engine.routes
+ assert_equal length, engine.routes.draw_paths.length
+ end
+end
diff --git a/railties/test/fixtures/lib/generators/usage_template/USAGE b/railties/test/fixtures/lib/generators/usage_template/USAGE
new file mode 100644
index 0000000000..bcd63c52e2
--- /dev/null
+++ b/railties/test/fixtures/lib/generators/usage_template/USAGE
@@ -0,0 +1 @@
+:: <%= 1 + 1 %> :: \ No newline at end of file
diff --git a/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb
new file mode 100644
index 0000000000..078b0f9412
--- /dev/null
+++ b/railties/test/fixtures/lib/generators/usage_template/usage_template_generator.rb
@@ -0,0 +1,5 @@
+require 'rails/generators'
+
+class UsageTemplateGenerator < Rails::Generators::Base
+ source_root File.expand_path("templates", File.dirname(__FILE__))
+end
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 51fa2fe16f..a8c8fcd5b7 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -9,8 +9,6 @@ class ActionsTest < Rails::Generators::TestCase
def setup
Rails.application = TestApp::Application
super
- @git_plugin_uri = 'git://github.com/technoweenie/restful-authentication.git'
- @svn_plugin_uri = 'svn://svnhub.com/technoweenie/restful-authentication/trunk'
end
def teardown
@@ -37,41 +35,6 @@ class ActionsTest < Rails::Generators::TestCase
assert_file 'lib/test_file.rb', 'heres block data'
end
- def test_plugin_with_git_option_should_run_plugin_install
- generator.expects(:run_ruby_script).once.with("script/rails plugin install #{@git_plugin_uri}", :verbose => false)
- action :plugin, 'restful-authentication', :git => @git_plugin_uri
- end
-
- def test_plugin_with_svn_option_should_run_plugin_install
- generator.expects(:run_ruby_script).once.with("script/rails plugin install #{@svn_plugin_uri}", :verbose => false)
- action :plugin, 'restful-authentication', :svn => @svn_plugin_uri
- end
-
- def test_plugin_with_git_option_and_branch_should_run_plugin_install
- generator.expects(:run_ruby_script).once.with("script/rails plugin install -b stable #{@git_plugin_uri}", :verbose => false)
- action :plugin, 'restful-authentication', :git => @git_plugin_uri, :branch => 'stable'
- end
-
- def test_plugin_with_svn_option_and_revision_should_run_plugin_install
- generator.expects(:run_ruby_script).once.with("script/rails plugin install -r 1234 #{@svn_plugin_uri}", :verbose => false)
- action :plugin, 'restful-authentication', :svn => @svn_plugin_uri, :revision => 1234
- end
-
- def test_plugin_with_git_option_and_submodule_should_use_git_scm
- generator.expects(:run).with("git submodule add #{@git_plugin_uri} vendor/plugins/rest_auth", :verbose => false)
- action :plugin, 'rest_auth', :git => @git_plugin_uri, :submodule => true
- end
-
- def test_plugin_with_git_option_and_submodule_should_use_git_scm
- generator.expects(:run).with("git submodule add -b stable #{@git_plugin_uri} vendor/plugins/rest_auth", :verbose => false)
- action :plugin, 'rest_auth', :git => @git_plugin_uri, :submodule => true, :branch => 'stable'
- end
-
- def test_plugin_with_no_options_should_skip_method
- generator.expects(:run).never
- action :plugin, 'rest_auth', {}
- end
-
def test_add_source_adds_source_to_gemfile
run_generator
action :add_source, 'http://gems.github.com'
@@ -95,11 +58,13 @@ class ActionsTest < Rails::Generators::TestCase
def test_gem_should_insert_on_separate_lines
run_generator
+ File.open('Gemfile', 'a') {|f| f.write('# Some content...') }
+
action :gem, 'rspec'
action :gem, 'rspec-rails'
- assert_file 'Gemfile', /gem "rspec"$/
- assert_file 'Gemfile', /gem "rspec-rails"$/
+ assert_file 'Gemfile', /^gem "rspec"$/
+ assert_file 'Gemfile', /^gem "rspec-rails"$/
end
def test_gem_group_should_wrap_gems_in_a_group
@@ -231,14 +196,14 @@ class ActionsTest < Rails::Generators::TestCase
def test_readme
run_generator
Rails::Generators::AppGenerator.expects(:source_root).times(2).returns(destination_root)
- assert_match(/Welcome to Rails/, action(:readme, "README"))
+ assert_match(/Welcome to Rails/, action(:readme, "README.rdoc"))
end
def test_readme_with_quiet
generator(default_arguments, :quiet => true)
run_generator
Rails::Generators::AppGenerator.expects(:source_root).times(2).returns(destination_root)
- assert_no_match(/Welcome to Rails/, action(:readme, "README"))
+ assert_no_match(/Welcome to Rails/, action(:readme, "README.rdoc"))
end
def test_log
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 133efb872f..26e912fd9e 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -1,4 +1,3 @@
-require 'abstract_unit'
require 'generators/generators_test_helper'
require 'rails/generators/rails/app/app_generator'
require 'generators/shared_generator_tests.rb'
@@ -33,7 +32,6 @@ DEFAULT_APP_FILES = %w(
test/unit
vendor
vendor/assets
- vendor/plugins
tmp/cache
tmp/cache/assets
)
@@ -55,6 +53,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "app/views/layouts/application.html.erb", /javascript_include_tag\s+"application"/
assert_file "app/assets/stylesheets/application.css"
assert_file "config/application.rb", /config\.assets\.enabled = true/
+ assert_file "public/index.html", /url\("assets\/rails.png"\);/
end
def test_invalid_application_name_raises_an_error
@@ -84,6 +83,16 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_equal false, $?.success?
end
+ def test_application_new_show_help_message_inside_existing_rails_directory
+ app_root = File.join(destination_root, 'myfirstapp')
+ run_generator [app_root]
+ output = Dir.chdir(app_root) do
+ `rails new --help`
+ end
+ assert_match(/rails new APP_PATH \[options\]/, output)
+ assert_equal true, $?.success?
+ end
+
def test_application_name_is_detected_if_it_exists_and_app_folder_renamed
app_root = File.join(destination_root, "myapp")
app_moved_root = File.join(destination_root, "myapp_moved")
@@ -123,13 +132,23 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "hats/config/environment.rb", /Hats::Application\.initialize!/
end
+ def test_gemfile_has_no_whitespace_errors
+ run_generator
+ absolute = File.expand_path("Gemfile", destination_root)
+ File.open(absolute, 'r') do |f|
+ f.each_line do |line|
+ assert_no_match %r{/^[ \t]+$/}, line
+ end
+ end
+ end
+
def test_config_database_is_added_by_default
run_generator
assert_file "config/database.yml", /sqlite3/
unless defined?(JRUBY_VERSION)
- assert_file "Gemfile", /^gem\s+["']sqlite3["']$/
+ assert_gem "sqlite3"
else
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/
+ assert_gem "activerecord-jdbcsqlite3-adapter"
end
end
@@ -137,45 +156,55 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator([destination_root, "-d", "mysql"])
assert_file "config/database.yml", /mysql/
unless defined?(JRUBY_VERSION)
- assert_file "Gemfile", /^gem\s+["']mysql2["']$/
+ assert_gem "mysql2"
+ else
+ assert_gem "activerecord-jdbcmysql-adapter"
+ end
+ end
+
+ def test_config_postgresql_database
+ run_generator([destination_root, "-d", "postgresql"])
+ assert_file "config/database.yml", /postgresql/
+ unless defined?(JRUBY_VERSION)
+ assert_gem "pg"
else
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/
+ assert_gem "activerecord-jdbcpostgresql-adapter"
end
end
def test_config_jdbcmysql_database
run_generator([destination_root, "-d", "jdbcmysql"])
assert_file "config/database.yml", /mysql/
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/
+ assert_gem "activerecord-jdbcmysql-adapter"
# TODO: When the JRuby guys merge jruby-openssl in
# jruby this will be removed
- assert_file "Gemfile", /^gem\s+["']jruby-openssl["']$/ if defined?(JRUBY_VERSION)
+ assert_gem "jruby-openssl" if defined?(JRUBY_VERSION)
end
def test_config_jdbcsqlite3_database
run_generator([destination_root, "-d", "jdbcsqlite3"])
assert_file "config/database.yml", /sqlite3/
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/
+ assert_gem "activerecord-jdbcsqlite3-adapter"
end
def test_config_jdbcpostgresql_database
run_generator([destination_root, "-d", "jdbcpostgresql"])
assert_file "config/database.yml", /postgresql/
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcpostgresql-adapter["']$/
+ assert_gem "activerecord-jdbcpostgresql-adapter"
end
def test_config_jdbc_database
run_generator([destination_root, "-d", "jdbc"])
assert_file "config/database.yml", /jdbc/
assert_file "config/database.yml", /mssql/
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbc-adapter["']$/
+ assert_gem "activerecord-jdbc-adapter"
end
def test_config_jdbc_database_when_no_option_given
if defined?(JRUBY_VERSION)
run_generator([destination_root])
assert_file "config/database.yml", /sqlite3/
- assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/
+ assert_gem "activerecord-jdbcsqlite3-adapter"
end
end
@@ -183,6 +212,8 @@ class AppGeneratorTest < Rails::Generators::TestCase
run_generator [destination_root, "--skip-active-record"]
assert_no_file "config/database.yml"
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
+ assert_file "config/application.rb", /#\s+config\.active_record\.whitelist_attributes = true/
+ assert_file "config/application.rb", /#\s+config\.active_record\.dependent_restrict_raises = false/
assert_file "test/test_helper.rb" do |helper_content|
assert_no_match(/fixtures :all/, helper_content)
end
@@ -192,12 +223,40 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_if_skip_sprockets_is_given
run_generator [destination_root, "--skip-sprockets"]
assert_file "config/application.rb" do |content|
- assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content)
+ assert_match(/#\s+require\s+["']sprockets\/rails\/railtie["']/, content)
assert_no_match(/config\.assets\.enabled = true/, content)
end
+ assert_file "Gemfile" do |content|
+ assert_no_match(/sass-rails/, content)
+ assert_no_match(/coffee-rails/, content)
+ assert_no_match(/uglifier/, content)
+ end
+ assert_file "config/environments/development.rb" do |content|
+ assert_no_match(/config\.assets\.debug = true/, content)
+ end
+ assert_file "config/environments/production.rb" do |content|
+ assert_no_match(/config\.assets\.digest = true/, content)
+ assert_no_match(/config\.assets\.compress = true/, content)
+ end
assert_file "test/performance/browsing_test.rb"
end
+ def test_inclusion_of_javascript_runtime
+ run_generator([destination_root])
+ if defined?(JRUBY_VERSION)
+ assert_gem "therubyrhino"
+ else
+ assert_file "Gemfile", /# gem\s+["']therubyracer["']+, platform: :ruby$/
+ end
+ end
+
+ def test_generator_if_skip_index_html_is_given
+ run_generator [destination_root, "--skip-index-html"]
+ assert_no_file "public/index.html"
+ assert_no_file "app/assets/images/rails.png"
+ assert_file "app/assets/images/.gitkeep"
+ end
+
def test_creation_of_a_test_directory
run_generator
assert_file 'test'
@@ -219,9 +278,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_match %r{^//= require jquery}, contents
assert_match %r{^//= require jquery_ujs}, contents
end
- assert_file 'Gemfile' do |contents|
- assert_match(/^gem 'jquery-rails'/, contents)
- end
+ assert_gem "jquery-rails"
end
def test_other_javascript_libraries
@@ -230,9 +287,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_match %r{^//= require prototype}, contents
assert_match %r{^//= require prototype_ujs}, contents
end
- assert_file 'Gemfile' do |contents|
- assert_match(/^gem 'prototype-rails'/, contents)
- end
+ assert_gem "prototype-rails"
end
def test_javascript_is_skipped_if_required
@@ -242,33 +297,9 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
end
- def test_inclusion_of_turn_gem_in_gemfile
- run_generator
- assert_file "Gemfile" do |contents|
- assert_match(/gem 'turn'/, contents) unless RUBY_VERSION < '1.9.2'
- assert_no_match(/gem 'turn'/, contents) if RUBY_VERSION < '1.9.2'
- end
- end
-
- def test_turn_gem_is_not_included_in_gemfile_if_skipping_test_unit
- run_generator [destination_root, "--skip-test-unit"]
- assert_file "Gemfile" do |contents|
- assert_no_match(/gem 'turn'/, contents) unless RUBY_VERSION < '1.9.2'
- end
- end
-
- def test_inclusion_of_ruby_debug
+ def test_inclusion_of_debugger
run_generator
- assert_file "Gemfile" do |contents|
- assert_match(/gem 'ruby-debug'/, contents) if RUBY_VERSION < '1.9'
- end
- end
-
- def test_inclusion_of_ruby_debug19_if_ruby19
- run_generator
- assert_file "Gemfile" do |contents|
- assert_match(/gem 'ruby-debug19', :require => 'ruby-debug'/, contents) unless RUBY_VERSION < '1.9'
- end
+ assert_file "Gemfile", /# gem 'debugger'/
end
def test_template_from_dir_pwd
@@ -282,7 +313,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_default_usage
- File.expects(:exist?).returns(false)
+ Rails::Generators::AppGenerator.expects(:usage_path).returns(nil)
assert_match(/Create rails files for app generator/, Rails::Generators::AppGenerator.desc)
end
@@ -309,36 +340,57 @@ class AppGeneratorTest < Rails::Generators::TestCase
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
+ assert_match(/config.session_store :cookie_store, key: '_.+_session'/, file)
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)
+ def test_generated_environments_file_for_sanitizer
+ run_generator [destination_root, "--skip-active-record"]
+ %w(development test).each do |env|
+ assert_file "config/environments/#{env}.rb" do |file|
+ assert_no_match(/config.active_record.mass_assignment_sanitizer = :strict/, file)
+ end
end
end
- def test_generated_environments_file_for_sanitizer
+ def test_generated_environments_file_for_auto_explain
run_generator [destination_root, "--skip-active-record"]
- ["config/environments/development.rb", "config/environments/test.rb"].each do |env_file|
- assert_file env_file do |file|
- assert_no_match(/config.active_record.mass_assignment_sanitizer = :strict/, file)
+ %w(development production).each do |env|
+ assert_file "config/environments/#{env}.rb" do |file|
+ assert_no_match %r(auto_explain_threshold_in_seconds), file
end
end
end
+ def test_active_record_whitelist_attributes_is_present_application_config
+ run_generator
+ assert_file "config/application.rb", /config\.active_record\.whitelist_attributes = true/
+ end
+
+ def test_active_record_dependent_restrict_raises_is_present_application_config
+ run_generator
+ assert_file "config/application.rb", /config\.active_record\.dependent_restrict_raises = false/
+ end
+
+ def test_pretend_option
+ output = run_generator [File.join(destination_root, "myapp"), "--pretend"]
+ assert_no_match(/run bundle install/, output)
+ end
+
+ def test_humans_txt_file
+ run_generator [File.join(destination_root, 'things-43')]
+ assert_file "things-43/public/humans.txt", /Name: Things43/, /Software: Ruby on Rails/
+ end
+
protected
def action(*args, &block)
silence(:stdout) { generator.send(*args, &block) }
end
+ def assert_gem(gem)
+ assert_file "Gemfile", /^gem\s+["']#{gem}["']$/
+ end
end
class CustomAppGeneratorTest < Rails::Generators::TestCase
diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb
index a85829085c..6e3fc84781 100644
--- a/railties/test/generators/generated_attribute_test.rb
+++ b/railties/test/generators/generated_attribute_test.rb
@@ -69,7 +69,7 @@ class GeneratedAttributeTest < Rails::Generators::TestCase
end
def test_default_value_for_type
- att = Rails::Generators::GeneratedAttribute.new("type", "string")
+ att = Rails::Generators::GeneratedAttribute.parse("type:string")
assert_equal("", att.default)
end
@@ -122,4 +122,9 @@ class GeneratedAttributeTest < Rails::Generators::TestCase
assert_equal :string, create_generated_attribute(nil, 'title').type
assert_equal :string, create_generated_attribute("", 'title').type
end
+
+ def test_handles_index_names_for_references
+ assert_equal "post", create_generated_attribute('string', 'post').index_name
+ assert_equal "post_id", create_generated_attribute('references', 'post').index_name
+ end
end
diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb
index 139d6b1421..c501780e7f 100644
--- a/railties/test/generators/mailer_generator_test.rb
+++ b/railties/test/generators/mailer_generator_test.rb
@@ -10,11 +10,7 @@ class MailerGeneratorTest < Rails::Generators::TestCase
run_generator
assert_file "app/mailers/notifier.rb" do |mailer|
assert_match(/class Notifier < ActionMailer::Base/, mailer)
- if RUBY_VERSION < "1.9"
- assert_match(/default :from => "from@example.com"/, mailer)
- else
- assert_match(/default from: "from@example.com"/, mailer)
- end
+ assert_match(/default from: "from@example.com"/, mailer)
end
end
@@ -77,33 +73,14 @@ class MailerGeneratorTest < Rails::Generators::TestCase
assert_file "app/mailers/notifier.rb" do |mailer|
assert_instance_method :foo, mailer do |foo|
- if RUBY_VERSION < "1.9"
- assert_match(/mail :to => "to@example.org"/, foo)
- else
- assert_match(/mail to: "to@example.org"/, foo)
- end
+ assert_match(/mail to: "to@example.org"/, foo)
assert_match(/@greeting = "Hi"/, foo)
end
assert_instance_method :bar, mailer do |bar|
- if RUBY_VERSION < "1.9"
- assert_match(/mail :to => "to@example.org"/, bar)
- else
- assert_match(/mail to: "to@example.org"/, bar)
- end
+ assert_match(/mail to: "to@example.org"/, bar)
assert_match(/@greeting = "Hi"/, bar)
end
end
end
-
- def test_force_old_style_hash
- run_generator ["notifier", "foo", "--old-style-hash"]
- assert_file "app/mailers/notifier.rb" do |mailer|
- assert_match(/default :from => "from@example.com"/, mailer)
-
- assert_instance_method :foo, mailer do |foo|
- assert_match(/mail :to => "to@example.org"/, foo)
- end
- end
- end
end
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 337257df7d..b320e40654 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -41,6 +41,24 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_remove_migration_with_indexed_attribute
+ migration = "remove_title_body_from_posts"
+ run_generator [migration, "title:string:index", "body:text"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :up, content do |up|
+ assert_match(/remove_column :posts, :title/, up)
+ assert_match(/remove_column :posts, :body/, up)
+ end
+
+ assert_method :down, content do |down|
+ assert_match(/add_column :posts, :title, :string/, down)
+ assert_match(/add_column :posts, :body, :text/, down)
+ assert_match(/add_index :posts, :title/, down)
+ end
+ end
+ end
+
def test_remove_migration_with_attributes
migration = "remove_title_body_from_posts"
run_generator [migration, "title:string", "body:text"]
@@ -58,6 +76,68 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_add_migration_with_attributes_and_indices
+ migration = "add_title_with_index_and_body_to_posts"
+ run_generator [migration, "title:string:index", "body:text", "user_id:integer:uniq"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |up|
+ assert_match(/add_column :posts, :title, :string/, up)
+ assert_match(/add_column :posts, :body, :text/, up)
+ assert_match(/add_column :posts, :user_id, :integer/, up)
+ end
+ assert_match(/add_index :posts, :title/, content)
+ assert_match(/add_index :posts, :user_id, unique: true/, content)
+ end
+ end
+
+ def test_add_migration_with_attributes_and_wrong_index_declaration
+ migration = "add_title_and_content_to_books"
+ run_generator [migration, "title:string:inex", "content:text", "user_id:integer:unik"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |up|
+ assert_match(/add_column :books, :title, :string/, up)
+ assert_match(/add_column :books, :content, :text/, up)
+ assert_match(/add_column :books, :user_id, :integer/, up)
+ end
+ assert_no_match(/add_index :books, :title/, content)
+ assert_no_match(/add_index :books, :user_id/, content)
+ end
+ end
+
+ def test_add_migration_with_attributes_without_type_and_index
+ migration = "add_title_with_index_and_body_to_posts"
+ run_generator [migration, "title:index", "body:text", "user_uuid:uniq"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |up|
+ assert_match(/add_column :posts, :title, :string/, up)
+ assert_match(/add_column :posts, :body, :text/, up)
+ assert_match(/add_column :posts, :user_uuid, :string/, up)
+ end
+ assert_match(/add_index :posts, :title/, content)
+ assert_match(/add_index :posts, :user_uuid, unique: true/, content)
+ end
+ end
+
+ def test_add_migration_with_attributes_index_declaration_and_attribute_options
+ migration = "add_title_and_content_to_books"
+ run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{1,2}:index", "discount:decimal{3.4}:uniq"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_method :change, content do |up|
+ assert_match(/add_column :books, :title, :string, limit: 40/, up)
+ assert_match(/add_column :books, :content, :string, limit: 255/, up)
+ assert_match(/add_column :books, :price, :decimal, precision: 1, scale: 2/, up)
+ assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 4/, up)
+ end
+ assert_match(/add_index :books, :title/, content)
+ assert_match(/add_index :books, :price/, content)
+ assert_match(/add_index :books, :discount, unique: true/, content)
+ end
+ end
+
def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove
migration = "create_books"
run_generator [migration, "title:string", "content:text"]
@@ -72,4 +152,8 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
end
+
+ def test_properly_identifies_usage_file
+ assert generator_class.send(:usage_path)
+ end
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 1b0cb425c6..fd3b8c8a17 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -113,6 +113,74 @@ class ModelGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_migration_with_attributes_and_with_index
+ run_generator ["product", "name:string:index", "supplier_id:integer:index", "user_id:integer:uniq", "order_id:uniq"]
+
+ assert_migration "db/migrate/create_products.rb" do |m|
+ assert_method :change, m do |up|
+ assert_match(/create_table :products/, up)
+ assert_match(/t\.string :name/, up)
+ assert_match(/t\.integer :supplier_id/, up)
+ assert_match(/t\.integer :user_id/, up)
+ assert_match(/t\.string :order_id/, up)
+
+ assert_match(/add_index :products, :name/, up)
+ assert_match(/add_index :products, :supplier_id/, up)
+ assert_match(/add_index :products, :user_id, unique: true/, up)
+ assert_match(/add_index :products, :order_id, unique: true/, up)
+ end
+ end
+ end
+
+ def test_migration_with_attributes_and_with_wrong_index_declaration
+ run_generator ["product", "name:string", "supplier_id:integer:inex", "user_id:integer:unqu"]
+
+ assert_migration "db/migrate/create_products.rb" do |m|
+ assert_method :change, m do |up|
+ assert_match(/create_table :products/, up)
+ assert_match(/t\.string :name/, up)
+ assert_match(/t\.integer :supplier_id/, up)
+ assert_match(/t\.integer :user_id/, up)
+
+ assert_no_match(/add_index :products, :name/, up)
+ assert_no_match(/add_index :products, :supplier_id/, up)
+ assert_no_match(/add_index :products, :user_id/, up)
+ end
+ end
+ end
+
+ def test_migration_with_missing_attribute_type_and_with_index
+ run_generator ["product", "name:index", "supplier_id:integer:index", "year:integer"]
+
+ assert_migration "db/migrate/create_products.rb" do |m|
+ assert_method :change, m do |up|
+ assert_match(/create_table :products/, up)
+ assert_match(/t\.string :name/, up)
+ assert_match(/t\.integer :supplier_id/, up)
+
+ assert_match(/add_index :products, :name/, up)
+ assert_match(/add_index :products, :supplier_id/, up)
+ assert_no_match(/add_index :products, :year/, up)
+ end
+ end
+ end
+
+ def test_add_migration_with_attributes_index_declaration_and_attribute_options
+ run_generator ["product", "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{5,2}:uniq"]
+
+ assert_migration "db/migrate/create_products.rb" do |content|
+ assert_method :change, content do |up|
+ assert_match(/create_table :products/, up)
+ assert_match(/t.string :title, limit: 40/, up)
+ assert_match(/t.string :content, limit: 255/, up)
+ assert_match(/t.decimal :price, precision: 5, scale: 2/, up)
+ end
+ assert_match(/add_index :products, :title/, content)
+ assert_match(/add_index :products, :price/, content)
+ assert_match(/add_index :products, :discount, unique: true/, content)
+ end
+ end
+
def test_migration_without_timestamps
ActiveRecord::Base.timestamped_migrations = false
run_generator ["account"]
@@ -215,7 +283,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/create_accounts.rb" do |m|
assert_method :change, m do |up|
- assert_match(/add_index/, up)
+ assert_match(/index: true/, up)
end
end
end
@@ -225,7 +293,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/create_accounts.rb" do |m|
assert_method :change, m do |up|
- assert_match(/add_index/, up)
+ assert_match(/index: true/, up)
end
end
end
@@ -235,7 +303,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/create_accounts.rb" do |m|
assert_method :change, m do |up|
- assert_no_match(/add_index/, up)
+ assert_no_match(/index: true/, up)
end
end
end
@@ -245,8 +313,18 @@ class ModelGeneratorTest < Rails::Generators::TestCase
assert_migration "db/migrate/create_accounts.rb" do |m|
assert_method :change, m do |up|
- assert_no_match(/add_index/, up)
+ assert_no_match(/index: true/, up)
end
end
end
+
+ def test_attr_accessible_added_with_non_reference_attributes
+ run_generator
+ assert_file 'app/models/account.rb', /attr_accessible :age, :name/
+ end
+
+ def test_attr_accessible_added_with_comments_when_no_attributes_present
+ run_generator ["Account"]
+ assert_file 'app/models/account.rb', /# attr_accessible :title, :body/
+ end
end
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index f23701e99e..2bc2c33a72 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -108,6 +108,15 @@ class NamedBaseTest < Rails::Generators::TestCase
assert_name g, 'sheep_index', :index_helper
end
+ def test_hide_namespace
+ g = generator ['Hidden']
+ g.class.stubs(:namespace).returns('hidden')
+
+ assert !Rails::Generators.hidden_namespaces.include?('hidden')
+ g.class.hide!
+ assert Rails::Generators.hidden_namespaces.include?('hidden')
+ end
+
protected
def assert_name(generator, value, method)
diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb
index dd1e4bdac1..c495258343 100644
--- a/railties/test/generators/namespaced_generators_test.rb
+++ b/railties/test/generators/namespaced_generators_test.rb
@@ -56,11 +56,20 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase
run_generator
assert_file "config/routes.rb", /get "account\/foo"/, /get "account\/bar"/
end
-#
+
def test_invokes_default_template_engine_even_with_no_action
run_generator ["account"]
assert_file "app/views/test_app/account"
end
+
+ def test_namespaced_controller_dont_indent_blank_lines
+ run_generator
+ assert_file "app/controllers/test_app/account_controller.rb" do |content|
+ content.split("\n").each do |line|
+ assert_no_match(/^\s+$/, line, "Don't indent blank lines")
+ end
+ end
+ end
end
class NamespacedModelGeneratorTest < NamespacedGeneratorTestCase
@@ -155,11 +164,7 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase
assert_file "app/mailers/test_app/notifier.rb" do |mailer|
assert_match(/module TestApp/, mailer)
assert_match(/class Notifier < ActionMailer::Base/, mailer)
- if RUBY_VERSION < "1.9"
- assert_match(/default :from => "from@example.com"/, mailer)
- else
- assert_match(/default from: "from@example.com"/, mailer)
- end
+ assert_match(/default from: "from@example.com"/, mailer)
end
end
diff --git a/railties/test/generators/orm_test.rb b/railties/test/generators/orm_test.rb
new file mode 100644
index 0000000000..9dd3d3e0ec
--- /dev/null
+++ b/railties/test/generators/orm_test.rb
@@ -0,0 +1,38 @@
+require "generators/generators_test_helper"
+require "rails/generators/rails/scaffold_controller/scaffold_controller_generator"
+
+# Mock out two ORMs
+module ORMWithGenerators
+ module Generators
+ class ActiveModel
+ def initialize(name)
+ end
+ end
+ end
+end
+
+module ORMWithoutGenerators
+ # No generators
+end
+
+class OrmTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+ tests Rails::Generators::ScaffoldControllerGenerator
+
+ def test_orm_class_returns_custom_generator_if_supported_custom_orm_set
+ g = generator ["Foo"], :orm => "ORMWithGenerators"
+ assert_equal ORMWithGenerators::Generators::ActiveModel, g.send(:orm_class)
+ end
+
+ def test_orm_class_returns_rails_generator_if_unsupported_custom_orm_set
+ g = generator ["Foo"], :orm => "ORMWithoutGenerators"
+ assert_equal Rails::Generators::ActiveModel, g.send(:orm_class)
+ end
+
+ def test_orm_instance_returns_orm_class_instance_with_name
+ g = generator ["Foo"]
+ orm_instance = g.send(:orm_instance)
+ assert g.send(:orm_class) === orm_instance
+ assert_equal "foo", orm_instance.name
+ end
+end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 9183945619..51374e5f3f 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -1,4 +1,3 @@
-require 'abstract_unit'
require 'generators/generators_test_helper'
require 'rails/generators/rails/plugin_new/plugin_new_generator'
require 'generators/shared_generator_tests.rb'
@@ -28,13 +27,20 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
include SharedGeneratorTests
def test_invalid_plugin_name_raises_an_error
- content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] }
- assert_equal "Invalid plugin name 43-things. Please give a name which does not start with numbers.\n", content
+ content = capture(:stderr){ run_generator [File.join(destination_root, "things-43")] }
+ assert_equal "Invalid plugin name things-43. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content
+
+ content = capture(:stderr){ run_generator [File.join(destination_root, "things4.3")] }
+ assert_equal "Invalid plugin name things4.3. Please give a name which use only alphabetic or numeric or \"_\" characters.\n", content
+
+ content = capture(:stderr){ run_generator [File.join(destination_root, "43things")] }
+ assert_equal "Invalid plugin name 43things. Please give a name which does not start with numbers.\n", content
end
- def test_invalid_plugin_name_is_fixed
- run_generator [File.join(destination_root, "things-43")]
- assert_file "things-43/lib/things-43.rb", /module Things43/
+ def test_camelcase_plugin_name_underscores_filenames
+ run_generator [File.join(destination_root, "CamelCasedName")]
+ assert_no_file "CamelCasedName/lib/CamelCasedName.rb"
+ assert_file "CamelCasedName/lib/camel_cased_name.rb", /module CamelCasedName/
end
def test_generating_without_options
@@ -205,7 +211,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_creating_gemspec
run_generator
assert_file "bukkits.gemspec", /s.name\s+= "bukkits"/
- assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*"\]/
+ assert_file "bukkits.gemspec", /s.files = Dir\["\{app,config,db,lib\}\/\*\*\/\*", "MIT-LICENSE", "Rakefile", "README\.rdoc"\]/
assert_file "bukkits.gemspec", /s.test_files = Dir\["test\/\*\*\/\*"\]/
assert_file "bukkits.gemspec", /s.version\s+ = Bukkits::VERSION/
end
@@ -230,6 +236,13 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_file "test/dummy"
end
+ def test_creating_dummy_application_with_different_name
+ run_generator [destination_root, "--dummy_path", "spec/fake"]
+ assert_file "spec/fake"
+ assert_file "spec/fake/config/application.rb"
+ assert_no_file "test/dummy"
+ end
+
def test_creating_dummy_without_tests_but_with_dummy_path
run_generator [destination_root, "--dummy_path", "spec/dummy", "--skip-test-unit"]
assert_file "spec/dummy"
@@ -237,6 +250,14 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_file "test"
end
+ def test_ensure_that_gitignore_can_be_generated_from_a_template_for_dummy_path
+ FileUtils.cd(Rails.root)
+ run_generator([destination_root, "--dummy_path", "spec/dummy" "--skip-test-unit"])
+ assert_file ".gitignore" do |contents|
+ assert_match(/spec\/dummy/, contents)
+ end
+ end
+
def test_skipping_test_unit
run_generator [destination_root, "--skip-test-unit"]
assert_no_file "test"
@@ -250,6 +271,37 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_no_file "bukkits.gemspec"
end
+ def test_creating_plugin_in_app_directory_adds_gemfile_entry
+ # simulate application existance
+ gemfile_path = "#{Rails.root}/Gemfile"
+ Object.const_set('APP_PATH', Rails.root)
+ FileUtils.touch gemfile_path
+
+ run_generator [destination_root]
+
+ assert_file gemfile_path, /gem 'bukkits', :path => 'tmp\/bukkits'/
+ ensure
+ Object.send(:remove_const, 'APP_PATH')
+ FileUtils.rm gemfile_path
+ end
+
+ def test_skipping_gemfile_entry
+ # simulate application existance
+ gemfile_path = "#{Rails.root}/Gemfile"
+ Object.const_set('APP_PATH', Rails.root)
+ FileUtils.touch gemfile_path
+
+ run_generator [destination_root, "--skip-gemfile-entry"]
+
+ assert_file gemfile_path do |contents|
+ assert_no_match(/gem 'bukkits', :path => 'tmp\/bukkits'/, contents)
+ end
+ ensure
+ Object.send(:remove_const, 'APP_PATH')
+ FileUtils.rm gemfile_path
+ end
+
+
protected
def action(*args, &block)
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 65b30b9fbd..1eea50b0d9 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -75,6 +75,19 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
assert_file "test/functional/users_controller_test.rb" do |content|
assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
assert_match(/test "should get index"/, content)
+ assert_match(/post :create, user: \{ age: @user.age, name: @user.name \}/, content)
+ assert_match(/put :update, id: @user, user: \{ age: @user.age, name: @user.name \}/, content)
+ end
+ end
+
+ def test_functional_tests_without_attributes
+ run_generator ["User"]
+
+ assert_file "test/functional/users_controller_test.rb" do |content|
+ assert_match(/class UsersControllerTest < ActionController::TestCase/, content)
+ assert_match(/test "should get index"/, content)
+ assert_match(/post :create, user: \{ \}/, content)
+ assert_match(/put :update, id: @user, user: \{ \}/, content)
end
end
@@ -126,18 +139,7 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
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)
+ 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 2db8090621..9b456c64ef 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -14,10 +14,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/
assert_file "test/unit/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/
assert_file "test/fixtures/product_lines.yml"
- assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product/
- assert_migration "db/migrate/create_product_lines.rb", /add_index :product_lines, :product_id/
- assert_migration "db/migrate/create_product_lines.rb", /references :user/
- assert_migration "db/migrate/create_product_lines.rb", /add_index :product_lines, :user_id/
+ assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product, index: true/
+ assert_migration "db/migrate/create_product_lines.rb", /references :user, index: true/
# Route
assert_file "config/routes.rb" do |route|
@@ -62,8 +60,11 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
end
end
- assert_file "test/functional/product_lines_controller_test.rb",
- /class ProductLinesControllerTest < ActionController::TestCase/
+ assert_file "test/functional/product_lines_controller_test.rb" do |test|
+ assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test)
+ assert_match(/post :create, product_line: \{ title: @product_line.title \}/, test)
+ assert_match(/put :update, id: @product_line, product_line: \{ title: @product_line.title \}/, test)
+ end
# Views
%w(
@@ -85,6 +86,17 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_file "app/assets/stylesheets/product_lines.css"
end
+ def test_functional_tests_without_attributes
+ run_generator ["product_line"]
+
+ assert_file "test/functional/product_lines_controller_test.rb" do |content|
+ assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, content)
+ assert_match(/test "should get index"/, content)
+ assert_match(/post :create, product_line: \{ \}/, content)
+ assert_match(/put :update, id: @product_line, product_line: \{ \}/, content)
+ end
+ end
+
def test_scaffold_on_revoke
run_generator
run_generator ["product_line"], :behavior => :revoke
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 1534f0d828..e78e67725d 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -91,21 +91,6 @@ module SharedGeneratorTests
assert_match(/It works!/, capture(:stdout) { generator.invoke_all })
end
- def test_template_raises_an_error_with_invalid_path
- content = capture(:stderr){ run_generator([destination_root, "-m", "non/existant/path"]) }
- assert_match(/The template \[.*\] could not be loaded/, content)
- assert_match(/non\/existant\/path/, content)
- end
-
- def test_template_is_executed_when_supplied
- path = "http://gist.github.com/103208.txt"
- template = %{ say "It works!" }
- template.instance_eval "def read; self; end" # Make the string respond to read
-
- generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match(/It works!/, capture(:stdout) { generator.invoke_all })
- end
-
def test_template_is_executed_when_supplied_an_https_path
path = "https://gist.github.com/103208.txt"
template = %{ say "It works!" }
@@ -119,13 +104,13 @@ module SharedGeneratorTests
generator([destination_root], :dev => true).expects(:bundle_command).with('install').once
quietly { generator.invoke_all }
rails_path = File.expand_path('../../..', Rails.root)
- assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/
+ assert_file 'Gemfile', /^gem\s+["']rails["'],\s+path:\s+["']#{Regexp.escape(rails_path)}["']$/
end
def test_edge_option
generator([destination_root], :edge => true).expects(:bundle_command).with('install').once
quietly { generator.invoke_all }
- assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$}
+ assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+github:\s+["']#{Regexp.escape("rails/rails")}["']$}
end
def test_skip_gemfile
diff --git a/railties/test/generators/task_generator_test.rb b/railties/test/generators/task_generator_test.rb
new file mode 100644
index 0000000000..f810a21d1e
--- /dev/null
+++ b/railties/test/generators/task_generator_test.rb
@@ -0,0 +1,12 @@
+require 'generators/generators_test_helper'
+require 'rails/generators/rails/task/task_generator'
+
+class TaskGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+ arguments %w(feeds foo bar)
+
+ def test_controller_skeleton_is_created
+ run_generator
+ assert_file "lib/tasks/feeds.rake", /namespace :feeds/
+ end
+end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 56329f3183..60e7e57a91 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -201,4 +201,16 @@ class GeneratorsTest < Rails::Generators::TestCase
mspec = Rails::Generators.find_by_namespace :fixjour
assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "fixjour"))
end
+
+ def test_usage_with_embedded_ruby
+ require File.expand_path("fixtures/lib/generators/usage_template/usage_template_generator", File.dirname(__FILE__))
+ output = capture(:stdout) { Rails::Generators.invoke :usage_template, ['--help'] }
+ assert_match /:: 2 ::/, output
+ end
+
+ def test_hide_namespace
+ assert !Rails::Generators.hidden_namespaces.include?("special:namespace")
+ Rails::Generators.hide_namespace("special:namespace")
+ assert Rails::Generators.hidden_namespaces.include?("special:namespace")
+ end
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index 06b658e7bd..03be81e59f 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -8,17 +8,17 @@
# Rails booted up.
require 'fileutils'
-require 'test/unit'
-require 'rubygems'
+require 'bundler/setup'
+require 'minitest/autorun'
+require 'active_support/test_case'
# TODO: Remove setting this magic constant
RAILS_FRAMEWORK_ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../..")
# These files do not require any others and are needed
# to run the tests
-require "#{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/testing/isolation"
-require "#{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/testing/declarative"
-require "#{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/core_ext/kernel/reporting"
+require "active_support/testing/isolation"
+require "active_support/core_ext/kernel/reporting"
module TestHelpers
module Paths
@@ -111,11 +111,16 @@ module TestHelpers
routes = File.read("#{app_path}/config/routes.rb")
if routes =~ /(\n\s*end\s*)\Z/
File.open("#{app_path}/config/routes.rb", 'w') do |f|
- f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)'\n" + $1
+ f.puts $` + "\nmatch ':controller(/:action(/:id))(.:format)', :via => :all\n" + $1
end
end
- add_to_config 'config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"; config.session_store :cookie_store, :key => "_myapp_session"; config.active_support.deprecation = :log'
+ add_to_config <<-RUBY
+ config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
+ config.session_store :cookie_store, :key => "_myapp_session"
+ config.active_support.deprecation = :log
+ config.action_controller.allow_forgery_protection = false
+ RUBY
end
def teardown_app
@@ -137,7 +142,7 @@ module TestHelpers
app.initialize!
app.routes.draw do
- match "/" => "omg#index"
+ get "/" => "omg#index"
end
require 'rack/test'
@@ -155,7 +160,7 @@ module TestHelpers
app_file 'config/routes.rb', <<-RUBY
AppTemplate::Application.routes.draw do
- match ':controller(/:action)'
+ get ':controller(/:action)'
end
RUBY
end
@@ -178,20 +183,6 @@ module TestHelpers
end
end
- def plugin(name, string = "")
- dir = "#{app_path}/vendor/plugins/#{name}"
- FileUtils.mkdir_p(dir)
-
- File.open("#{dir}/init.rb", 'w') do |f|
- f.puts "::#{name.upcase} = 'loaded'"
- f.puts string
- end
-
- Bukkit.new(dir).tap do |bukkit|
- yield bukkit if block_given?
- end
- end
-
def engine(name)
dir = "#{app_path}/random/#{name}"
FileUtils.mkdir_p(dir)
@@ -257,10 +248,11 @@ module TestHelpers
def use_frameworks(arr)
to_remove = [:actionmailer,
- :activemodel,
- :activerecord,
- :activeresource] - arr
- remove_from_config "config.active_record.identity_map = true" if to_remove.include? :activerecord
+ :activerecord] - arr
+ if to_remove.include? :activerecord
+ remove_from_config "config.active_record.whitelist_attributes = true"
+ remove_from_config "config.active_record.dependent_restrict_raises = false"
+ end
$:.reject! {|path| path =~ %r'/(#{to_remove.join('|')})/' }
end
@@ -270,11 +262,10 @@ module TestHelpers
end
end
-class Test::Unit::TestCase
+class ActiveSupport::TestCase
include TestHelpers::Paths
include TestHelpers::Rack
include TestHelpers::Generation
- extend ActiveSupport::Testing::Declarative
end
# Create a scope and build a fixture rails app
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index c0f3887263..aa04cad033 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -22,13 +22,14 @@ class PathsTest < ActiveSupport::TestCase
root = Rails::Paths::Root.new(nil)
root.add "app"
root.path = "/root"
- assert_equal ["app"], root["app"]
+ assert_equal ["app"], root["app"].to_ary
assert_equal ["/root/app"], root["app"].to_a
end
test "creating a root level path" do
@root.add "app"
assert_equal ["/foo/bar/app"], @root["app"].to_a
+ assert_equal [Pathname.new("/foo/bar/app")], @root["app"].paths
end
test "creating a root level path with options" do
@@ -91,10 +92,6 @@ class PathsTest < ActiveSupport::TestCase
assert_equal ["/foo/bar/app2", "/foo/bar/app"], @root["app"].to_a
end
- test "the root can only have one physical path" do
- assert_raise(RuntimeError) { Rails::Paths::Root.new(["/fiz", "/biz"]) }
- end
-
test "it is possible to add a path that should be autoloaded only once" do
@root.add "app", :with => "/app"
@root["app"].autoload_once!
@@ -195,6 +192,7 @@ class PathsTest < ActiveSupport::TestCase
@root["app"] = "/app"
@root["app"].glob = "*.rb"
assert_equal "*.rb", @root["app"].glob
+ assert_equal [Pathname.new("/app")], @root["app"].paths
end
test "it should be possible to override a path's default glob without assignment" do
diff --git a/railties/test/queueing/test_queue_test.rb b/railties/test/queueing/test_queue_test.rb
new file mode 100644
index 0000000000..78c6c617fe
--- /dev/null
+++ b/railties/test/queueing/test_queue_test.rb
@@ -0,0 +1,67 @@
+require 'abstract_unit'
+require 'rails/queueing'
+
+class TestQueueTest < ActiveSupport::TestCase
+ class Job
+ def initialize(&block)
+ @block = block
+ end
+
+ def run
+ @block.call if @block
+ end
+ end
+
+ def setup
+ @queue = Rails::Queueing::TestQueue.new
+ end
+
+ def test_drain_raises
+ @queue.push Job.new { raise }
+ assert_raises(RuntimeError) { @queue.drain }
+ end
+
+ def test_jobs
+ @queue.push 1
+ @queue.push 2
+ assert_equal [1,2], @queue.jobs
+ end
+
+ def test_contents
+ assert @queue.empty?
+ job = Job.new
+ @queue.push job
+ refute @queue.empty?
+ assert_equal job, @queue.pop
+ end
+
+ def test_order
+ processed = []
+
+ job1 = Job.new { processed << 1 }
+ job2 = Job.new { processed << 2 }
+
+ @queue.push job1
+ @queue.push job2
+ @queue.drain
+
+ assert_equal [1,2], processed
+ end
+
+ def test_drain
+ t = nil
+ ran = false
+
+ job = Job.new do
+ ran = true
+ t = Thread.current
+ end
+
+ @queue.push job
+ @queue.drain
+
+ assert @queue.empty?
+ assert ran, "The job runs synchronously when the queue is drained"
+ assert_not_equal t, Thread.current
+ end
+end
diff --git a/railties/test/queueing/threaded_consumer_test.rb b/railties/test/queueing/threaded_consumer_test.rb
new file mode 100644
index 0000000000..c34a966d6e
--- /dev/null
+++ b/railties/test/queueing/threaded_consumer_test.rb
@@ -0,0 +1,100 @@
+require 'abstract_unit'
+require 'rails/queueing'
+
+class TestThreadConsumer < ActiveSupport::TestCase
+ class Job
+ attr_reader :id
+ def initialize(id, &block)
+ @id = id
+ @block = block
+ end
+
+ def run
+ @block.call if @block
+ end
+ end
+
+ def setup
+ @queue = Rails::Queueing::Queue.new
+ @consumer = Rails::Queueing::ThreadedConsumer.start(@queue)
+ end
+
+ def teardown
+ @queue.push nil
+ end
+
+ test "the jobs are executed" do
+ ran = false
+
+ job = Job.new(1) do
+ ran = true
+ end
+
+ @queue.push job
+ sleep 0.1
+ assert_equal true, ran
+ end
+
+ test "the jobs are not executed synchronously" do
+ ran = false
+
+ job = Job.new(1) do
+ sleep 0.1
+ ran = true
+ end
+
+ @queue.push job
+ assert_equal false, ran
+ end
+
+ test "shutting down the queue synchronously drains the jobs" do
+ ran = false
+
+ job = Job.new(1) do
+ sleep 0.1
+ ran = true
+ end
+
+ @queue.push job
+ assert_equal false, ran
+
+ @consumer.shutdown
+
+ assert_equal true, ran
+ end
+
+ test "log job that raises an exception" do
+ require "active_support/log_subscriber/test_helper"
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ Rails.logger = logger
+
+ job = Job.new(1) do
+ raise "RuntimeError: Error!"
+ end
+
+ @queue.push job
+ sleep 0.1
+
+ assert_equal 1, logger.logged(:error).size
+ assert_match(/Job Error: RuntimeError: Error!/, logger.logged(:error).last)
+ end
+
+ test "test overriding exception handling" do
+ @consumer.shutdown
+ @consumer = Class.new(Rails::Queueing::ThreadedConsumer) do
+ attr_reader :last_error
+ def handle_exception(e)
+ @last_error = e.message
+ end
+ end.start(@queue)
+
+ job = Job.new(1) do
+ raise "RuntimeError: Error!"
+ end
+
+ @queue.push job
+ sleep 0.1
+
+ assert_equal "RuntimeError: Error!", @consumer.last_error
+ end
+end
diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb
index 8a9363fb80..f7a30a16d2 100644
--- a/railties/test/rails_info_controller_test.rb
+++ b/railties/test/rails_info_controller_test.rb
@@ -11,7 +11,7 @@ class InfoControllerTest < ActionController::TestCase
def setup
Rails.application.routes.draw do
- match '/rails/info/properties' => "rails/info#properties"
+ get '/rails/info/properties' => "rails/info#properties"
end
@request.stubs(:local? => true)
@controller.stubs(:consider_all_requests_local? => false)
diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb
index 1da66062aa..b9fb071d23 100644
--- a/railties/test/rails_info_test.rb
+++ b/railties/test/rails_info_test.rb
@@ -43,7 +43,7 @@ class InfoTest < ActiveSupport::TestCase
def test_frameworks_exist
Rails::Info.frameworks.each do |framework|
- dir = File.dirname(__FILE__) + "/../../" + framework.gsub('_', '')
+ dir = File.dirname(__FILE__) + "/../../" + framework.delete('_')
assert File.directory?(dir), "#{framework.classify} does not exist"
end
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index f8b84e49c9..7a047ef93a 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -1,13 +1,11 @@
require "isolation/abstract_unit"
-require "railties/shared_tests"
require "stringio"
require "rack/test"
module RailtiesTest
- class EngineTest < Test::Unit::TestCase
+ class EngineTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
- include SharedTests
include Rack::Test::Methods
def setup
@@ -29,6 +27,395 @@ module RailtiesTest
teardown_app
end
+ def boot_rails
+ super
+ require "#{app_path}/config/environment"
+ end
+
+ test "serving sprocket's assets" do
+ @plugin.write "app/assets/javascripts/engine.js.erb", "<%= :alert %>();"
+
+ boot_rails
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ get "/assets/engine.js"
+ assert_match "alert()", last_response.body
+ end
+
+ test "rake environment can be called in the engine" do
+ boot_rails
+
+ @plugin.write "Rakefile", <<-RUBY
+ APP_RAKEFILE = '#{app_path}/Rakefile'
+ load 'rails/tasks/engine.rake'
+ task :foo => :environment do
+ puts "Task ran"
+ end
+ RUBY
+
+ Dir.chdir(@plugin.path) do
+ output = `bundle exec rake foo`
+ assert_match "Task ran", output
+ end
+ end
+
+ test "copying migrations" do
+ @plugin.write "db/migrate/1_create_users.rb", <<-RUBY
+ class CreateUsers < ActiveRecord::Migration
+ end
+ RUBY
+
+ @plugin.write "db/migrate/2_add_last_name_to_users.rb", <<-RUBY
+ class AddLastNameToUsers < ActiveRecord::Migration
+ end
+ RUBY
+
+ @plugin.write "db/migrate/3_create_sessions.rb", <<-RUBY
+ class CreateSessions < ActiveRecord::Migration
+ end
+ RUBY
+
+ app_file "db/migrate/1_create_sessions.rb", <<-RUBY
+ class CreateSessions < ActiveRecord::Migration
+ def up
+ end
+ end
+ RUBY
+
+ add_to_config "ActiveRecord::Base.timestamped_migrations = false"
+
+ boot_rails
+
+ Dir.chdir(app_path) do
+ output = `bundle exec rake bukkits:install:migrations`
+
+ assert File.exists?("#{app_path}/db/migrate/2_create_users.bukkits.rb")
+ assert File.exists?("#{app_path}/db/migrate/3_add_last_name_to_users.bukkits.rb")
+ assert_match(/Copied migration 2_create_users.bukkits.rb from bukkits/, output)
+ assert_match(/Copied migration 3_add_last_name_to_users.bukkits.rb from bukkits/, output)
+ assert_match(/NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output)
+ assert_equal 3, Dir["#{app_path}/db/migrate/*.rb"].length
+
+ output = `bundle exec rake railties:install:migrations`.split("\n")
+
+ assert_no_match(/2_create_users/, output.join("\n"))
+
+ bukkits_migration_order = output.index(output.detect{|o| /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/ =~ o })
+ assert_not_nil bukkits_migration_order, "Expected migration to be skipped"
+
+ migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length
+ output = `bundle exec rake railties:install:migrations`
+
+ assert_equal migrations_count, Dir["#{app_path}/db/migrate/*.rb"].length
+ end
+ end
+
+ test "mountable engine should copy migrations within engine_path" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ class Engine < ::Rails::Engine
+ isolate_namespace Bukkits
+ end
+ end
+ RUBY
+
+ @plugin.write "db/migrate/0_add_first_name_to_users.rb", <<-RUBY
+ class AddFirstNameToUsers < ActiveRecord::Migration
+ end
+ RUBY
+
+ @plugin.write "Rakefile", <<-RUBY
+ APP_RAKEFILE = '#{app_path}/Rakefile'
+ load 'rails/tasks/engine.rake'
+ RUBY
+
+ add_to_config "ActiveRecord::Base.timestamped_migrations = false"
+
+ boot_rails
+
+ Dir.chdir(@plugin.path) do
+ output = `bundle exec rake app:bukkits:install:migrations`
+ assert File.exists?("#{app_path}/db/migrate/0_add_first_name_to_users.bukkits.rb")
+ assert_match(/Copied migration 0_add_first_name_to_users.bukkits.rb from bukkits/, output)
+ assert_equal 1, Dir["#{app_path}/db/migrate/*.rb"].length
+ end
+ end
+
+ test "no rake task without migrations" do
+ boot_rails
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+ Rails.application.load_tasks
+ assert !Rake::Task.task_defined?('bukkits:install:migrations')
+ end
+
+ test "puts its lib directory on load path" do
+ boot_rails
+ require "another"
+ assert_equal "Another", Another.name
+ end
+
+ test "puts its models directory on autoload path" do
+ @plugin.write "app/models/my_bukkit.rb", "class MyBukkit ; end"
+ boot_rails
+ assert_nothing_raised { MyBukkit }
+ end
+
+ test "puts its controllers directory on autoload path" do
+ @plugin.write "app/controllers/bukkit_controller.rb", "class BukkitController ; end"
+ boot_rails
+ assert_nothing_raised { BukkitController }
+ end
+
+ test "adds its views to view paths" do
+ @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
+ class BukkitController < ActionController::Base
+ def index
+ end
+ end
+ RUBY
+
+ @plugin.write "app/views/bukkit/index.html.erb", "Hello bukkits"
+
+ boot_rails
+
+ require "action_controller"
+ require "rack/mock"
+ response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/"))
+ assert_equal "Hello bukkits\n", response[2].body
+ end
+
+ test "adds its views to view paths with lower proriority than app ones" do
+ @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
+ class BukkitController < ActionController::Base
+ def index
+ end
+ end
+ RUBY
+
+ @plugin.write "app/views/bukkit/index.html.erb", "Hello bukkits"
+ app_file "app/views/bukkit/index.html.erb", "Hi bukkits"
+
+ boot_rails
+
+ require "action_controller"
+ require "rack/mock"
+ response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/"))
+ assert_equal "Hi bukkits\n", response[2].body
+ end
+
+ test "adds helpers to controller views" do
+ @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
+ class BukkitController < ActionController::Base
+ def index
+ end
+ end
+ RUBY
+
+ @plugin.write "app/helpers/bukkit_helper.rb", <<-RUBY
+ module BukkitHelper
+ def bukkits
+ "bukkits"
+ end
+ end
+ RUBY
+
+ @plugin.write "app/views/bukkit/index.html.erb", "Hello <%= bukkits %>"
+
+ boot_rails
+
+ require "rack/mock"
+ response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/"))
+ assert_equal "Hello bukkits\n", response[2].body
+ end
+
+ test "autoload any path under app" do
+ @plugin.write "app/anything/foo.rb", <<-RUBY
+ module Foo; end
+ RUBY
+ boot_rails
+ assert Foo
+ end
+
+ test "routes are added to router" do
+ @plugin.write "config/routes.rb", <<-RUBY
+ class Sprokkit
+ def self.call(env)
+ [200, {'Content-Type' => 'text/html'}, ["I am a Sprokkit"]]
+ end
+ end
+
+ Rails.application.routes.draw do
+ get "/sprokkit", :to => Sprokkit
+ end
+ RUBY
+
+ boot_rails
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ get "/sprokkit"
+ assert_equal "I am a Sprokkit", last_response.body
+ end
+
+ test "routes in engines have lower priority than application ones" do
+ controller "foo", <<-RUBY
+ class FooController < ActionController::Base
+ def index
+ render :text => "foo"
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ get 'foo', :to => 'foo#index'
+ end
+ RUBY
+
+ @plugin.write "app/controllers/bar_controller.rb", <<-RUBY
+ class BarController < ActionController::Base
+ def index
+ render :text => "bar"
+ end
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get 'foo', :to => 'bar#index'
+ get 'bar', :to => 'bar#index'
+ end
+ RUBY
+
+ boot_rails
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ get '/foo'
+ assert_equal 'foo', last_response.body
+
+ get '/bar'
+ assert_equal 'bar', last_response.body
+ end
+
+ test "rake tasks lib tasks are loaded" do
+ $executed = false
+ @plugin.write "lib/tasks/foo.rake", <<-RUBY
+ task :foo do
+ $executed = true
+ end
+ RUBY
+
+ boot_rails
+ require 'rake'
+ require 'rdoc/task'
+ require 'rake/testtask'
+ Rails.application.load_tasks
+ Rake::Task[:foo].invoke
+ assert $executed
+ end
+
+ test "i18n files have lower priority than application ones" do
+ add_to_config <<-RUBY
+ config.i18n.load_path << "#{app_path}/app/locales/en.yml"
+ RUBY
+
+ app_file 'app/locales/en.yml', <<-YAML
+en:
+ bar: "1"
+YAML
+
+ app_file 'config/locales/en.yml', <<-YAML
+en:
+ foo: "2"
+ bar: "2"
+YAML
+
+ @plugin.write 'config/locales/en.yml', <<-YAML
+en:
+ foo: "3"
+YAML
+
+ boot_rails
+
+ expected_locales = %W(
+ #{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/locale/en.yml
+ #{RAILS_FRAMEWORK_ROOT}/activemodel/lib/active_model/locale/en.yml
+ #{RAILS_FRAMEWORK_ROOT}/activerecord/lib/active_record/locale/en.yml
+ #{RAILS_FRAMEWORK_ROOT}/actionpack/lib/action_view/locale/en.yml
+ #{@plugin.path}/config/locales/en.yml
+ #{app_path}/config/locales/en.yml
+ #{app_path}/app/locales/en.yml
+ ).map { |path| File.expand_path(path) }
+
+ actual_locales = I18n.load_path.map { |path|
+ File.expand_path(path)
+ } & expected_locales # remove locales external to Rails
+
+ assert_equal expected_locales, actual_locales
+
+ assert_equal "2", I18n.t(:foo)
+ assert_equal "1", I18n.t(:bar)
+ end
+
+ test "namespaced controllers with namespaced routes" do
+ @plugin.write "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ namespace :admin do
+ namespace :foo do
+ get "bar", :to => "bar#index"
+ end
+ end
+ end
+ RUBY
+
+ @plugin.write "app/controllers/admin/foo/bar_controller.rb", <<-RUBY
+ class Admin::Foo::BarController < ApplicationController
+ def index
+ render :text => "Rendered from namespace"
+ end
+ end
+ RUBY
+
+ boot_rails
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ get "/admin/foo/bar"
+ assert_equal 200, last_response.status
+ assert_equal "Rendered from namespace", last_response.body
+ end
+
+ test "initializers" do
+ $plugin_initializer = false
+ @plugin.write "config/initializers/foo.rb", <<-RUBY
+ $plugin_initializer = true
+ RUBY
+
+ boot_rails
+ assert $plugin_initializer
+ end
+
+ test "midleware referenced in configuration" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ class Bukkits
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ @app.call(env)
+ end
+ end
+ RUBY
+
+ add_to_config "config.middleware.use \"Bukkits\""
+ boot_rails
+ end
+
test "Rails::Engine itself does not respond to config" do
boot_rails
assert !Rails::Engine.respond_to?(:config)
@@ -60,7 +447,6 @@ module RailtiesTest
assert index < initializers.index { |i| i.name == :build_middleware_stack }
end
-
class Upcaser
def initialize(app)
@app = app
@@ -135,7 +521,7 @@ module RailtiesTest
@plugin.write "config/routes.rb", <<-RUBY
Bukkits::Engine.routes.draw do
- match "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, ['foo']] }
+ get "/foo" => lambda { |env| [200, {'Content-Type' => 'text/html'}, ['foo']] }
end
RUBY
@@ -151,63 +537,31 @@ module RailtiesTest
assert_equal "foo", last_response.body
end
- test "engine can load its own plugins" do
+ test "it loads its environments file" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
class Engine < ::Rails::Engine
+ config.paths["config/environments"].push "config/environments/additional.rb"
end
end
RUBY
- @plugin.write "vendor/plugins/yaffle/init.rb", <<-RUBY
- config.yaffle_loaded = true
- RUBY
-
- boot_rails
-
- assert Bukkits::Engine.config.yaffle_loaded
- end
-
- test "engine does not load plugins that already exists in application" do
- @plugin.write "lib/bukkits.rb", <<-RUBY
- module Bukkits
- class Engine < ::Rails::Engine
- end
- end
- RUBY
-
- @plugin.write "vendor/plugins/yaffle/init.rb", <<-RUBY
- config.engine_yaffle_loaded = true
- RUBY
-
- app_file "vendor/plugins/yaffle/init.rb", <<-RUBY
- config.app_yaffle_loaded = true
- RUBY
-
- warnings = capture(:stderr) { boot_rails }
-
- assert !warnings.empty?
- assert !Bukkits::Engine.config.respond_to?(:engine_yaffle_loaded)
- assert Rails.application.config.app_yaffle_loaded
- end
-
- test "it loads its environment file" do
- @plugin.write "lib/bukkits.rb", <<-RUBY
- module Bukkits
- class Engine < ::Rails::Engine
- end
+ @plugin.write "config/environments/development.rb", <<-RUBY
+ Bukkits::Engine.configure do
+ config.environment_loaded = true
end
RUBY
- @plugin.write "config/environments/development.rb", <<-RUBY
+ @plugin.write "config/environments/additional.rb", <<-RUBY
Bukkits::Engine.configure do
- config.environment_loaded = true
+ config.additional_environment_loaded = true
end
RUBY
boot_rails
assert Bukkits::Engine.config.environment_loaded
+ assert Bukkits::Engine.config.additional_environment_loaded
end
test "it passes router in env" do
@@ -254,18 +608,18 @@ module RailtiesTest
app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
- match "/bar" => "bar#index", :as => "bar"
+ get "/bar" => "bar#index", :as => "bar"
mount Bukkits::Engine => "/bukkits", :as => "bukkits"
end
RUBY
@plugin.write "config/routes.rb", <<-RUBY
Bukkits::Engine.routes.draw do
- match "/foo" => "foo#index", :as => "foo"
- match "/foo/show" => "foo#show"
- match "/from_app" => "foo#from_app"
- match "/routes_helpers_in_view" => "foo#routes_helpers_in_view"
- match "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace"
+ get "/foo" => "foo#index", :as => "foo"
+ get "/foo/show" => "foo#show"
+ get "/from_app" => "foo#from_app"
+ get "/routes_helpers_in_view" => "foo#routes_helpers_in_view"
+ get "/polymorphic_path_without_namespace" => "foo#polymorphic_path_without_namespace"
resources :posts
end
RUBY
@@ -323,7 +677,7 @@ module RailtiesTest
assert_equal "bukkits_", Bukkits.table_name_prefix
assert_equal "bukkits", Bukkits::Engine.engine_name
- assert_equal Bukkits._railtie, Bukkits::Engine
+ assert_equal Bukkits.railtie_namespace, Bukkits::Engine
assert ::Bukkits::MyMailer.method_defined?(:foo_path)
assert !::Bukkits::MyMailer.method_defined?(:bar_path)
@@ -422,7 +776,7 @@ module RailtiesTest
@plugin.write "config/routes.rb", <<-RUBY
Bukkits::Awesome::Engine.routes.draw do
- match "/foo" => "foo#index"
+ get "/foo" => "foo#index"
end
RUBY
@@ -455,13 +809,19 @@ module RailtiesTest
Rails.application.load_seed
assert Rails.application.config.app_seeds_loaded
- assert_raise(NoMethodError) do Bukkits::Engine.config.bukkits_seeds_loaded end
+ assert_raise(NoMethodError) { Bukkits::Engine.config.bukkits_seeds_loaded }
Bukkits::Engine.load_seed
assert Bukkits::Engine.config.bukkits_seeds_loaded
end
- test "using namespace more than once on one module should not overwrite _railtie method" do
+ test "skips nonexistent seed data" do
+ FileUtils.rm "#{app_path}/db/seeds.rb"
+ boot_rails
+ assert_nil Rails.application.load_seed
+ end
+
+ test "using namespace more than once on one module should not overwrite railtie_namespace method" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module AppTemplate
class Engine < ::Rails::Engine
@@ -478,7 +838,7 @@ module RailtiesTest
boot_rails
- assert_equal AppTemplate._railtie, AppTemplate::Engine
+ assert_equal AppTemplate.railtie_namespace, AppTemplate::Engine
end
test "properly reload routes" do
@@ -510,8 +870,6 @@ module RailtiesTest
boot_rails
- require "#{rails_root}/config/environment"
-
get("/foo")
assert_equal "foo", last_response.body
@@ -545,7 +903,6 @@ module RailtiesTest
RUBY
boot_rails
- require "#{rails_root}/config/environment"
app_generators = Rails.application.config.generators.options[:rails]
assert_equal :mongoid , app_generators[:orm]
@@ -568,7 +925,6 @@ module RailtiesTest
RUBY
boot_rails
- require "#{rails_root}/config/environment"
generators = Bukkits::Engine.config.generators.options[:rails]
assert_equal :active_record, generators[:orm]
@@ -592,7 +948,6 @@ module RailtiesTest
RUBY
boot_rails
- require "#{rails_root}/config/environment"
assert_equal "foo", Bukkits.table_name_prefix
end
@@ -606,7 +961,6 @@ module RailtiesTest
RUBY
boot_rails
- require "#{rails_root}/config/environment"
assert_equal Bukkits::Engine.instance, Rails::Engine.find(@plugin.path)
@@ -654,13 +1008,189 @@ module RailtiesTest
add_to_config("config.action_dispatch.show_exceptions = false")
boot_rails
- require "#{rails_root}/config/environment"
methods = Bukkits::Engine.helpers.public_instance_methods.collect(&:to_s).sort
expected = ["bar", "baz"]
assert_equal expected, methods
end
+ test "setting priority for engines with config.railties_order" do
+ @blog = engine "blog" do |plugin|
+ plugin.write "lib/blog.rb", <<-RUBY
+ module Blog
+ class Engine < ::Rails::Engine
+ end
+ end
+ RUBY
+ end
+
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ class Engine < ::Rails::Engine
+ isolate_namespace Bukkits
+ end
+ end
+ RUBY
+
+ controller "main", <<-RUBY
+ class MainController < ActionController::Base
+ def foo
+ render :inline => '<%= render :partial => "shared/foo" %>'
+ end
+
+ def bar
+ render :inline => '<%= render :partial => "shared/bar" %>'
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get "/foo" => "main#foo"
+ get "/bar" => "main#bar"
+ end
+ RUBY
+
+ @plugin.write "app/views/shared/_foo.html.erb", <<-RUBY
+ Bukkit's foo partial
+ RUBY
+
+ app_file "app/views/shared/_foo.html.erb", <<-RUBY
+ App's foo partial
+ RUBY
+
+ @blog.write "app/views/shared/_bar.html.erb", <<-RUBY
+ Blog's bar partial
+ RUBY
+
+ app_file "app/views/shared/_bar.html.erb", <<-RUBY
+ App's bar partial
+ RUBY
+
+ @plugin.write "app/assets/javascripts/foo.js", <<-RUBY
+ // Bukkit's foo js
+ RUBY
+
+ app_file "app/assets/javascripts/foo.js", <<-RUBY
+ // App's foo js
+ RUBY
+
+ @blog.write "app/assets/javascripts/bar.js", <<-RUBY
+ // Blog's bar js
+ RUBY
+
+ app_file "app/assets/javascripts/bar.js", <<-RUBY
+ // App's bar js
+ RUBY
+
+ add_to_config("config.railties_order = [:all, :main_app, Blog::Engine]")
+
+ boot_rails
+
+ get("/foo")
+ assert_equal "Bukkit's foo partial", last_response.body.strip
+
+ get("/bar")
+ assert_equal "App's bar partial", last_response.body.strip
+
+ get("/assets/foo.js")
+ assert_equal "// Bukkit's foo js\n;", last_response.body.strip
+
+ get("/assets/bar.js")
+ assert_equal "// App's bar js\n;", last_response.body.strip
+ end
+
+ test "railties_order adds :all with lowest priority if not given" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ class Engine < ::Rails::Engine
+ end
+ end
+ RUBY
+
+ controller "main", <<-RUBY
+ class MainController < ActionController::Base
+ def foo
+ render :inline => '<%= render :partial => "shared/foo" %>'
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get "/foo" => "main#foo"
+ end
+ RUBY
+
+ @plugin.write "app/views/shared/_foo.html.erb", <<-RUBY
+ Bukkit's foo partial
+ RUBY
+
+ app_file "app/views/shared/_foo.html.erb", <<-RUBY
+ App's foo partial
+ RUBY
+
+ add_to_config("config.railties_order = [Bukkits::Engine]")
+
+ boot_rails
+
+ get("/foo")
+ assert_equal "Bukkit's foo partial", last_response.body.strip
+ end
+
+ test "engine can be properly mounted at root" do
+ add_to_config("config.action_dispatch.show_exceptions = false")
+ add_to_config("config.serve_static_assets = false")
+
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ class Engine < ::Rails::Engine
+ isolate_namespace ::Bukkits
+ end
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Bukkits::Engine.routes.draw do
+ root "foo#index"
+ end
+ RUBY
+
+ @plugin.write "app/controllers/bukkits/foo_controller.rb", <<-RUBY
+ module Bukkits
+ class FooController < ActionController::Base
+ def index
+ text = <<-TEXT
+ script_name: \#{request.script_name}
+ fullpath: \#{request.fullpath}
+ path: \#{request.path}
+ TEXT
+ render :text => text
+ end
+ end
+ end
+ RUBY
+
+
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ mount Bukkits::Engine => "/"
+ end
+ RUBY
+
+ boot_rails
+
+ expected = <<-TEXT
+ script_name:
+ fullpath: /
+ path: /
+ TEXT
+
+ get("/")
+ assert_equal expected.split("\n").map(&:strip),
+ last_response.body.split("\n").map(&:strip)
+ end
+
private
def app
Rails.application
diff --git a/railties/test/railties/generators_test.rb b/railties/test/railties/generators_test.rb
index f8540d69d9..6ebbabc0ff 100644
--- a/railties/test/railties/generators_test.rb
+++ b/railties/test/railties/generators_test.rb
@@ -46,10 +46,6 @@ module RailtiesTests
gem 'rails', :path => '#{RAILS_FRAMEWORK_ROOT}'
gem 'sqlite3'
-
- if RUBY_VERSION < '1.9'
- gem "ruby-debug", ">= 0.10.3"
- end
GEMFILE
end
end
diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb
index 0491fc2174..4c0fdee556 100644
--- a/railties/test/railties/mounted_engine_test.rb
+++ b/railties/test/railties/mounted_engine_test.rb
@@ -1,7 +1,7 @@
require 'isolation/abstract_unit'
module ApplicationTests
- class ApplicationRoutingTest < Test::Unit::TestCase
+ class ApplicationRoutingTest < ActiveSupport::TestCase
require 'rack/test'
include Rack::Test::Methods
include ActiveSupport::Testing::Isolation
@@ -18,13 +18,13 @@ module ApplicationTests
AppTemplate::Application.routes.draw do
mount Weblog::Engine, :at => '/', :as => 'weblog'
resources :posts
- match "/engine_route" => "application_generating#engine_route"
- match "/engine_route_in_view" => "application_generating#engine_route_in_view"
- match "/weblog_engine_route" => "application_generating#weblog_engine_route"
- match "/weblog_engine_route_in_view" => "application_generating#weblog_engine_route_in_view"
- match "/url_for_engine_route" => "application_generating#url_for_engine_route"
- match "/polymorphic_route" => "application_generating#polymorphic_route"
- match "/application_polymorphic_path" => "application_generating#application_polymorphic_path"
+ get "/engine_route" => "application_generating#engine_route"
+ get "/engine_route_in_view" => "application_generating#engine_route_in_view"
+ get "/weblog_engine_route" => "application_generating#weblog_engine_route"
+ get "/weblog_engine_route_in_view" => "application_generating#weblog_engine_route_in_view"
+ get "/url_for_engine_route" => "application_generating#url_for_engine_route"
+ get "/polymorphic_route" => "application_generating#polymorphic_route"
+ get "/application_polymorphic_path" => "application_generating#application_polymorphic_path"
scope "/:user", :user => "anonymous" do
mount Blog::Engine => "/blog"
end
@@ -42,7 +42,7 @@ module ApplicationTests
@simple_plugin.write "config/routes.rb", <<-RUBY
Weblog::Engine.routes.draw do
- match '/weblog' => "weblogs#index", :as => 'weblogs'
+ get '/weblog' => "weblogs#index", :as => 'weblogs'
end
RUBY
@@ -86,9 +86,9 @@ module ApplicationTests
@plugin.write "config/routes.rb", <<-RUBY
Blog::Engine.routes.draw do
resources :posts
- match '/generate_application_route', :to => 'posts#generate_application_route'
- match '/application_route_in_view', :to => 'posts#application_route_in_view'
- match '/engine_polymorphic_path', :to => 'posts#engine_polymorphic_path'
+ get '/generate_application_route', :to => 'posts#generate_application_route'
+ get '/application_route_in_view', :to => 'posts#application_route_in_view'
+ get '/engine_polymorphic_path', :to => 'posts#engine_polymorphic_path'
end
RUBY
diff --git a/railties/test/railties/plugin_ordering_test.rb b/railties/test/railties/plugin_ordering_test.rb
deleted file mode 100644
index 1cfaf557e9..0000000000
--- a/railties/test/railties/plugin_ordering_test.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-require "isolation/abstract_unit"
-
-module RailtiesTest
- class PluginOrderingTest < Test::Unit::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- $arr = []
- plugin "a_plugin", "$arr << :a"
- plugin "b_plugin", "$arr << :b"
- plugin "c_plugin", "$arr << :c"
- end
-
- def teardown
- teardown_app
- end
-
- def boot_rails
- super
- require "#{app_path}/config/environment"
- end
-
- test "plugins are loaded alphabetically by default" do
- boot_rails
- assert_equal [:a, :b, :c], $arr
- end
-
- test "if specified, only those plugins are loaded" do
- add_to_config "config.plugins = [:b_plugin]"
- boot_rails
- assert_equal [:b], $arr
- end
-
- test "the plugins are initialized in the order they are specified" do
- add_to_config "config.plugins = [:b_plugin, :a_plugin]"
- boot_rails
- assert_equal [:b, :a], $arr
- end
-
- test "if :all is specified, the remaining plugins are loaded in alphabetical order" do
- add_to_config "config.plugins = [:c_plugin, :all]"
- boot_rails
- assert_equal [:c, :a, :b], $arr
- end
-
- test "if :all is at the beginning, it represents the plugins not otherwise specified" do
- add_to_config "config.plugins = [:all, :b_plugin]"
- boot_rails
- assert_equal [:a, :c, :b], $arr
- end
-
- test "plugin order array is strings" do
- add_to_config "config.plugins = %w( c_plugin all )"
- boot_rails
- assert_equal [:c, :a, :b], $arr
- end
-
- test "can require lib file from a different plugin" do
- plugin "foo", "require 'bar'" do |plugin|
- plugin.write "lib/foo.rb", "$foo = true"
- end
-
- plugin "bar", "require 'foo'" do |plugin|
- plugin.write "lib/bar.rb", "$bar = true"
- end
-
- add_to_config "config.plugins = [:foo, :bar]"
-
- boot_rails
-
- assert $foo
- assert $bar
- end
- end
-end
diff --git a/railties/test/railties/plugin_test.rb b/railties/test/railties/plugin_test.rb
deleted file mode 100644
index f307d53cf7..0000000000
--- a/railties/test/railties/plugin_test.rb
+++ /dev/null
@@ -1,123 +0,0 @@
-require "isolation/abstract_unit"
-require "railties/shared_tests"
-
-module RailtiesTest
- class PluginTest < Test::Unit::TestCase
- include ActiveSupport::Testing::Isolation
- include SharedTests
-
- def setup
- build_app
-
- @plugin = plugin "bukkits", "::LEVEL = config.log_level" do |plugin|
- plugin.write "lib/bukkits.rb", "class Bukkits; end"
- plugin.write "lib/another.rb", "class Another; end"
- end
- end
-
- def teardown
- teardown_app
- end
-
- test "Rails::Plugin itself does not respond to config" do
- boot_rails
- assert !Rails::Plugin.respond_to?(:config)
- end
-
- test "cannot inherit from Rails::Plugin" do
- boot_rails
- assert_raise RuntimeError do
- class Foo < Rails::Plugin; end
- end
- end
-
- test "plugin can load the file with the same name in lib" do
- boot_rails
- require "bukkits"
- assert_equal "Bukkits", Bukkits.name
- end
-
- test "plugin gets added to dependency list" do
- boot_rails
- assert_equal "Another", Another.name
- end
-
- test "plugin constants get reloaded if config.reload_plugins is set to true" do
- add_to_config <<-RUBY
- config.reload_plugins = true
- RUBY
-
- boot_rails
-
- assert_equal "Another", Another.name
- ActiveSupport::Dependencies.clear
- @plugin.delete("lib/another.rb")
- assert_raises(NameError) { Another }
- end
-
- test "plugin constants are not reloaded by default" do
- boot_rails
- assert_equal "Another", Another.name
- ActiveSupport::Dependencies.clear
- @plugin.delete("lib/another.rb")
- assert_nothing_raised { Another }
- end
-
- test "it loads the plugin's init.rb file" do
- boot_rails
- assert_equal "loaded", BUKKITS
- end
-
- test "the init.rb file has access to the config object" do
- boot_rails
- assert_equal :debug, LEVEL
- end
-
- test "plugin_init_is_run_before_application_ones" do
- plugin "foo", "$foo = true" do |plugin|
- plugin.write "lib/foo.rb", "module Foo; end"
- end
-
- app_file 'config/initializers/foo.rb', <<-RUBY
- raise "no $foo" unless $foo
- raise "no Foo" unless Foo
- RUBY
-
- boot_rails
- assert $foo
- end
-
- test "plugin should work without init.rb" do
- @plugin.delete("init.rb")
-
- boot_rails
-
- require "bukkits"
- assert_nothing_raised { Bukkits }
- end
-
- test "plugin cannot declare an engine for it" do
- @plugin.write "lib/bukkits.rb", <<-RUBY
- class Bukkits
- class Engine < Rails::Engine
- end
- end
- RUBY
-
- @plugin.write "init.rb", <<-RUBY
- require "bukkits"
- RUBY
-
- rescued = false
-
- begin
- boot_rails
- rescue Exception => e
- rescued = true
- assert_equal '"bukkits" is a Railtie/Engine and cannot be installed as a plugin', e.message
- end
-
- assert rescued, "Expected boot rails to fail"
- end
- end
-end
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index 55f85c7202..cd495320b5 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -1,7 +1,7 @@
require "isolation/abstract_unit"
module RailtiesTest
- class RailtieTest < Test::Unit::TestCase
+ class RailtieTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def setup
diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb
deleted file mode 100644
index 21fde49ff7..0000000000
--- a/railties/test/railties/shared_tests.rb
+++ /dev/null
@@ -1,361 +0,0 @@
-module RailtiesTest
- # Holds tests shared between plugin and engines
- module SharedTests
- def boot_rails
- super
- require "#{app_path}/config/environment"
- end
-
- def app
- @app ||= Rails.application
- end
-
- def test_serving_sprockets_assets
- @plugin.write "app/assets/javascripts/engine.js.erb", "<%= :alert %>();"
-
- boot_rails
- require 'rack/test'
- extend Rack::Test::Methods
-
- get "/assets/engine.js"
- assert_match "alert()", last_response.body
- end
-
- def test_copying_migrations
- @plugin.write "db/migrate/1_create_users.rb", <<-RUBY
- class CreateUsers < ActiveRecord::Migration
- end
- RUBY
-
- @plugin.write "db/migrate/2_add_last_name_to_users.rb", <<-RUBY
- class AddLastNameToUsers < ActiveRecord::Migration
- end
- RUBY
-
- @plugin.write "db/migrate/3_create_sessions.rb", <<-RUBY
- class CreateSessions < ActiveRecord::Migration
- end
- RUBY
-
- app_file "db/migrate/1_create_sessions.rb", <<-RUBY
- class CreateSessions < ActiveRecord::Migration
- end
- RUBY
-
- yaffle = plugin "acts_as_yaffle", "::LEVEL = config.log_level" do |plugin|
- plugin.write "lib/acts_as_yaffle.rb", "class ActsAsYaffle; end"
- end
-
- yaffle.write "db/migrate/1_create_yaffles.rb", <<-RUBY
- class CreateYaffles < ActiveRecord::Migration
- end
- RUBY
-
- add_to_config "ActiveRecord::Base.timestamped_migrations = false"
-
- boot_rails
- railties = Rails.application.railties.all.map(&:railtie_name)
-
- Dir.chdir(app_path) do
- output = `bundle exec rake bukkits:install:migrations`
-
- assert File.exists?("#{app_path}/db/migrate/2_create_users.rb")
- assert File.exists?("#{app_path}/db/migrate/3_add_last_name_to_users.rb")
- assert_match(/Copied migration 2_create_users.rb from bukkits/, output)
- assert_match(/Copied migration 3_add_last_name_to_users.rb from bukkits/, output)
- assert_match(/NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/, output)
- assert_equal 3, Dir["#{app_path}/db/migrate/*.rb"].length
-
- output = `bundle exec rake railties:install:migrations`.split("\n")
-
- assert File.exists?("#{app_path}/db/migrate/4_create_yaffles.rb")
- assert_no_match(/2_create_users/, output.join("\n"))
-
- yaffle_migration_order = output.index(output.detect{|o| /Copied migration 4_create_yaffles.rb from acts_as_yaffle/ =~ o })
- bukkits_migration_order = output.index(output.detect{|o| /NOTE: Migration 3_create_sessions.rb from bukkits has been skipped/ =~ o })
- assert_not_nil yaffle_migration_order, "Expected migration to be copied"
- assert_not_nil bukkits_migration_order, "Expected migration to be skipped"
- assert_equal(railties.index('acts_as_yaffle') > railties.index('bukkits'), yaffle_migration_order > bukkits_migration_order)
-
- migrations_count = Dir["#{app_path}/db/migrate/*.rb"].length
- output = `bundle exec rake railties:install:migrations`
-
- assert_equal migrations_count, Dir["#{app_path}/db/migrate/*.rb"].length
- end
- end
-
- def test_no_rake_task_without_migrations
- boot_rails
- require 'rake'
- require 'rdoc/task'
- require 'rake/testtask'
- Rails.application.load_tasks
- assert !Rake::Task.task_defined?('bukkits:install:migrations')
- end
-
- def test_puts_its_lib_directory_on_load_path
- boot_rails
- require "another"
- assert_equal "Another", Another.name
- end
-
- def test_puts_its_models_directory_on_autoload_path
- @plugin.write "app/models/my_bukkit.rb", "class MyBukkit ; end"
- boot_rails
- assert_nothing_raised { MyBukkit }
- end
-
- def test_puts_its_controllers_directory_on_autoload_path
- @plugin.write "app/controllers/bukkit_controller.rb", "class BukkitController ; end"
- boot_rails
- assert_nothing_raised { BukkitController }
- end
-
- def test_adds_its_views_to_view_paths
- @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
- class BukkitController < ActionController::Base
- def index
- end
- end
- RUBY
-
- @plugin.write "app/views/bukkit/index.html.erb", "Hello bukkits"
-
- boot_rails
-
- require "action_controller"
- require "rack/mock"
- response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/"))
- assert_equal "Hello bukkits\n", response[2].body
- end
-
- def test_adds_its_views_to_view_paths_with_lower_proriority_than_app_ones
- @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
- class BukkitController < ActionController::Base
- def index
- end
- end
- RUBY
-
- @plugin.write "app/views/bukkit/index.html.erb", "Hello bukkits"
- app_file "app/views/bukkit/index.html.erb", "Hi bukkits"
-
- boot_rails
-
- require "action_controller"
- require "rack/mock"
- response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/"))
- assert_equal "Hi bukkits\n", response[2].body
- end
-
- def test_adds_helpers_to_controller_views
- @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
- class BukkitController < ActionController::Base
- def index
- end
- end
- RUBY
-
- @plugin.write "app/helpers/bukkit_helper.rb", <<-RUBY
- module BukkitHelper
- def bukkits
- "bukkits"
- end
- end
- RUBY
-
- @plugin.write "app/views/bukkit/index.html.erb", "Hello <%= bukkits %>"
-
- boot_rails
-
- require "rack/mock"
- response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/"))
- assert_equal "Hello bukkits\n", response[2].body
- end
-
- def test_autoload_any_path_under_app
- @plugin.write "app/anything/foo.rb", <<-RUBY
- module Foo; end
- RUBY
- boot_rails
- assert Foo
- end
-
- def test_routes_are_added_to_router
- @plugin.write "config/routes.rb", <<-RUBY
- class Sprokkit
- def self.call(env)
- [200, {'Content-Type' => 'text/html'}, ["I am a Sprokkit"]]
- end
- end
-
- Rails.application.routes.draw do
- match "/sprokkit", :to => Sprokkit
- end
- RUBY
-
- boot_rails
- require 'rack/test'
- extend Rack::Test::Methods
-
- get "/sprokkit"
- assert_equal "I am a Sprokkit", last_response.body
- end
-
- def test_routes_in_plugins_have_lower_priority_than_application_ones
- controller "foo", <<-RUBY
- class FooController < ActionController::Base
- def index
- render :text => "foo"
- end
- end
- RUBY
-
- app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
- match 'foo', :to => 'foo#index'
- end
- RUBY
-
- @plugin.write "app/controllers/bar_controller.rb", <<-RUBY
- class BarController < ActionController::Base
- def index
- render :text => "bar"
- end
- end
- RUBY
-
- @plugin.write "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- match 'foo', :to => 'bar#index'
- match 'bar', :to => 'bar#index'
- end
- RUBY
-
- boot_rails
- require 'rack/test'
- extend Rack::Test::Methods
-
- get '/foo'
- assert_equal 'foo', last_response.body
-
- get '/bar'
- assert_equal 'bar', last_response.body
- end
-
- def test_rake_tasks_lib_tasks_are_loaded
- $executed = false
- @plugin.write "lib/tasks/foo.rake", <<-RUBY
- task :foo do
- $executed = true
- end
- RUBY
-
- boot_rails
- require 'rake'
- require 'rdoc/task'
- require 'rake/testtask'
- Rails.application.load_tasks
- Rake::Task[:foo].invoke
- assert $executed
- end
-
- def test_i18n_files_have_lower_priority_than_application_ones
- add_to_config <<-RUBY
- config.i18n.load_path << "#{app_path}/app/locales/en.yml"
- RUBY
-
- app_file 'app/locales/en.yml', <<-YAML
-en:
- bar: "1"
-YAML
-
- app_file 'config/locales/en.yml', <<-YAML
-en:
- foo: "2"
- bar: "2"
-YAML
-
- @plugin.write 'config/locales/en.yml', <<-YAML
-en:
- foo: "3"
-YAML
-
- boot_rails
-
- expected_locales = %W(
- #{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/locale/en.yml
- #{RAILS_FRAMEWORK_ROOT}/activemodel/lib/active_model/locale/en.yml
- #{RAILS_FRAMEWORK_ROOT}/activerecord/lib/active_record/locale/en.yml
- #{RAILS_FRAMEWORK_ROOT}/actionpack/lib/action_view/locale/en.yml
- #{@plugin.path}/config/locales/en.yml
- #{app_path}/config/locales/en.yml
- #{app_path}/app/locales/en.yml
- ).map { |path| File.expand_path(path) }
-
- actual_locales = I18n.load_path.map { |path|
- File.expand_path(path)
- } & expected_locales # remove locales external to Rails
-
- assert_equal expected_locales, actual_locales
-
- assert_equal "2", I18n.t(:foo)
- assert_equal "1", I18n.t(:bar)
- end
-
- def test_namespaced_controllers_with_namespaced_routes
- @plugin.write "config/routes.rb", <<-RUBY
- Rails.application.routes.draw do
- namespace :admin do
- namespace :foo do
- match "bar", :to => "bar#index"
- end
- end
- end
- RUBY
-
- @plugin.write "app/controllers/admin/foo/bar_controller.rb", <<-RUBY
- class Admin::Foo::BarController < ApplicationController
- def index
- render :text => "Rendered from namespace"
- end
- end
- RUBY
-
- boot_rails
- require 'rack/test'
- extend Rack::Test::Methods
-
- get "/admin/foo/bar"
- assert_equal 200, last_response.status
- assert_equal "Rendered from namespace", last_response.body
- end
-
- def test_initializers
- $plugin_initializer = false
- @plugin.write "config/initializers/foo.rb", <<-RUBY
- $plugin_initializer = true
- RUBY
-
- boot_rails
- assert $plugin_initializer
- end
-
- def test_midleware_referenced_in_configuration
- @plugin.write "lib/bukkits.rb", <<-RUBY
- class Bukkits
- def initialize(app)
- @app = app
- end
-
- def call(env)
- @app.call(env)
- end
- end
- RUBY
-
- add_to_config "config.middleware.use \"Bukkits\""
- boot_rails
- end
- end
-end
diff --git a/tasks/release.rb b/tasks/release.rb
index 2422efa786..650b381e0f 100644
--- a/tasks/release.rb
+++ b/tasks/release.rb
@@ -1,4 +1,4 @@
-FRAMEWORKS = %w( activesupport activemodel activerecord activeresource actionpack actionmailer railties )
+FRAMEWORKS = %w( activesupport activemodel activerecord actionpack actionmailer railties )
root = File.expand_path('../../', __FILE__)
version = File.read("#{root}/RAILS_VERSION").strip
@@ -26,16 +26,16 @@ directory "pkg"
major, minor, tiny, pre = version.split('.')
pre = pre ? pre.inspect : "nil"
- ruby.gsub! /^(\s*)MAJOR = .*?$/, "\\1MAJOR = #{major}"
+ ruby.gsub!(/^(\s*)MAJOR = .*?$/, "\\1MAJOR = #{major}")
raise "Could not insert MAJOR in #{file}" unless $1
- ruby.gsub! /^(\s*)MINOR = .*?$/, "\\1MINOR = #{minor}"
+ ruby.gsub!(/^(\s*)MINOR = .*?$/, "\\1MINOR = #{minor}")
raise "Could not insert MINOR in #{file}" unless $1
- ruby.gsub! /^(\s*)TINY = .*?$/, "\\1TINY = #{tiny}"
+ ruby.gsub!(/^(\s*)TINY = .*?$/, "\\1TINY = #{tiny}")
raise "Could not insert TINY in #{file}" unless $1
- ruby.gsub! /^(\s*)PRE = .*?$/, "\\1PRE = #{pre}"
+ ruby.gsub!(/^(\s*)PRE = .*?$/, "\\1PRE = #{pre}")
raise "Could not insert PRE in #{file}" unless $1
File.open(file, 'w') { |f| f.write ruby }
@@ -66,7 +66,7 @@ namespace :changelog do
FRAMEWORKS.each do |fw|
require 'date'
replace = '\1(' + Date.today.strftime('%B %d, %Y') + ')'
- fname = File.join fw, 'CHANGELOG'
+ fname = File.join fw, 'CHANGELOG.md'
contents = File.read(fname).sub(/^([^(]*)\(unreleased\)/, replace)
File.open(fname, 'wb') { |f| f.write contents }
@@ -76,7 +76,7 @@ namespace :changelog do
task :release_summary do
FRAMEWORKS.each do |fw|
puts "## #{fw}"
- fname = File.join fw, 'CHANGELOG'
+ fname = File.join fw, 'CHANGELOG.md'
contents = File.readlines fname
contents.shift
changes = []
@@ -116,6 +116,7 @@ namespace :all do
task :tag do
sh "git tag #{tag}"
+ sh "git push --tags"
end
task :release => %w(ensure_clean_state build commit tag push)
diff --git a/tools/profile b/tools/profile
index a6e3b41900..6cc935bce5 100755
--- a/tools/profile
+++ b/tools/profile
@@ -1,14 +1,12 @@
#!/usr/bin/env ruby
# Example:
# tools/profile_requires activesupport/lib/active_support.rb
-# tools/profile_requires activeresource/examples/simple.rb
abort 'Use REE so you can profile memory and object allocation' unless GC.respond_to?(:enable_stats)
ENV['NO_RELOAD'] ||= '1'
ENV['RAILS_ENV'] ||= 'development'
GC.enable_stats
-require 'rubygems'
Gem.source_index
require 'benchmark'
diff --git a/version.rb b/version.rb
index 254227ecf7..ec878f9dcf 100644
--- a/version.rb
+++ b/version.rb
@@ -1,7 +1,7 @@
module Rails
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"